Finish logic

This commit is contained in:
Oystein Kristoffer Tveit 2020-11-12 23:43:39 +01:00
parent 23adf99347
commit 1bd7fa4e0e
3 changed files with 109 additions and 72 deletions

View File

@ -2,12 +2,13 @@ from typing import Callable, Iterable, Union
from os import system from os import system
from piece import Piece from piece import Piece
from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap, determineMove from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap as boldBoxChars, determineMove
class Board: class Board:
def __init__(self, boardState=None): def __init__(self, boardState=None):
"""Create a standard board if nothing else is defined in boardState"""
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)],
@ -16,14 +17,7 @@ class Board:
[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 ] if boardState == None else boardState
def draw( def draw(self, config={}) -> str:
self,
config={
'highlightedContent': [],
'highlightEscapeCodes': ('\033[32;5;7m', '\033[0m'),
'highlightedBoxes': [],
}
) -> str:
"""Returns a string representing the board """Returns a string representing the board
config options: config options:
@ -32,6 +26,7 @@ class Board:
highlightedBoxes: [(x,y)] - Boxes to make bold highlightedBoxes: [(x,y)] - Boxes to make bold
""" """
# Fill default values in config dict
def fillConfigDefaultValue(key, defaultValue): def fillConfigDefaultValue(key, defaultValue):
if key not in config: if key not in config:
config[key] = defaultValue config[key] = defaultValue
@ -40,15 +35,15 @@ class Board:
fillConfigDefaultValue('highlightedBoxes', []) fillConfigDefaultValue('highlightedBoxes', [])
fillConfigDefaultValue('highlightEscapeCodes', ('\033[32;5;7m', '\033[0m')) fillConfigDefaultValue('highlightEscapeCodes', ('\033[32;5;7m', '\033[0m'))
# Draw general outline # Draw general outline with ┼ as all corners
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, _ 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] = ''
stringArray[2 * y + 1] += list( symbol = str(self.boardArray[y][x]) if self.boardArray[y][x] != None else ' '
' {}'.format(str(self.boardArray[y][x]) if self.boardArray[y][x] != None else ' ')) stringArray[2 * y + 1] += list(' {}'.format(symbol))
stringArray[2 * y + 2] += list('───┼') stringArray[2 * y + 2] += list('───┼')
# Overwrite corners # Overwrite corners
@ -58,21 +53,21 @@ class Board:
stringArray[-1][-1] = '' stringArray[-1][-1] = ''
# Overwrite T-junctions # Overwrite T-junctions
for i in range(int(len(stringArray[0]) / 4) - 1): for i in range(int(len(stringArray[0]) / 4) - 1): # Loop row
stringArray[0][i * 4 + 4] = '' stringArray[0][i * 4 + 4] = ''
stringArray[-1][i * 4 + 4] = '' stringArray[-1][i * 4 + 4] = ''
for i in range(int(len(stringArray) / 2) - 1): for i in range(int(len(stringArray) / 2) - 1): # Loop column
stringArray[i * 2 + 2][0] = '' stringArray[i * 2 + 2][0] = ''
stringArray[i * 2 + 2][-1] = '' stringArray[i * 2 + 2][-1] = ''
def highlightContent(x, y, escapeCodes=config['highlightEscapeCodes']): def highlightContent(x, y, modifiers=config['highlightEscapeCodes']):
"""highlight contents of a piece with xterm-256colors modifiers""" """highlight inner part of a box with xterm-256colors modifiers"""
stringArray[y * 2 + 1][x * 4 + 1] = \ stringArray[y * 2 + 1][x * 4 + 1] = \
escapeCodes[0] + stringArray[y * 2 + 1][x * 4 + 1] modifiers[0] + stringArray[y * 2 + 1][x * 4 + 1]
stringArray[y * 2 + 1][x * 4 + 3] += escapeCodes[1] stringArray[y * 2 + 1][x * 4 + 3] += modifiers[1]
def highlightBox(x, y): def highlightBox(x, y):
"""Make box around a piece bold""" """Make box around a position bold"""
pointsToChange = \ pointsToChange = \
[(x * 4 + 0, y * 2 + i) for i in range(3)] + \ [(x * 4 + 0, y * 2 + i) for i in range(3)] + \
@ -80,10 +75,11 @@ class Board:
[(x * 4 + i, y * 2 + 0) for i in range(1,4)] + \ [(x * 4 + i, y * 2 + 0) for i in range(1,4)] + \
[(x * 4 + i, y * 2 + 2) for i in range(1,4)] [(x * 4 + i, y * 2 + 2) for i in range(1,4)]
# This has to doublecheck that the character exists, because if neighbour
# boxes are to be highlighed, it will try to overwrite already bold borders
for x, y in pointsToChange: for x, y in pointsToChange:
stringArray[y][x] = boxCharsToBoldBoxCharsMap[ symbolExists = stringArray[y][x] in boldBoxChars
stringArray[y] stringArray[y][x] = boldBoxChars[stringArray[y][x]] if symbolExists else stringArray[y][x]
[x]] if stringArray[y][x] in boxCharsToBoldBoxCharsMap else stringArray[y][x]
# Color white pieces # Color white pieces
for piece in self.getPositionsWhere(lambda piece: piece.color == 'white'): for piece in self.getPositionsWhere(lambda piece: piece.color == 'white'):
@ -91,6 +87,7 @@ class Board:
for box in config['highlightedBoxes']: for box in config['highlightedBoxes']:
highlightBox(*box) highlightBox(*box)
for piece in config['highlightedContent']: for piece in config['highlightedContent']:
highlightContent(*piece) highlightContent(*piece)
@ -121,7 +118,7 @@ class Board:
if move := determineMove(key, x, y, (0, 7)): if move := determineMove(key, x, y, (0, 7)):
x += move[0] x += move[0]
y += move[1] y += move[1]
elif key == 'e' and self.getPieceAt(x, y).color==player.color: elif key == 'e' and self.getPieceAt(x, y).color == player.color:
return (x, y) return (x, y)
except AttributeError: except AttributeError:
pass pass
@ -173,28 +170,33 @@ class Board:
try: try:
if condition(piece): if condition(piece):
result.append((x, y)) result.append((x, y))
except AttributeError: except AttributeError: # Position is None
pass pass
return result return result
def checkCheck(self, color) -> bool: def checkCheck(self, color, simulation=False) -> 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)[0] 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, simulation=simulation) 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)[0] 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:
if king not in Piece.possibleMoves(*piece, self): # Get all pieces that threaten the king
piecesToCheck.remove(piece) piecesToCheck = [piece for piece in piecesToCheck if king in Piece.possibleMoves(*piece, self)]
# Add only self if piece is pawn, knight or king
result = [] result = []
for piece in piecesToCheck: for piece in piecesToCheck:
result.append(piece) result.append([piece])
if self.getPieceAt(*piece).type not in ['p', 'n', 'k']: if self.getPieceAt(*piece).type not in ['p', 'n', 'k']:
# Get the direction as a tuple from the threatening piece to the king
def getDirection(fromPosition, toPosition) -> tuple: def getDirection(fromPosition, toPosition) -> tuple:
x = -1 if toPosition[0] > fromPosition[0] else \ x = -1 if toPosition[0] > fromPosition[0] else \
0 if toPosition[0] == fromPosition[0] else 1 0 if toPosition[0] == fromPosition[0] else 1
@ -204,22 +206,36 @@ class Board:
direction = getDirection(piece, king) direction = getDirection(piece, king)
# Return a list of every position until the king
def getPositionsUntilKing(x, y, direction) -> Iterable[tuple]: def getPositionsUntilKing(x, y, direction) -> Iterable[tuple]:
result = [] result = []
x += direction[0]
y += direction[1]
while self.getPieceAt(x, y) == None: while self.getPieceAt(x, y) == None:
result.append((x, y)) result.append((x, y))
x += direction[0] x += direction[0]
y += direction[1] y += direction[1]
return result return result
result += getPositionsUntilKing(*piece, direction) result[-1] += getPositionsUntilKing(*king, direction)
# Combine lists so that only tuples in all the lists of threatening pieces are valid
def getCommonValues(lst: Iterable[Iterable[tuple]]):
result = set(lst[0])
for sublst in lst[1:]:
result.intersection_update(sublst)
return result return result
return getCommonValues(result)
def playerHasLegalMoves(self, color) -> bool: def playerHasLegalMoves(self, color) -> bool:
enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color) enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color)
if self.checkCheck(color):
getLegalMoves = lambda piece: Piece.possibleMoves( getLegalMoves = lambda piece: Piece.possibleMoves(
*piece, self, legalMoves=self.getPositionsToProtectKing(color)) *piece, self, legalMoves=self.getPositionsToProtectKing(color))
else:
getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self)
return not any(getLegalMoves(piece) == None for piece in enemyPieces) return not any(getLegalMoves(piece) == None for piece in enemyPieces)
def checkStaleMate(self, color) -> bool: def checkStaleMate(self, color) -> bool:

