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 piece import Piece
from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap, determineMove
from util import centerText, centerBlockText, boxCharsToBoldBoxCharsMap as boldBoxChars, determineMove
class Board:
def __init__(self, boardState=None):
"""Create a standard board if nothing else is defined in boardState"""
self.boardArray = [
[Piece(type, 'black') for type in ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r']],
[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']],
] if boardState == None else boardState
def draw(
self,
config={
'highlightedContent': [],
'highlightEscapeCodes': ('\033[32;5;7m', '\033[0m'),
'highlightedBoxes': [],
}
) -> str:
def draw(self, config={}) -> str:
"""Returns a string representing the board
config options:
@ -32,6 +26,7 @@ class Board:
highlightedBoxes: [(x,y)] - Boxes to make bold
"""
# Fill default values in config dict
def fillConfigDefaultValue(key, defaultValue):
if key not in config:
config[key] = defaultValue
@ -40,15 +35,15 @@ class Board:
fillConfigDefaultValue('highlightedBoxes', [])
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)]
for y, row in enumerate(self.boardArray):
for x, _ in enumerate(row):
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 ' '))
symbol = str(self.boardArray[y][x]) if self.boardArray[y][x] != None else ' '
stringArray[2 * y + 1] += list(' {}'.format(symbol))
stringArray[2 * y + 2] += list('───┼')
# Overwrite corners
@ -58,21 +53,21 @@ class Board:
stringArray[-1][-1] = ''
# 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[-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][-1] = ''
def highlightContent(x, y, escapeCodes=config['highlightEscapeCodes']):
"""highlight contents of a piece with xterm-256colors modifiers"""
def highlightContent(x, y, modifiers=config['highlightEscapeCodes']):
"""highlight inner part of a box with xterm-256colors modifiers"""
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]
modifiers[0] + stringArray[y * 2 + 1][x * 4 + 1]
stringArray[y * 2 + 1][x * 4 + 3] += modifiers[1]
def highlightBox(x, y):
"""Make box around a piece bold"""
"""Make box around a position bold"""
pointsToChange = \
[(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 + 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:
stringArray[y][x] = boxCharsToBoldBoxCharsMap[
stringArray[y]
[x]] if stringArray[y][x] in boxCharsToBoldBoxCharsMap else stringArray[y][x]
symbolExists = stringArray[y][x] in boldBoxChars
stringArray[y][x] = boldBoxChars[stringArray[y][x]] if symbolExists else stringArray[y][x]
# Color white pieces
for piece in self.getPositionsWhere(lambda piece: piece.color == 'white'):
@ -91,6 +87,7 @@ class Board:
for box in config['highlightedBoxes']:
highlightBox(*box)
for piece in config['highlightedContent']:
highlightContent(*piece)
@ -121,7 +118,7 @@ class Board:
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:
elif key == 'e' and self.getPieceAt(x, y).color == player.color:
return (x, y)
except AttributeError:
pass
@ -173,28 +170,33 @@ class Board:
try:
if condition(piece):
result.append((x, y))
except AttributeError:
except AttributeError: # Position is None
pass
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"""
king = self.getPositionsWhere(lambda piece: piece.type == 'k' and piece.color == color)[0]
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]:
"""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]
piecesToCheck = self.getPositionsWhere(lambda piece: piece.color != color)
for piece in piecesToCheck:
if king not in Piece.possibleMoves(*piece, self):
piecesToCheck.remove(piece)
# Get all pieces that threaten the king
piecesToCheck = [piece for piece in piecesToCheck if king in Piece.possibleMoves(*piece, self)]
# Add only self if piece is pawn, knight or king
result = []
for piece in piecesToCheck:
result.append(piece)
result.append([piece])
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:
x = -1 if toPosition[0] > fromPosition[0] else \
0 if toPosition[0] == fromPosition[0] else 1
@ -204,22 +206,36 @@ class Board:
direction = getDirection(piece, king)
# Return a list of every position until the king
def getPositionsUntilKing(x, y, direction) -> Iterable[tuple]:
result = []
x += direction[0]
y += direction[1]
while self.getPieceAt(x, y) == None:
result.append((x, y))
x += direction[0]
y += direction[1]
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 getCommonValues(result)
def playerHasLegalMoves(self, color) -> bool:
enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color)
if self.checkCheck(color):
getLegalMoves = lambda piece: Piece.possibleMoves(
*piece, self, legalMoves=self.getPositionsToProtectKing(color))
else:
getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self)
return not any(getLegalMoves(piece) == None for piece in enemyPieces)
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 dataclasses import dataclass
from board import Board
from piece import Piece
@dataclass
class Player:
name: str
color: str
class Chess:
def __init__(self, players):
self.players = players
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):
chosenTile = 0,0
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)
def update(self):
def turn(self, playerNum):
system('clear')
self.makeMove(players[0])
# if self.board.checkCheckMate(players[1].color):
# self.win(players[0])
# elif self.board.checkStaleMate(players[1].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()
self.makeMove(players[playerNum])
# 1 - 1 = 0 and 1 - 0 = 1
if self.board.checkCheckMate(players[1 - playerNum].color):
self.win(players[playerNum])
elif self.board.checkStaleMate(players[1 - playerNum].color):
self.tie()
def loop(self):
while True:
self.update()
self.turn(0)
self.turn(1)
if __name__ == "__main__":
@ -64,5 +76,3 @@ if __name__ == "__main__":
game = Chess(('Spiller 1', 'Spiller 2'))
game.loop()
# board = Board()
# print(board.selectPiece(players[0], centering=True))

View File

@ -1,8 +1,6 @@
from typing import Iterable, Callable
from itertools import product
from copy import deepcopy
from sys import setrecursionlimit as setRecursionLimit
setRecursionLimit(100000)
class Piece:
@ -34,12 +32,23 @@ class Piece:
return symbols[0 if self.color == 'white' else 1][self.type]
@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)
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
@ -52,13 +61,14 @@ class Piece:
return True
return False
# TODO: fix deepcopy segfault and recursion error
def assertNotCheck(newX, newY) -> bool:
"""Simulate a move and return whether or not the move will result in check"""
testBoard = deepcopy(board)
testBoard.movePiece((x, y), (newX, newY))
return not testBoard.checkCheck(piece.color)
return not testBoard.checkCheck(piece.color, simulation=True)
def addWhileInsideBoard(direction: tuple):
"""Adds moves in direction until it either hits a piece or the edge"""
localX, localY = x, y
while positionInsideBounds(localX, localY):
localX += direction[0]
@ -121,6 +131,7 @@ class Piece:
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)]
if not simulation:
moves = [position for position in moves if assertNotCheck(*position)]
return moves