From d630115cce90b53d81d9305db08389025df6204a Mon Sep 17 00:00:00 2001 From: h7x4 Date: Thu, 12 Nov 2020 00:17:02 +0100 Subject: [PATCH] add some more logic --- Exercise 10/chess/board.py | 113 ++++++++++++++++++++++++++++++++----- Exercise 10/chess/chess.py | 17 +++++- Exercise 10/chess/piece.py | 23 ++++++-- 3 files changed, 133 insertions(+), 20 deletions(-) diff --git a/Exercise 10/chess/board.py b/Exercise 10/chess/board.py index 3aeb4be..a6163f2 100644 --- a/Exercise 10/chess/board.py +++ b/Exercise 10/chess/board.py @@ -1,4 +1,6 @@ +from typing import Callable, Iterable from os import system +from shutil import get_terminal_size as getTerminalSize from piece import Piece @@ -7,13 +9,11 @@ class Board: def __init__(self): self.boardArray = [ - # [], - [Piece('p', 'black') for _ in range(8)], + [Piece(type, 'black') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']], [Piece('p', 'black') for _ in range(8)], *[[None for _ in range(8)] for _ in range(4)], [Piece('p', 'white') for _ in range(8)], - [Piece('p', 'white') for _ in range(8)], - # [], + [Piece(type, 'white') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']], ] def draw( @@ -22,7 +22,6 @@ class Board: 'highlightedContent': [], 'highlightEscapeCodes': ('\033[32;5;7m', '\033[0m'), 'highlightedBoxes': [], - 'center': False } ) -> str: """Returns a string representing the board @@ -31,7 +30,6 @@ class Board: highlightedContent: [(x,y)] - Pieces to color highlightEscapeCodes: (str, str) - Terminal escape codes to color highlightedContent with highlightedBoxes: [(x,y)] - Boxes to make bold - center: Bool - Whether or not to indent the board into the center """ def fillConfigDefaultValue(key, defaultValue): @@ -41,7 +39,6 @@ class Board: fillConfigDefaultValue('highlightedContent', []) fillConfigDefaultValue('highlightedBoxes', []) fillConfigDefaultValue('highlightEscapeCodes', ('\033[32;5;7m', '\033[0m')) - fillConfigDefaultValue('center', False) # Draw general outline stringArray = [list('┼' + '───┼' * 8)] + [[None] for _ in range(8 * 2)] @@ -107,20 +104,34 @@ class Board: for x, y in config['highlightedContent']: highlightContent(x, y) - # TODO: Center board - return '\n'.join([''.join(line) for line in stringArray]) - def selectPiece(self, player) -> tuple: + def selectPiece(self, player, x=0, y=0, centering=True) -> tuple: """Lets the user select a piece from a graphic board""" - x, y = 0, 0 while True: system('clear') - print(player.name, '\n') - print(self.draw({'highlightedBoxes': [(x, y)]}), '\n') + menuString = '\n' + player.name + '\n\n' + menuString += self.draw({'highlightedBoxes': [(x, y)]}) + '\n' + inputString = f" W E\nA S D <- Enter : " - key = input(f" W E\n A S D <- Enter : ")[0] + def centerText(text): + terminalWidth = getTerminalSize((60, 0))[0] # Column size 60 as fallback + return "\n".join(line.center(terminalWidth) for line in text.split('\n')) + + def centerBlockText(text): + terminalWidth = getTerminalSize((60, 0))[0] # Column size 60 as fallback + textArray = text.split('\n') + offset = int((terminalWidth - len(textArray[0])) / 2) + return "\n".join(offset * ' ' + line for line in textArray) + + if centering: + menuString = centerText(menuString) + inputString = centerBlockText(inputString) + + print(menuString) + + key = input(inputString)[0] if key in ['s', 'j'] and y != 7: y += 1 elif key in ['w', 'k'] and y != 0: y -= 1 elif key in ['d', 'l'] and x != 7: x += 1 @@ -129,3 +140,77 @@ class Board: def getPieceAt(self, x, y) -> Piece: return self.boardArray[y][x] + + 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): + if condition(piece): + result.append((x, y)) + 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""" + king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color) + 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""" + king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color) + 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) + + def getPositionsUntilKing(x, y, direction): + 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 + + def checkStaleMate(self, color) -> bool: + """Check whether a team is caught in stalemate. The color is the color of the team to check""" + enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color) + getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self, legalMoves = self.getPositionsToProtectKing(color)) + piecesHasNoLegalMoves = any( getLegalMoves(piece) == None for piece in enemyPieces) + return (not self.checkCheck(color)) and piecesHasNoLegalMoves + + def checkCheckMate(self, color) -> bool: + """Check whether a team is caught in checkmate. The color is the color of the team to check""" + enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color) + getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self, legalMoves = self.getPositionsToProtectKing(color)) + piecesHasNoLegalMoves = any( getLegalMoves(piece) == None for piece in enemyPieces) + return self.checkCheck(color) and piecesHasNoLegalMoves + + 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 + diff --git a/Exercise 10/chess/chess.py b/Exercise 10/chess/chess.py index baad40a..431c2cc 100644 --- a/Exercise 10/chess/chess.py +++ b/Exercise 10/chess/chess.py @@ -1,7 +1,8 @@ -from board import Board from os import system from dataclasses import dataclass +from board import Board + @dataclass class Player: name: str @@ -12,8 +13,20 @@ class Chess: def __init__(self, players): self.players = players + def lose(self, player): + print(player.name, 'lost.') + exit(0) + def update(self): system('clear') + board.selectPiece(players[0]) + if board.checkCheckMate(players[1].color): + self.lose(players[1]) + + system('clear') + board.selectPiece(players[1]) + if board.checkCheckMate(players[0].color): + self.lose(players[0]) def loop(self): while True: @@ -30,4 +43,4 @@ if __name__ == "__main__": # game = Chess(('Spiller 1', 'Spiller 2')) # game.loop() board = Board() - print(board.selectPiece(players[0])) + print(board.selectPiece(players[0], centering=True)) diff --git a/Exercise 10/chess/piece.py b/Exercise 10/chess/piece.py index 32f7414..b21c558 100644 --- a/Exercise 10/chess/piece.py +++ b/Exercise 10/chess/piece.py @@ -1,5 +1,6 @@ from typing import Iterable, Callable from itertools import product +from copy import deepcopy class Piece: @@ -12,21 +13,29 @@ class Piece: return self.type.upper() if self.color == 'white' else self.type @staticmethod - def possibleMoves(x, y, board) -> Callable[[int, int], Iterable[tuple]]: + def possibleMoves(x, y, board, legalMoves=None) -> Callable[[int, int], Iterable[tuple]]: piece = board.getPieceAt(x, y) moves = [] + # TODO: If player is in check, only validate the moves that are leading to places that can be blocked in order to save the king. Make a function that overwrites `moves`. Notice: The king is the only piece that can 'Escape' to a position that may or may not be in the list of positions to be blocked. Make an exception + pieceIsEnemyColor = lambda pieceToCheck: pieceToCheck != None and pieceToCheck.color != piece.color pieceIsEmpty = lambda pieceToCheck: pieceToCheck == None pieceIsEmptyOrEnemyColor = lambda pieceToCheck: pieceToCheck == None or pieceToCheck.color != piece.color - def addMoveIfTrue(xOffset, yOffset, condition): + def addMoveIfTrue(xOffset, yOffset, condition: Callable[[Piece], bool]): + """Tests a condition against a position away from self. Adds if condition returns true""" if condition(board.getPieceAt(x + xOffset, y + yOffset)): moves.append((x + xOffset, y + yOffset)) return True return False - def addWhileInsideBoard(direction): + def assertNotCheck(newX, newY) -> bool: + testBoard = deepcopy(board) + testBoard.movePiece((x, y), (newX, newY)) # J + return not testBoard.checkCheck() + + def addWhileInsideBoard(direction: tuple): localX = x localY = y while localX not in [0, 7] and localY not in [0, 7]: @@ -55,7 +64,8 @@ class Piece: positions.remove((0, 0)) for position in positions: addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor) - # TODO: Check moves for check and remove the bad ones + + moves = [position for position in moves if assertNotCheck(*position)] elif piece.type == 'r': for direction in product([0, 1], repeat=2): @@ -71,4 +81,9 @@ class Piece: for direction in directions: addWhileInsideBoard(direction) + #TODO: remove moves that will put the king in check + + if legalMoves != None and piece.type != 'k': + moves = [move for move in moves if move in legalMoves] + return moves