64
Exercise 10/chess/chess.py Normal file → Executable file
View File

@ -1,58 +1,70 @@
#!/bin/python3
from os import system from os import system
from dataclasses import dataclass from dataclasses import dataclass
from board import Board from board import Board
from piece import Piece from piece import Piece
@dataclass @dataclass
class Player: class Player:
name: str name: str
color: str color: str
class Chess: class Chess:
def __init__(self, players): def __init__(self, players):
self.players = players self.players = players
self.board = Board() self.board = Board()
def win(self, player):
if player.color == 'white':
print('''
''')
else:
print('''
''')
exit(0)
def tie(self):
print('''
''')
exit(0)
def makeMove(self, player): def makeMove(self, player):
chosenTile = 0,0 chosenTile = 0, 0
while True: while True:
piece = self.board.selectPiece(player, *chosenTile) piece = self.board.selectPiece(player, *chosenTile)
chosenTile = piece chosenTile = piece
possibleMoves = Piece.possibleMoves(*piece, self.board) possibleMoves = Piece.possibleMoves(*piece, self.board)
if move := self.board.selectMove(player, *piece, possibleMoves): if move := self.board.selectMove(player, *piece, possibleMoves):
break break
print(move)
input()
self.board.movePiece(piece, move) self.board.movePiece(piece, move)
def win(self, player): def turn(self, playerNum):
print(player.name, 'won.')
exit(0)
def tie(self):
print('Stalemate')
exit(0)
def update(self):
system('clear') system('clear')
self.makeMove(players[0]) self.makeMove(players[playerNum])
# if self.board.checkCheckMate(players[1].color): # 1 - 1 = 0 and 1 - 0 = 1
# self.win(players[0]) if self.board.checkCheckMate(players[1 - playerNum].color):
# elif self.board.checkStaleMate(players[1].color): self.win(players[playerNum])
# self.tie() elif self.board.checkStaleMate(players[1 - playerNum].color):
self.tie()
system('clear')
self.makeMove(players[1])
# if self.board.checkCheckMate(players[0].color):
# self.win(players[1])
# elif self.board.checkStaleMate(players[0].color):
# self.tie()
def loop(self): def loop(self):
while True: while True:
self.update() self.turn(0)
self.turn(1)
if __name__ == "__main__": if __name__ == "__main__":
@ -64,5 +76,3 @@ if __name__ == "__main__":
game = Chess(('Spiller 1', 'Spiller 2')) game = Chess(('Spiller 1', 'Spiller 2'))
game.loop() game.loop()
# board = Board()
# print(board.selectPiece(players[0], centering=True))

