Finish logic
This commit is contained in:
parent
23adf99347
commit
1bd7fa4e0e
|
@ -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)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
|
|
@ -1,20 +1,47 @@
|
||||||
|
#!/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:
|
||||||
|
@ -23,36 +50,21 @@ class Chess:
|
||||||
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))
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue