final changes

This commit is contained in:
Oystein Kristoffer Tveit 2020-11-13 18:48:48 +01:00
parent 1bd7fa4e0e
commit 744d91aa49
4 changed files with 106 additions and 69 deletions

View File

@ -1,8 +1,28 @@
from typing import Callable, Iterable, Union 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 as boldBoxChars, determineMove
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)
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
class Board: class Board:
@ -69,6 +89,20 @@ class Board:
def highlightBox(x, y): def highlightBox(x, y):
"""Make box around a position bold""" """Make box around a position bold"""
boldBoxChars = {
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
'': '',
}
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)] + \
@ -94,33 +128,46 @@ class Board:
return '\n'.join([''.join(line) for line in stringArray]) return '\n'.join([''.join(line) for line in stringArray])
def selectPiece(self, player, x=0, y=0, centering=True) -> tuple: def selectPiece(self, player, x=0, y=0, centering=True) -> tuple:
"""Lets the user select a piece from a graphic board""" """Lets the user select a piece"""
while True: while True:
system('clear') system('clear')
playerString = '\n' + player.name + '\n\n' playerString = '\n' + player.name + '\n'
menuString = self.draw({'highlightedBoxes': [(x, y)]}) + '\n' checkString = f"\033[41m{'CHECK' if self.checkCheck(player.color) else ''}\033[0m" + '\n'
hoveringPiece = self.getPieceAt(x, y)
pieceIsOwnColor = hoveringPiece != None and hoveringPiece.color == player.color
menuString = self.draw({
'highlightedBoxes': [(x, y)],
'highlightedContent': Piece.possibleMoves(x, y, self) if pieceIsOwnColor else []
}) + '\n'
inputString = f" W E\nA S D <- Enter : " inputString = f" W E\nA S D <- Enter : "
if centering: if centering:
playerString = centerText(playerString) playerString = centerText(playerString)
checkString = centerText(checkString)
menuString = centerBlockText(menuString) menuString = centerBlockText(menuString)
inputString = centerBlockText(inputString) inputString = centerBlockText(inputString)
print(playerString) print(playerString)
print(checkString)
print(menuString) print(menuString)
try: try:
key = input(inputString)[0] key = input(inputString)[0]
except IndexError: except IndexError: # Input was empty
key = '' key = ''
try: try:
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 hoveringPiece.color == player.color \
and Piece.possibleMoves(x, y, self) != []:
return (x, y) return (x, y)
except AttributeError: except AttributeError: # Chosen tile contains no piece
pass pass
def selectMove(self, player, x, y, legalMoves, centering=True) -> Union[tuple, bool]: def selectMove(self, player, x, y, legalMoves, centering=True) -> Union[tuple, bool]:
@ -128,7 +175,8 @@ class Board:
while True: while True:
system('clear') system('clear')
playerString = '\n' + player.name + '\n\n' playerString = '\n' + player.name + '\n'
checkString = f"\033[41m{'CHECK' if self.checkCheck(player.color) else ''}\033[0m" + '\n'
menuString = self.draw({ menuString = self.draw({
'highlightedBoxes': [(x, y)], 'highlightedBoxes': [(x, y)],
'highlightedContent': legalMoves 'highlightedContent': legalMoves
@ -137,16 +185,19 @@ class Board:
if centering: if centering:
playerString = centerText(playerString) playerString = centerText(playerString)
checkString = centerText(checkString) #TODO: Doesn't center because of escape chars
menuString = centerBlockText(menuString) menuString = centerBlockText(menuString)
inputString = centerBlockText(inputString) inputString = centerBlockText(inputString)
print(playerString) print(playerString)
print(checkString)
print(menuString) print(menuString)
try: try:
key = input(inputString)[0] key = input(inputString)[0]
except IndexError: except IndexError: # Input was empty
key = '' key = ''
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]
@ -155,14 +206,15 @@ class Board:
elif key == 'e' and (x, y) in legalMoves: elif key == 'e' and (x, y) in legalMoves:
return (x, y) return (x, y)
def getPieceAt(self, x, y) -> Piece: def getPieceAt(self, x, y) -> Union[Piece, None]:
"""Gets a piece at a certain position"""
try: try:
return self.boardArray[y][x] return self.boardArray[y][x]
except IndexError: except IndexError: # Outside board
return None 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 """
result = [] result = []
for y, row in enumerate(self.boardArray): for y, row in enumerate(self.boardArray):
@ -178,9 +230,9 @@ class Board:
"""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([ # Resend simulation status into possibleMoves in order to avoid indefinite recursion
king in Piece.possibleMoves(*piece, self, simulation=simulation) 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"""
@ -196,18 +248,16 @@ class Board:
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:
"""Get the direction as a tuple from the threatening piece to the king"""
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
y = -1 if toPosition[1] > fromPosition[1] else \ y = -1 if toPosition[1] > fromPosition[1] else \
0 if toPosition[1] == fromPosition[1] else 1 0 if toPosition[1] == fromPosition[1] else 1
return (x, y) return (x, y)
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]:
"""Return a list of every position until the king"""
result = [] result = []
x += direction[0] x += direction[0]
y += direction[1] y += direction[1]
@ -217,10 +267,11 @@ class Board:
y += direction[1] y += direction[1]
return result return result
direction = getDirection(piece, king)
result[-1] += getPositionsUntilKing(*king, 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]]): def getCommonValues(lst: Iterable[Iterable[tuple]]):
"""Combine lists so that only tuples in all the lists of threatening pieces are valid"""
result = set(lst[0]) result = set(lst[0])
for sublst in lst[1:]: for sublst in lst[1:]:
result.intersection_update(sublst) result.intersection_update(sublst)
@ -229,6 +280,7 @@ class Board:
return getCommonValues(result) return getCommonValues(result)
def playerHasLegalMoves(self, color) -> bool: def playerHasLegalMoves(self, color) -> bool:
""" returns whether or not a player has any legal moves left"""
enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color) enemyPieces = self.getPositionsWhere(lambda piece: piece.color == color)
if self.checkCheck(color): if self.checkCheck(color):
getLegalMoves = lambda piece: Piece.possibleMoves( getLegalMoves = lambda piece: Piece.possibleMoves(
@ -236,7 +288,7 @@ class Board:
else: else:
getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self) getLegalMoves = lambda piece: Piece.possibleMoves(*piece, self)
return not any(getLegalMoves(piece) == None for piece in enemyPieces) return any(getLegalMoves(piece) != [] 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"""
@ -246,12 +298,12 @@ class Board:
"""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"""
return self.checkCheck(color) and not self.playerHasLegalMoves(color) return self.checkCheck(color) and not self.playerHasLegalMoves(color)
def movePiece(self, position, toPosition, piecesToRemove=None): def movePiece(self, position, toPosition, piecesToRemove=[]):
""" Move a piece from position to toPosition. In case of extra pieces to be removes, add them to the list piecesToRemove"""
x, y = position x, y = position
toX, toY = toPosition toX, toY = toPosition
self.boardArray[toY][toX] = self.boardArray[y][x] self.boardArray[toY][toX] = self.boardArray[y][x]
self.boardArray[y][x] = None self.boardArray[y][x] = 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

@ -32,6 +32,7 @@ class Chess:
''') ''')
input('Press any button to exit...')
exit(0) exit(0)
def tie(self): def tie(self):
@ -40,10 +41,26 @@ class Chess:
''') ''')
input('Press any button to exit...')
exit(0) exit(0)
def promoteIfPossible(self, player, position):
promoteY = 0 if player.color == 'white' else 7
if (piece := self.board.getPieceAt(*position)).type == 'p' and position[1] == promoteY:
while True:
answer = input('What would you like your pawn to become? (q,b,r or n) ')
if answer in 'qbrn' and len(answer) == 1:
break
else:
print('\nCouldn\'t parse input. Try again')
piece.type = answer
def makeMove(self, player): def makeMove(self, player):
chosenTile = 0, 0 # Get the first piece belonging to the player
currentPlayersPiece = lambda piece: piece.color == player.color
chosenTile = self.board.getPositionsWhere(currentPlayersPiece)[0]
while True: while True:
piece = self.board.selectPiece(player, *chosenTile) piece = self.board.selectPiece(player, *chosenTile)
chosenTile = piece chosenTile = piece
@ -51,6 +68,7 @@ class Chess:
if move := self.board.selectMove(player, *piece, possibleMoves): if move := self.board.selectMove(player, *piece, possibleMoves):
break break
self.board.movePiece(piece, move) self.board.movePiece(piece, move)
self.promoteIfPossible(player, move)
def turn(self, playerNum): def turn(self, playerNum):
system('clear') system('clear')

View File

@ -12,6 +12,7 @@ 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
# Unused code. I'm missing the font for my terminal, but go ahead and use piece.symbol instead of str(symbol) in board.draw if you'd like
@property @property
def symbol(self): def symbol(self):
symbols = [{ symbols = [{
@ -54,8 +55,8 @@ class Piece:
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) 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]) -> 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 move if condition returns true. Returns condition result"""
if condition(board.getPieceAt(x + xOffset, y + yOffset)): if condition(board.getPieceAt(x + xOffset, y + yOffset)):
moves.append((x + xOffset, y + yOffset)) moves.append((x + xOffset, y + yOffset))
return True return True
@ -73,21 +74,23 @@ class Piece:
while positionInsideBounds(localX, localY): while positionInsideBounds(localX, localY):
localX += direction[0] localX += direction[0]
localY += direction[1] localY += direction[1]
if board.getPieceAt(localX, localY) == None: currentPiece = board.getPieceAt(localX, localY)
if pieceIsEmpty(currentPiece):
moves.append((localX, localY)) moves.append((localX, localY))
else: else:
if board.getPieceAt(localX, localY).color != piece.color: if pieceIsEnemyColor(currentPiece):
moves.append((localX, localY)) moves.append((localX, localY))
return return
if piece.type == 'p': if piece.type == 'p':
localY = 1 if piece.color == 'black' else -1 localY = 1 if piece.color == 'black' else -1
startPosition = 1 if piece.color == 'black' else 6 startPosition = 1 if piece.color == 'black' else 6
pieceAtStartPosition = lambda pieceToCheck: pieceToCheck == None and y == startPosition
addMoveIfTrue(1, localY, pieceIsEnemyColor) addMoveIfTrue(1, localY, pieceIsEnemyColor)
addMoveIfTrue(-1, localY, pieceIsEnemyColor) addMoveIfTrue(-1, localY, pieceIsEnemyColor)
if addMoveIfTrue(0, localY, pieceIsEmpty): if addMoveIfTrue(0, localY, pieceIsEmpty):
addMoveIfTrue(0, localY * 2, addMoveIfTrue(0, localY * 2, pieceAtStartPosition)
lambda pieceToCheck: pieceToCheck == None and y == startPosition)
elif piece.type == 'n': elif piece.type == 'n':
positions = [ positions = [
@ -126,7 +129,7 @@ class Piece:
# Remove moves that will lead the piece out of the board # Remove moves that will lead the piece out of the board
moves = [move for move in moves if positionInsideBounds(*move)] 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 # Remove moves that is not included in the legal moves (moves to block 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]

View File

@ -1,36 +0,0 @@
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