Make pieces move

This commit is contained in:
Oystein Kristoffer Tveit 2020-11-12 19:17:16 +01:00
parent d630115cce
commit 23adf99347
4 changed files with 210 additions and 90 deletions

View File

@ -1,20 +1,20 @@
from typing import Callable, Iterable from typing import Callable, Iterable, Union
from os import system from os import system
from shutil import get_terminal_size as getTerminalSize
from piece import Piece from piece import Piece
from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap, determineMove
class Board: class Board:
def __init__(self): def __init__(self, boardState=None):
self.boardArray = [ self.boardArray = [
[Piece(type, 'black') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']], [Piece(type, 'black') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']],
[Piece('p', 'black') for _ in range(8)], [Piece('p', 'black') for _ in range(8)],
*[[None for _ in range(8)] for _ in range(4)], *[[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']], [Piece(type, 'white') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']],
] ] if boardState == None else boardState
def draw( def draw(
self, self,
@ -43,7 +43,7 @@ class Board:
# Draw general outline # Draw general outline
stringArray = [list('' + '───┼' * 8)] + [[None] for _ in range(8 * 2)] stringArray = [list('' + '───┼' * 8)] + [[None] for _ in range(8 * 2)]
for y, row in enumerate(self.boardArray): for y, row in enumerate(self.boardArray):
for x, column in enumerate(row): for x, _ in enumerate(row):
stringArray[2 * y + 1][4 * x] = '' stringArray[2 * y + 1][4 * x] = ''
stringArray[2 * y + 2][4 * x] = '' stringArray[2 * y + 2][4 * x] = ''
@ -65,30 +65,15 @@ class Board:
stringArray[i * 2 + 2][0] = '' stringArray[i * 2 + 2][0] = ''
stringArray[i * 2 + 2][-1] = '' stringArray[i * 2 + 2][-1] = ''
def highlightContent(x, y): def highlightContent(x, y, escapeCodes=config['highlightEscapeCodes']):
"""highlight contents of a piece with xterm-256colors modifiers""" """highlight contents of a piece with xterm-256colors modifiers"""
stringArray[y * 2 + stringArray[y * 2 + 1][x * 4 + 1] = \
1][x * 4 + escapeCodes[0] + stringArray[y * 2 + 1][x * 4 + 1]
1] = config['highlightEscapeCodes'][0] + stringArray[y * 2 + 1][x * 4 + 1] stringArray[y * 2 + 1][x * 4 + 3] += escapeCodes[1]
stringArray[y * 2 + 1][x * 4 + 3] += config['highlightEscapeCodes'][1]
def highlightBox(x, y): def highlightBox(x, y):
"""Make box around a piece bold""" """Make box around a piece bold"""
characterMap = {
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
}
pointsToChange = \ pointsToChange = \
[(x * 4 + 0, y * 2 + i) for i in range(3)] + \ [(x * 4 + 0, y * 2 + i) for i in range(3)] + \
[(x * 4 + 4, y * 2 + i) for i in range(3)] + \ [(x * 4 + 4, y * 2 + i) for i in range(3)] + \
@ -96,13 +81,18 @@ class Board:
[(x * 4 + i, y * 2 + 2) for i in range(1,4)] [(x * 4 + i, y * 2 + 2) for i in range(1,4)]
for x, y in pointsToChange: for x, y in pointsToChange:
stringArray[y][x] = characterMap[ stringArray[y][x] = boxCharsToBoldBoxCharsMap[
stringArray[y][x]] if stringArray[y][x] in characterMap else stringArray[y][x] stringArray[y]
[x]] if stringArray[y][x] in boxCharsToBoldBoxCharsMap else stringArray[y][x]
for x, y in config['highlightedBoxes']: # Color white pieces
highlightBox(x, y) for piece in self.getPositionsWhere(lambda piece: piece.color == 'white'):
for x, y in config['highlightedContent']: highlightContent(*piece, ('\033[7m', '\033[0m'))
highlightContent(x, y)
for box in config['highlightedBoxes']:
highlightBox(*box)
for piece in config['highlightedContent']:
highlightContent(*piece)
return '\n'.join([''.join(line) for line in stringArray]) return '\n'.join([''.join(line) for line in stringArray])
@ -111,35 +101,68 @@ class Board:
while True: while True:
system('clear') system('clear')
menuString = '\n' + player.name + '\n\n' playerString = '\n' + player.name + '\n\n'
menuString += self.draw({'highlightedBoxes': [(x, y)]}) + '\n' menuString = self.draw({'highlightedBoxes': [(x, y)]}) + '\n'
inputString = f" W E\nA S D <- Enter : " inputString = f" W E\nA S D <- Enter : "
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: if centering:
menuString = centerText(menuString) playerString = centerText(playerString)
menuString = centerBlockText(menuString)
inputString = centerBlockText(inputString) inputString = centerBlockText(inputString)
print(playerString)
print(menuString) print(menuString)
key = input(inputString)[0] try:
if key in ['s', 'j'] and y != 7: y += 1 key = input(inputString)[0]
elif key in ['w', 'k'] and y != 0: y -= 1 except IndexError:
elif key in ['d', 'l'] and x != 7: x += 1 key = ''
elif key in ['a', 'h'] and x != 0: x -= 1 try:
elif key == 'e': return (x, y) 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 : "
if centering:
playerString = centerText(playerString)
menuString = centerBlockText(menuString)
inputString = centerBlockText(inputString)
print(playerString)
print(menuString)
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)
def getPieceAt(self, x, y) -> Piece: def getPieceAt(self, x, y) -> Piece:
return self.boardArray[y][x] try:
return self.boardArray[y][x]
except IndexError:
return None
def getPositionsWhere(self, condition: Callable[[Piece], bool]) -> Iterable[tuple]: def getPositionsWhere(self, condition: Callable[[Piece], bool]) -> Iterable[tuple]:
""" Returns a list of xy pairs of the pieces where a condition is met """ """ Returns a list of xy pairs of the pieces where a condition is met """
@ -147,19 +170,22 @@ class Board:
result = [] result = []
for y, row in enumerate(self.boardArray): for y, row in enumerate(self.boardArray):
for x, piece in enumerate(row): for x, piece in enumerate(row):
if condition(piece): try:
result.append((x, y)) if condition(piece):
result.append((x, y))
except AttributeError:
pass
return result return result
def checkCheck(self, color) -> bool: def checkCheck(self, color) -> bool:
"""Check whether a team is caught in check. The color is the color of the team to check""" """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) king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color)[0]
piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color) piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color)
return any([king in Piece.possibleMoves(*piece, self) for piece in piecesToCheck]) return any([king in Piece.possibleMoves(*piece, self) for piece in piecesToCheck])
def getPositionsToProtectKing(self, color) -> Iterable[tuple]: 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""" """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) king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color)[0]
piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color) piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color)
for piece in piecesToCheck: for piece in piecesToCheck:
if king not in Piece.possibleMoves(*piece, self): if king not in Piece.possibleMoves(*piece, self):
@ -178,7 +204,7 @@ class Board:
direction = getDirection(piece, king) direction = getDirection(piece, king)
def getPositionsUntilKing(x, y, direction): def getPositionsUntilKing(x, y, direction) -> Iterable[tuple]:
result = [] result = []
while self.getPieceAt(x, y) == None: while self.getPieceAt(x, y) == None:
result.append((x, y)) result.append((x, y))
@ -190,19 +216,19 @@ class Board:
return result return result
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)
def checkStaleMate(self, color) -> bool: def checkStaleMate(self, color) -> bool:
"""Check whether a team is caught in stalemate. The color is the color of the team to check""" """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) return (not self.checkCheck(color)) and not self.playerHasLegalMoves(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: def checkCheckMate(self, color) -> bool:
"""Check whether a team is caught in checkmate. The color is the color of the team to check""" """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) return self.checkCheck(color) and not self.playerHasLegalMoves(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): def movePiece(self, position, toPosition, piecesToRemove=None):
x, y = position x, y = position
@ -213,4 +239,3 @@ class Board:
if piecesToRemove != None: if piecesToRemove != None:
for x, y in piecesToRemove: for x, y in piecesToRemove:
self.boardArray[y][x] = None self.boardArray[y][x] = None

View File

@ -2,6 +2,7 @@ from os import system
from dataclasses import dataclass from dataclasses import dataclass
from board import Board from board import Board
from piece import Piece
@dataclass @dataclass
class Player: class Player:
@ -12,21 +13,42 @@ class Chess:
def __init__(self, players): def __init__(self, players):
self.players = players self.players = players
self.board = Board()
def lose(self, player): def makeMove(self, player):
print(player.name, 'lost.') chosenTile = 0,0
while True:
piece = self.board.selectPiece(player, *chosenTile)
chosenTile = piece
possibleMoves = Piece.possibleMoves(*piece, self.board)
if move := self.board.selectMove(player, *piece, possibleMoves):
break
print(move)
input()
self.board.movePiece(piece, move)
def win(self, player):
print(player.name, 'won.')
exit(0)
def tie(self):
print('Stalemate')
exit(0) exit(0)
def update(self): def update(self):
system('clear') system('clear')
board.selectPiece(players[0]) self.makeMove(players[0])
if board.checkCheckMate(players[1].color): # if self.board.checkCheckMate(players[1].color):
self.lose(players[1]) # self.win(players[0])
# elif self.board.checkStaleMate(players[1].color):
# self.tie()
system('clear') system('clear')
board.selectPiece(players[1]) self.makeMove(players[1])
if board.checkCheckMate(players[0].color): # if self.board.checkCheckMate(players[0].color):
self.lose(players[0]) # self.win(players[1])
# elif self.board.checkStaleMate(players[0].color):
# self.tie()
def loop(self): def loop(self):
while True: while True:
@ -40,7 +62,7 @@ if __name__ == "__main__":
Player('Spiller 2', 'black'), Player('Spiller 2', 'black'),
) )
# game = Chess(('Spiller 1', 'Spiller 2')) game = Chess(('Spiller 1', 'Spiller 2'))
# game.loop() game.loop()
board = Board() # board = Board()
print(board.selectPiece(players[0], centering=True)) # print(board.selectPiece(players[0], centering=True))

View File

@ -1,6 +1,8 @@
from typing import Iterable, Callable from typing import Iterable, Callable
from itertools import product from itertools import product
from copy import deepcopy from copy import deepcopy
from sys import setrecursionlimit as setRecursionLimit
setRecursionLimit(100000)
class Piece: class Piece:
@ -12,6 +14,25 @@ class Piece:
def __str__(self): def __str__(self):
return self.type.upper() if self.color == 'white' else self.type return self.type.upper() if self.color == 'white' else self.type
@property
def symbol(self):
symbols = [{
'p': '',
'r': '',
'n': '',
'b': '',
'q': '',
'k': '',
}, {
'p': '♟︎',
'r': '',
'n': '',
'b': '',
'q': '',
'k': '',
}]
return symbols[0 if self.color == 'white' else 1][self.type]
@staticmethod @staticmethod
def possibleMoves(x, y, board, legalMoves=None) -> Callable[[int, int], Iterable[tuple]]: def possibleMoves(x, y, board, legalMoves=None) -> Callable[[int, int], Iterable[tuple]]:
piece = board.getPieceAt(x, y) piece = board.getPieceAt(x, y)
@ -22,6 +43,7 @@ class Piece:
pieceIsEnemyColor = lambda pieceToCheck: pieceToCheck != None and pieceToCheck.color != piece.color pieceIsEnemyColor = lambda pieceToCheck: pieceToCheck != None and pieceToCheck.color != piece.color
pieceIsEmpty = lambda pieceToCheck: pieceToCheck == None pieceIsEmpty = lambda pieceToCheck: pieceToCheck == None
pieceIsEmptyOrEnemyColor = lambda pieceToCheck: pieceToCheck == None or pieceToCheck.color != piece.color pieceIsEmptyOrEnemyColor = lambda pieceToCheck: pieceToCheck == None or pieceToCheck.color != piece.color
positionInsideBounds = lambda x, y: x in range(8) and y in range(8)
def addMoveIfTrue(xOffset, yOffset, condition: Callable[[Piece], bool]): def addMoveIfTrue(xOffset, yOffset, condition: Callable[[Piece], bool]):
"""Tests a condition against a position away from self. Adds if condition returns true""" """Tests a condition against a position away from self. Adds if condition returns true"""
@ -30,15 +52,15 @@ class Piece:
return True return True
return False return False
# TODO: fix deepcopy segfault and recursion error
def assertNotCheck(newX, newY) -> bool: def assertNotCheck(newX, newY) -> bool:
testBoard = deepcopy(board) testBoard = deepcopy(board)
testBoard.movePiece((x, y), (newX, newY)) # J testBoard.movePiece((x, y), (newX, newY))
return not testBoard.checkCheck() return not testBoard.checkCheck(piece.color)
def addWhileInsideBoard(direction: tuple): def addWhileInsideBoard(direction: tuple):
localX = x localX, localY = x, y
localY = y while positionInsideBounds(localX, localY):
while localX not in [0, 7] and localY not in [0, 7]:
localX += direction[0] localX += direction[0]
localY += direction[1] localY += direction[1]
if board.getPieceAt(localX, localY) == None: if board.getPieceAt(localX, localY) == None:
@ -49,13 +71,25 @@ class Piece:
return return
if piece.type == 'p': if piece.type == 'p':
addMoveIfTrue(1, 1, pieceIsEnemyColor) localY = 1 if piece.color == 'black' else -1
addMoveIfTrue(-1, 1, pieceIsEnemyColor) startPosition = 1 if piece.color == 'black' else 6
if addMoveIfTrue(0, 1, pieceIsEmpty): addMoveIfTrue(1, localY, pieceIsEnemyColor)
addMoveIfTrue(0, 2, lambda pieceToCheck: pieceToCheck == None and piece.moves == 0) addMoveIfTrue(-1, localY, pieceIsEnemyColor)
if addMoveIfTrue(0, localY, pieceIsEmpty):
addMoveIfTrue(0, localY * 2,
lambda pieceToCheck: pieceToCheck == None and y == startPosition)
elif piece.type == 'n': elif piece.type == 'n':
positions = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2), (2, -1), (2, 1)] positions = [
(-2, -1),
(-2, 1),
(-1, -2),
(-1, 2),
(1, -2),
(1, 2),
(2, -1),
(2, 1),
]
for position in positions: for position in positions:
addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor) addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor)
@ -65,10 +99,8 @@ class Piece:
for position in positions: for position in positions:
addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor) addMoveIfTrue(*position, pieceIsEmptyOrEnemyColor)
moves = [position for position in moves if assertNotCheck(*position)]
elif piece.type == 'r': elif piece.type == 'r':
for direction in product([0, 1], repeat=2): for direction in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
addWhileInsideBoard(direction) addWhileInsideBoard(direction)
elif piece.type == 'b': elif piece.type == 'b':
@ -81,9 +113,14 @@ class Piece:
for direction in directions: for direction in directions:
addWhileInsideBoard(direction) addWhileInsideBoard(direction)
#TODO: remove moves that will put the king in check # Remove moves that will lead the piece out of the board
moves = [move for move in moves if positionInsideBounds(*move)]
# Remove moves that won't block the path for whichever piece has caused the game to check
if legalMoves != None and piece.type != 'k': if legalMoves != None and piece.type != 'k':
moves = [move for move in moves if move in legalMoves] moves = [move for move in moves if move in legalMoves]
# Remove moves that will put the king in check
# moves = [position for position in moves if assertNotCheck(*position)]
return moves return moves

36
Exercise 10/chess/util.py Normal file
View File

@ -0,0 +1,36 @@
from shutil import get_terminal_size as getTerminalSize
# ░█▀▄░█▀█░█▀█░█▀▄░█▀█░█▄█░░░█▀▀░█░█░▀█▀░▀█▀
# ░█▀▄░█▀█░█░█░█░█░█░█░█░█░░░▀▀█░█▀█░░█░░░█░
# ░▀░▀░▀░▀░▀░▀░▀▀░░▀▀▀░▀░▀░░░▀▀▀░▀░▀░▀▀▀░░▀░
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)
boxCharsToBoldBoxCharsMap = {
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
}
def determineMove(key, x, y, maxmin) -> tuple:
if key in ['s', 'j'] and y != maxmin[1]: return (0, 1)
elif key in ['w', 'k'] and y != maxmin[0]: return (0, -1)
elif key in ['d', 'l'] and x != maxmin[1]: return (1, 0)
elif key in ['a', 'h'] and x != maxmin[0]: return (-1, 0)
else: return False