View File

@ -1,8 +1,6 @@
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:
@ -34,12 +32,23 @@ class Piece:
return symbols[0 if self.color == 'white' else 1][self.type] 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,
simulation=False,
) -> Callable[[int, int], Iterable[tuple]]:
"""
Calculate all possible moves for a piece at (x, y) given a board in a certain state.
If there is restrictions for where the piece can go, the legal moves can be set to these.
If the function is part of a simulation, simulation needs to be set to True so that it doesn't keep on recursing simulation indefinetely.
"""
piece = board.getPieceAt(x, y) piece = board.getPieceAt(x, y)
moves = [] 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 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
@ -52,13 +61,14 @@ 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:
"""Simulate a move and return whether or not the move will result in check"""
testBoard = deepcopy(board) testBoard = deepcopy(board)
testBoard.movePiece((x, y), (newX, newY)) testBoard.movePiece((x, y), (newX, newY))
return not testBoard.checkCheck(piece.color) return not testBoard.checkCheck(piece.color, simulation=True)
def addWhileInsideBoard(direction: tuple): def addWhileInsideBoard(direction: tuple):
"""Adds moves in direction until it either hits a piece or the edge"""
localX, localY = x, y localX, localY = x, y
while positionInsideBounds(localX, localY): while positionInsideBounds(localX, localY):
localX += direction[0] localX += direction[0]
@ -121,6 +131,7 @@ class Piece:
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 # Remove moves that will put the king in check
# moves = [position for position in moves if assertNotCheck(*position)] if not simulation:
moves = [position for position in moves if assertNotCheck(*position)]
return moves return moves