2020-11-12 19:17:16 +01:00
|
|
|
from typing import Callable, Iterable, Union
|
2020-11-10 23:56:21 +01:00
|
|
|
from os import system
|
|
|
|
|
|
|
|
from piece import Piece
|
2020-11-12 19:17:16 +01:00
|
|
|
from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap, determineMove
|
2020-11-10 23:56:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Board:
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
def __init__(self, boardState=None):
|
2020-11-10 23:56:21 +01:00
|
|
|
self.boardArray = [
|
2020-11-12 00:17:02 +01:00
|
|
|
[Piece(type, 'black') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']],
|
2020-11-10 23:56:21 +01:00
|
|
|
[Piece('p', 'black') for _ in range(8)],
|
|
|
|
*[[None for _ in range(8)] for _ in range(4)],
|
|
|
|
[Piece('p', 'white') for _ in range(8)],
|
2020-11-12 00:17:02 +01:00
|
|
|
[Piece(type, 'white') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']],
|
2020-11-12 19:17:16 +01:00
|
|
|
] if boardState == None else boardState
|
2020-11-10 23:56:21 +01:00
|
|
|
|
|
|
|
def draw(
|
|
|
|
self,
|
|
|
|
config={
|
|
|
|
'highlightedContent': [],
|
|
|
|
'highlightEscapeCodes': ('\033[32;5;7m', '\033[0m'),
|
|
|
|
'highlightedBoxes': [],
|
|
|
|
}
|
|
|
|
) -> str:
|
|
|
|
"""Returns a string representing the board
|
|
|
|
|
|
|
|
config options:
|
|
|
|
highlightedContent: [(x,y)] - Pieces to color
|
|
|
|
highlightEscapeCodes: (str, str) - Terminal escape codes to color highlightedContent with
|
|
|
|
highlightedBoxes: [(x,y)] - Boxes to make bold
|
|
|
|
"""
|
|
|
|
|
|
|
|
def fillConfigDefaultValue(key, defaultValue):
|
|
|
|
if key not in config:
|
|
|
|
config[key] = defaultValue
|
|
|
|
|
|
|
|
fillConfigDefaultValue('highlightedContent', [])
|
|
|
|
fillConfigDefaultValue('highlightedBoxes', [])
|
|
|
|
fillConfigDefaultValue('highlightEscapeCodes', ('\033[32;5;7m', '\033[0m'))
|
|
|
|
|
|
|
|
# Draw general outline
|
|
|
|
stringArray = [list('┼' + '───┼' * 8)] + [[None] for _ in range(8 * 2)]
|
|
|
|
for y, row in enumerate(self.boardArray):
|
2020-11-12 19:17:16 +01:00
|
|
|
for x, _ in enumerate(row):
|
2020-11-10 23:56:21 +01:00
|
|
|
stringArray[2 * y + 1][4 * x] = '│'
|
|
|
|
stringArray[2 * y + 2][4 * x] = '┼'
|
|
|
|
|
|
|
|
stringArray[2 * y + 1] += list(
|
|
|
|
' {} │'.format(str(self.boardArray[y][x]) if self.boardArray[y][x] != None else ' '))
|
|
|
|
stringArray[2 * y + 2] += list('───┼')
|
|
|
|
|
|
|
|
# Overwrite corners
|
|
|
|
stringArray[0][0] = '╭'
|
|
|
|
stringArray[0][-1] = '╮'
|
|
|
|
stringArray[-1][0] = '╰'
|
|
|
|
stringArray[-1][-1] = '╯'
|
|
|
|
|
|
|
|
# Overwrite T-junctions
|
|
|
|
for i in range(int(len(stringArray[0]) / 4) - 1):
|
|
|
|
stringArray[0][i * 4 + 4] = '┬'
|
|
|
|
stringArray[-1][i * 4 + 4] = '┴'
|
|
|
|
for i in range(int(len(stringArray) / 2) - 1):
|
|
|
|
stringArray[i * 2 + 2][0] = '├'
|
|
|
|
stringArray[i * 2 + 2][-1] = '┤'
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
def highlightContent(x, y, escapeCodes=config['highlightEscapeCodes']):
|
2020-11-10 23:56:21 +01:00
|
|
|
"""highlight contents of a piece with xterm-256colors modifiers"""
|
2020-11-12 19:17:16 +01:00
|
|
|
stringArray[y * 2 + 1][x * 4 + 1] = \
|
|
|
|
escapeCodes[0] + stringArray[y * 2 + 1][x * 4 + 1]
|
|
|
|
stringArray[y * 2 + 1][x * 4 + 3] += escapeCodes[1]
|
2020-11-10 23:56:21 +01:00
|
|
|
|
|
|
|
def highlightBox(x, y):
|
|
|
|
"""Make box around a piece bold"""
|
|
|
|
|
|
|
|
pointsToChange = \
|
|
|
|
[(x * 4 + 0, y * 2 + i) for i in range(3)] + \
|
|
|
|
[(x * 4 + 4, y * 2 + i) for i in range(3)] + \
|
|
|
|
[(x * 4 + i, y * 2 + 0) for i in range(1,4)] + \
|
|
|
|
[(x * 4 + i, y * 2 + 2) for i in range(1,4)]
|
|
|
|
|
|
|
|
for x, y in pointsToChange:
|
2020-11-12 19:17:16 +01:00
|
|
|
stringArray[y][x] = boxCharsToBoldBoxCharsMap[
|
|
|
|
stringArray[y]
|
|
|
|
[x]] if stringArray[y][x] in boxCharsToBoldBoxCharsMap else stringArray[y][x]
|
|
|
|
|
|
|
|
# Color white pieces
|
|
|
|
for piece in self.getPositionsWhere(lambda piece: piece.color == 'white'):
|
|
|
|
highlightContent(*piece, ('\033[7m', '\033[0m'))
|
2020-11-10 23:56:21 +01:00
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
for box in config['highlightedBoxes']:
|
|
|
|
highlightBox(*box)
|
|
|
|
for piece in config['highlightedContent']:
|
|
|
|
highlightContent(*piece)
|
2020-11-10 23:56:21 +01:00
|
|
|
|
|
|
|
return '\n'.join([''.join(line) for line in stringArray])
|
|
|
|
|
2020-11-12 00:17:02 +01:00
|
|
|
def selectPiece(self, player, x=0, y=0, centering=True) -> tuple:
|
2020-11-10 23:56:21 +01:00
|
|
|
"""Lets the user select a piece from a graphic board"""
|
|
|
|
|
|
|
|
while True:
|
|
|
|
system('clear')
|
2020-11-12 19:17:16 +01:00
|
|
|
playerString = '\n' + player.name + '\n\n'
|
|
|
|
menuString = self.draw({'highlightedBoxes': [(x, y)]}) + '\n'
|
2020-11-12 00:17:02 +01:00
|
|
|
inputString = f" W E\nA S D <- Enter : "
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
if centering:
|
|
|
|
playerString = centerText(playerString)
|
|
|
|
menuString = centerBlockText(menuString)
|
|
|
|
inputString = centerBlockText(inputString)
|
2020-11-12 00:17:02 +01:00
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
print(playerString)
|
|
|
|
print(menuString)
|
|
|
|
|
|
|
|
try:
|
|
|
|
key = input(inputString)[0]
|
|
|
|
except IndexError:
|
|
|
|
key = ''
|
|
|
|
try:
|
|
|
|
if move := determineMove(key, x, y, (0, 7)):
|
|
|
|
x += move[0]
|
|
|
|
y += move[1]
|
|
|
|
elif key == 'e' and self.getPieceAt(x, y).color==player.color:
|
|
|
|
return (x, y)
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
def selectMove(self, player, x, y, legalMoves, centering=True) -> Union[tuple, bool]:
|
|
|
|
"""Lets the user select a move to make from a graphic board"""
|
|
|
|
|
|
|
|
while True:
|
|
|
|
system('clear')
|
|
|
|
playerString = '\n' + player.name + '\n\n'
|
|
|
|
menuString = self.draw({
|
|
|
|
'highlightedBoxes': [(x, y)],
|
|
|
|
'highlightedContent': legalMoves
|
|
|
|
}) + '\n'
|
|
|
|
inputString = f"Q W E\nA S D <- Enter : "
|
2020-11-12 00:17:02 +01:00
|
|
|
|
|
|
|
if centering:
|
2020-11-12 19:17:16 +01:00
|
|
|
playerString = centerText(playerString)
|
|
|
|
menuString = centerBlockText(menuString)
|
2020-11-12 00:17:02 +01:00
|
|
|
inputString = centerBlockText(inputString)
|
2020-11-10 23:56:21 +01:00
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
print(playerString)
|
2020-11-12 00:17:02 +01:00
|
|
|
print(menuString)
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
try:
|
|
|
|
key = input(inputString)[0]
|
|
|
|
except IndexError:
|
|
|
|
key = ''
|
|
|
|
if move := determineMove(key, x, y, (0, 7)):
|
|
|
|
x += move[0]
|
|
|
|
y += move[1]
|
|
|
|
elif key == 'q':
|
|
|
|
return False
|
|
|
|
elif key == 'e' and (x, y) in legalMoves:
|
|
|
|
return (x, y)
|
2020-11-10 23:56:21 +01:00
|
|
|
|
|
|
|
def getPieceAt(self, x, y) -> Piece:
|
2020-11-12 19:17:16 +01:00
|
|
|
try:
|
|
|
|
return self.boardArray[y][x]
|
|
|
|
except IndexError:
|
|
|
|
return None
|
2020-11-12 00:17:02 +01:00
|
|
|
|
|
|
|
def getPositionsWhere(self, condition: Callable[[Piece], bool]) -> Iterable[tuple]:
|
|
|
|
""" Returns a list of xy pairs of the pieces where a condition is met """
|
|
|
|
|
|
|
|
result = []
|
|
|
|
for y, row in enumerate(self.boardArray):
|
|
|
|
for x, piece in enumerate(row):
|
2020-11-12 19:17:16 +01:00
|
|
|
try:
|
|
|
|
if condition(piece):
|
|
|
|
result.append((x, y))
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
2020-11-12 00:17:02 +01:00
|
|
|
return result
|
|
|
|
|
|
|
|
def checkCheck(self, color) -> bool:
|
|
|
|
"""Check whether a team is caught in check. The color is the color of the team to check"""
|
2020-11-12 19:17:16 +01:00
|
|
|
king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color)[0]
|
2020-11-12 00:17:02 +01:00
|
|
|
piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color)
|
|
|
|
return any([king in Piece.possibleMoves(*piece, self) for piece in piecesToCheck])
|
|
|
|
|
|
|
|
def getPositionsToProtectKing(self, color) -> Iterable[tuple]:
|
|
|
|
"""Get a list of the positions to protect in order to protect the king when in check. The color is the color of the team who's in check"""
|
2020-11-12 19:17:16 +01:00
|
|
|
king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color)[0]
|
2020-11-12 00:17:02 +01:00
|
|
|
piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color)
|
|
|
|
for piece in piecesToCheck:
|
|
|
|
if king not in Piece.possibleMoves(*piece, self):
|
|
|
|
piecesToCheck.remove(piece)
|
|
|
|
result = []
|
|
|
|
for piece in piecesToCheck:
|
|
|
|
result.append(piece)
|
|
|
|
if self.getPieceAt(*piece).type not in ['p', 'n', 'k']:
|
|
|
|
|
|
|
|
def getDirection(fromPosition, toPosition) -> tuple:
|
|
|
|
x = -1 if toPosition[0] > fromPosition[0] else \
|
|
|
|
0 if toPosition[0] == fromPosition[0] else 1
|
|
|
|
y = -1 if toPosition[1] > fromPosition[1] else \
|
|
|
|
0 if toPosition[1] == fromPosition[1] else 1
|
|
|
|
return (x, y)
|
|
|
|
|
|
|
|
direction = getDirection(piece, king)
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
def getPositionsUntilKing(x, y, direction) -> Iterable[tuple]:
|
2020-11-12 00:17:02 +01:00
|
|
|
result = []
|
|
|
|
while self.getPieceAt(x, y) == None:
|
|
|
|
result.append((x, y))
|
|
|
|
x += direction[0]
|
|
|
|
y += direction[1]
|
|
|
|
return result
|
|
|
|
|
|
|
|
result += getPositionsUntilKing(*piece, direction)
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
2020-11-12 19:17:16 +01:00
|
|
|
def playerHasLegalMoves(self, color) -> bool:
|
|
|
|
enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color)
|
|
|
|
getLegalMoves = lambda piece: Piece.possibleMoves(
|
|
|
|
*piece, self, legalMoves=self.getPositionsToProtectKing(color))
|
|
|
|
return not any(getLegalMoves(piece) == None for piece in enemyPieces)
|
|
|
|
|
2020-11-12 00:17:02 +01:00
|
|
|
def checkStaleMate(self, color) -> bool:
|
|
|
|
"""Check whether a team is caught in stalemate. The color is the color of the team to check"""
|
2020-11-12 19:17:16 +01:00
|
|
|
return (not self.checkCheck(color)) and not self.playerHasLegalMoves(color)
|
2020-11-12 00:17:02 +01:00
|
|
|
|
|
|
|
def checkCheckMate(self, color) -> bool:
|
|
|
|
"""Check whether a team is caught in checkmate. The color is the color of the team to check"""
|
2020-11-12 19:17:16 +01:00
|
|
|
return self.checkCheck(color) and not self.playerHasLegalMoves(color)
|
2020-11-12 00:17:02 +01:00
|
|
|
|
|
|
|
def movePiece(self, position, toPosition, piecesToRemove=None):
|
|
|
|
x, y = position
|
|
|
|
toX, toY = toPosition
|
|
|
|
self.boardArray[toY][toX] = self.boardArray[y][x]
|
|
|
|
self.boardArray[y][x] = None
|
|
|
|
|
|
|
|
if piecesToRemove != None:
|
|
|
|
for x, y in piecesToRemove:
|
|
|
|
self.boardArray[y][x] = None
|