diff options
Diffstat (limited to 'chess.py')
| -rw-r--r-- | chess.py | 1033 |
1 files changed, 1033 insertions, 0 deletions
diff --git a/chess.py b/chess.py new file mode 100644 index 0000000..64ba1af --- /dev/null +++ b/chess.py @@ -0,0 +1,1033 @@ +import chessconf +import random +import copy +import subprocess +import typing +from collections.abc import Generator + +mv_struct = tuple[tuple[int, int, str], tuple[int, int, str]] | tuple[tuple[int, int, str], tuple[int, int, str], tuple[int, int, str]] | tuple[tuple[int, int, str], tuple[int, int, str], tuple[int, int, str], tuple[int, int, str]] +board_struct = tuple[list[str], list[str], list[str], list[str], list[str], list[str], list[str], list[str]] + +ICON: typing.Final[dict[str, str]] = { + 'K': chessconf.WHITECOLOR + chessconf.WHITE_KING_ICON + '\x1b[0m', + 'Q': chessconf.WHITECOLOR + chessconf.WHITE_QUEEN_ICON + '\x1b[0m', + 'R': chessconf.WHITECOLOR + chessconf.WHITE_ROOK_ICON + '\x1b[0m', + 'B': chessconf.WHITECOLOR + chessconf.WHITE_BISHOP_ICON + '\x1b[0m', + 'N': chessconf.WHITECOLOR + chessconf.WHITE_KNIGHT_ICON + '\x1b[0m', + 'P': chessconf.WHITECOLOR + chessconf.WHITE_PAWN_ICON + '\x1b[0m', + '.': chessconf.SPACECOLOR + chessconf.SPACE_ICON + '\x1b[0m', + 'k': chessconf.BLACKCOLOR + chessconf.BLACK_KING_ICON + '\x1b[0m', + 'q': chessconf.BLACKCOLOR + chessconf.BLACK_QUEEN_ICON + '\x1b[0m', + 'r': chessconf.BLACKCOLOR + chessconf.BLACK_ROOK_ICON + '\x1b[0m', + 'b': chessconf.BLACKCOLOR + chessconf.BLACK_BISHOP_ICON + '\x1b[0m', + 'n': chessconf.BLACKCOLOR + chessconf.BLACK_KNIGHT_ICON + '\x1b[0m', + 'p': chessconf.BLACKCOLOR + chessconf.BLACK_PAWN_ICON + '\x1b[0m'} + +INT_TO_ALPH: typing.Final[dict[int, str]] = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h'} +ALPH_TO_INT: typing.Final[dict[str, int]] = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5, 'g': 6, 'h': 7} +INT_TO_NUM: typing.Final[dict[int, str]] = {0: '8', 1: '7', 2: '6', 3: '5', 4: '4', 5: '3', 6: '2', 7: '1'} +NUM_TO_INT: typing.Final[dict[str, int]] = {'8': 0, '7': 1, '6': 2, '5': 3, '4': 4, '3': 5, '2': 6, '1': 7} + +COLOR_WHITE: typing.Final[dict[str, str]] = {'R': 'R', 'N': 'N', 'B': 'B', 'K': 'K', 'Q': 'Q', 'P': 'P', 'r': 'R', 'n': 'N', 'b': 'B', 'k': 'K', 'q': 'Q', 'p': 'P'} +COLOR_BLACK: typing.Final[dict[str, str]] = {'R': 'r', 'N': 'n', 'B': 'b', 'K': 'k', 'Q': 'q', 'P': 'p', 'r': 'r', 'n': 'n', 'b': 'b', 'k': 'k', 'q': 'q', 'p': 'p'} + +WHITEPIECES: typing.Final[set[str]] = {'R', 'N', 'B', 'Q', 'K', 'P'} +BLACKPIECES: typing.Final[set[str]] = {'r', 'n', 'b', 'q', 'k', 'p'} + +class Board(): + def __init__(self, white: bool = True) -> None: + self.pos: board_struct = ( + ['r', 'n', 'b', 'q', 'k', 'b', 'n', 'r'], + ['p', 'p', 'p', 'p', 'p', 'p', 'p', 'p'], + ['.', '.', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', '.', '.'], + ['P', 'P', 'P', 'P', 'P', 'P', 'P', 'P'], + ['R', 'N', 'B', 'Q', 'K', 'B', 'N', 'R']) + + self.white: bool = white + self.bot: bool | None = None + + self.end_game: bool = False + self.last_move_capture: bool = False + + self.log: list = [] + self.pos_log: list = [copy.deepcopy(self.pos)] + + self.half_mv_ctr: int = 0 + self.fifty_mv_ctr: int = 0 + + self.ep_sqr: tuple | None = None + + self.board_turned: bool = False + self.asked_draw: bool = False + + self.w_short_castling: bool = True + self.w_long_castling: bool = True + self.b_short_castling: bool = True + self.b_long_castling: bool = True + + def render(self, white: bool) -> None: + if (white and not self.board_turned) or (not white and self.board_turned): + if self.board_turned: + print('\nBoard turned! Use /turnboard to turn it back.') + print(f''' + ┌───┬───┬───┬───┬───┬───┬───┬───┐ +8 │ {' │ '.join((ICON[self.pos[0][0]], ICON[self.pos[0][1]], ICON[self.pos[0][2]], ICON[self.pos[0][3]], ICON[self.pos[0][4]], ICON[self.pos[0][5]], ICON[self.pos[0][6]], ICON[self.pos[0][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +7 │ {' │ '.join((ICON[self.pos[1][0]], ICON[self.pos[1][1]], ICON[self.pos[1][2]], ICON[self.pos[1][3]], ICON[self.pos[1][4]], ICON[self.pos[1][5]], ICON[self.pos[1][6]], ICON[self.pos[1][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +6 │ {' │ '.join((ICON[self.pos[2][0]], ICON[self.pos[2][1]], ICON[self.pos[2][2]], ICON[self.pos[2][3]], ICON[self.pos[2][4]], ICON[self.pos[2][5]], ICON[self.pos[2][6]], ICON[self.pos[2][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +5 │ {' │ '.join((ICON[self.pos[3][0]], ICON[self.pos[3][1]], ICON[self.pos[3][2]], ICON[self.pos[3][3]], ICON[self.pos[3][4]], ICON[self.pos[3][5]], ICON[self.pos[3][6]], ICON[self.pos[3][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +4 │ {' │ '.join((ICON[self.pos[4][0]], ICON[self.pos[4][1]], ICON[self.pos[4][2]], ICON[self.pos[4][3]], ICON[self.pos[4][4]], ICON[self.pos[4][5]], ICON[self.pos[4][6]], ICON[self.pos[4][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +3 │ {' │ '.join((ICON[self.pos[5][0]], ICON[self.pos[5][1]], ICON[self.pos[5][2]], ICON[self.pos[5][3]], ICON[self.pos[5][4]], ICON[self.pos[5][5]], ICON[self.pos[5][6]], ICON[self.pos[5][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +2 │ {' │ '.join((ICON[self.pos[6][0]], ICON[self.pos[6][1]], ICON[self.pos[6][2]], ICON[self.pos[6][3]], ICON[self.pos[6][4]], ICON[self.pos[6][5]], ICON[self.pos[6][6]], ICON[self.pos[6][7]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +1 │ {' │ '.join((ICON[self.pos[7][0]], ICON[self.pos[7][1]], ICON[self.pos[7][2]], ICON[self.pos[7][3]], ICON[self.pos[7][4]], ICON[self.pos[7][5]], ICON[self.pos[7][6]], ICON[self.pos[7][7]]))} │ + └───┴───┴───┴───┴───┴───┴───┴───┘ + a b c d e f g h''') + else: + if self.board_turned: + print('\nBoard turned! Use /turnboard to turn it back.') + print(f''' + ┌───┬───┬───┬───┬───┬───┬───┬───┐ +1 │ {' │ '.join((ICON[self.pos[7][7]], ICON[self.pos[7][6]], ICON[self.pos[7][5]], ICON[self.pos[7][4]], ICON[self.pos[7][3]], ICON[self.pos[7][2]], ICON[self.pos[7][1]], ICON[self.pos[7][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +2 │ {' │ '.join((ICON[self.pos[6][7]], ICON[self.pos[6][6]], ICON[self.pos[6][5]], ICON[self.pos[6][4]], ICON[self.pos[6][3]], ICON[self.pos[6][2]], ICON[self.pos[6][1]], ICON[self.pos[6][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +3 │ {' │ '.join((ICON[self.pos[5][7]], ICON[self.pos[5][6]], ICON[self.pos[5][5]], ICON[self.pos[5][4]], ICON[self.pos[5][3]], ICON[self.pos[5][2]], ICON[self.pos[5][1]], ICON[self.pos[5][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +4 │ {' │ '.join((ICON[self.pos[4][7]], ICON[self.pos[4][6]], ICON[self.pos[4][5]], ICON[self.pos[4][4]], ICON[self.pos[4][3]], ICON[self.pos[4][2]], ICON[self.pos[4][1]], ICON[self.pos[4][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +5 │ {' │ '.join((ICON[self.pos[3][7]], ICON[self.pos[3][6]], ICON[self.pos[3][5]], ICON[self.pos[3][4]], ICON[self.pos[3][3]], ICON[self.pos[3][2]], ICON[self.pos[3][1]], ICON[self.pos[3][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +6 │ {' │ '.join((ICON[self.pos[2][7]], ICON[self.pos[2][6]], ICON[self.pos[2][5]], ICON[self.pos[2][4]], ICON[self.pos[2][3]], ICON[self.pos[2][2]], ICON[self.pos[2][1]], ICON[self.pos[2][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +7 │ {' │ '.join((ICON[self.pos[1][7]], ICON[self.pos[1][6]], ICON[self.pos[1][5]], ICON[self.pos[1][4]], ICON[self.pos[1][3]], ICON[self.pos[1][2]], ICON[self.pos[1][1]], ICON[self.pos[1][0]]))} │ + ├───┼───┼───┼───┼───┼───┼───┼───┤ +8 │ {' │ '.join((ICON[self.pos[0][7]], ICON[self.pos[0][6]], ICON[self.pos[0][5]], ICON[self.pos[0][4]], ICON[self.pos[0][3]], ICON[self.pos[0][2]], ICON[self.pos[0][1]], ICON[self.pos[0][0]]))} │ + └───┴───┴───┴───┴───┴───┴───┴───┘ + h g f e d c b a''') + + def _rook_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + enemypieces = BLACKPIECES if white else WHITEPIECES + rook = 'R' if white else 'r' + + directions = ((1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newy in range(8) and newx in range(8): + if self.pos[newy][newx] == '.': + yield ((y, x, '.'), (newy, newx, rook)) + elif self.pos[newy][newx] in enemypieces: + yield ((y, x, '.'), (newy, newx, rook)) + break + else: + break + newy, newx = newy + dy, newx + dx + + def _knight_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + ownpieces, knight = (WHITEPIECES, 'N') if white else (BLACKPIECES, 'n') + + directions = ((1, 2), (1, -2), (2, 1), (2, -1), (-1, 2), (-1, -2), (-2, 1), (-2, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8) and self.pos[newy][newx] not in ownpieces: + yield ((y, x, '.'), (newy, newx, knight)) + + def _bishop_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + enemypieces, bishop = (BLACKPIECES, 'B') if white else (WHITEPIECES, 'b') + + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + piece = self.pos[newy][newx] + if piece == '.': + yield ((y, x, '.'), (newy, newx, bishop)) + elif piece in enemypieces: + yield ((y, x, '.'), (newy, newx, bishop)) + break + else: + break + newy, newx = newy + dy, newx + dx + + def _queen_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + enemypieces, queen = (BLACKPIECES, 'Q') if white else (WHITEPIECES, 'q') + + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + piece = self.pos[newy][newx] + if piece == '.': + yield ((y, x, '.'), (newy, newx, queen)) + elif piece in enemypieces: + yield ((y, x, '.'), (newy, newx, queen)) + break + else: + break + newy, newx = newy + dy, newx + dx + + def _king_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + ownpieces, king = (WHITEPIECES, 'K') if white else (BLACKPIECES, 'k') + + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8) and self.pos[newy][newx] not in ownpieces: + yield ((y, x, '.'), (newy, newx, king)) + + def _castling_mvs(self, white: bool) -> Generator[mv_struct]: + short_castling, long_castling, color_piece, castling_row = (self.w_short_castling, self.w_long_castling, COLOR_WHITE, 7) if white else (self.b_short_castling, self.b_long_castling, COLOR_BLACK, 0) + + if short_castling and self.pos[castling_row][5] == '.' and self.pos[castling_row][6] == '.': + yield ((castling_row, 4, '.'), (castling_row, 5, color_piece['R']), (castling_row, 6, color_piece['K']), (castling_row, 7, '.')) + if long_castling and self.pos[castling_row][1] == '.' and self.pos[castling_row][2] == '.' and self.pos[castling_row][3] == '.': + yield ((castling_row, 0, '.'), (castling_row, 2, color_piece['K']), (castling_row, 3, color_piece['R']), (castling_row, 4, '.')) + + def _pawn_mvs(self, y: int, x: int, white: bool) -> Generator[mv_struct]: + + startingrow = 6 if white else 1 + forwardstep = -1 if white else 1 + promotionrow = 1 if white else 6 + + color_piece = COLOR_WHITE if white else COLOR_BLACK + enemypieces = BLACKPIECES if white else WHITEPIECES + + newy = y + forwardstep + + if newy in range(8) and self.pos[newy][x] == '.': + if y == promotionrow: + for promotion in {color_piece['Q'], color_piece['R'], color_piece['N'], color_piece['B']}: + yield ((y, x, '.'), (newy, x, promotion)) + else: + yield ((y, x, '.'), (newy, x, color_piece['P'])) + if y == startingrow and self.pos[newy + forwardstep][x] == '.': + yield ((y, x, '.'), (newy + forwardstep, x, color_piece['P'])) + for dx in (1, -1): + newx = x + dx + if newy in range(8) and newx in range(8) and self.pos[newy][newx] in enemypieces: + if y == promotionrow: + for i in (color_piece['Q'], color_piece['R'], color_piece['N'], color_piece['B']): + yield ((y, x, '.'), (newy, newx, i)) + else: # taking enemy piece + yield ((y, x, '.'), (newy, newx, color_piece['P'])) + if [newy, newx] == self.ep_sqr: # en passant + yield ((y, x, '.'), (newy, newx, color_piece['P']), (y, newx, '.')) + + def all_mvs(self, white: bool) -> Generator[mv_struct]: + color_piece = COLOR_WHITE if white else COLOR_BLACK + mv_generators = {color_piece['P']: self._pawn_mvs, color_piece['R']: self._rook_mvs, color_piece['N']: self._knight_mvs, color_piece['B']: self._bishop_mvs, color_piece['Q']: self._queen_mvs, color_piece['K']: self._king_mvs} + for y, row in enumerate(self.pos): + for x, piece in enumerate(row): + if generator := mv_generators.get(piece): + yield from generator(y, x, white) + yield from self._castling_mvs(white) + + def reach(self, white: bool) -> tuple: + reach = (([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], [])) + color_piece, forwardstep, startingrow, enemypieces = (COLOR_WHITE, -1, 6, BLACKPIECES) if white else (COLOR_BLACK, 1, 1, WHITEPIECES) + short_castling, long_castling, castling_row = (self.w_short_castling, self.w_long_castling, 7) if white else (self.b_short_castling, self.b_long_castling, 0) + for y, row in enumerate(self.pos): + for x, piece in enumerate(row): + + if piece == color_piece['R']: + directions = ((1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newy in range(8) and newx in range(8): + if self.pos[newy][newx] != '.': + reach[newy][newx].append((y, x, piece)) + break + reach[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['N']: + directions = ((1, 2), (1, -2), (2, 1), (2, -1), (-1, 2), (-1, -2), (-2, 1), (-2, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8): + reach[newy][newx].append((y, x, piece)) + + elif piece == color_piece['B']: + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + if self.pos[newy][newx] != '.': + reach[newy][newx].append((y, x, piece)) + break + reach[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['Q']: + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + if self.pos[newy][newx] != '.': + reach[newy][newx].append((y, x, piece)) + break + reach[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['K']: + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8): + reach[newy][newx].append((y, x, piece)) + + if short_castling and self.pos[castling_row][5] == '.' and self.pos[castling_row][6] == '.': + reach[castling_row][6].append((castling_row, 4, piece)) + if long_castling and self.pos[castling_row][2] == '.' and self.pos[castling_row][3] == '.': + reach[castling_row][2].append((castling_row, 4, piece)) + + elif piece == color_piece['P']: + if self.pos[y + forwardstep][x] == '.': + reach[y + forwardstep][x].append((y, x, piece)) + if y == startingrow and self.pos[y + (forwardstep * 2)][x] == '.': + reach[y + (forwardstep * 2)][x].append((y, x, piece)) + for dx in (1, -1): + newx = x + dx + if y + forwardstep in range(8) and newx in range(8) and (self.pos[y + forwardstep][newx] in enemypieces or self.ep_sqr == (y + forwardstep, newx)): + reach[y + forwardstep][newx].append((y, x, piece)) + return reach + + def control(self, white: bool) -> tuple: + control = (([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], []), ([], [], [], [], [], [], [], [])) + color_piece, colorenemypiece, forwardstep = (COLOR_WHITE, COLOR_BLACK, -1) if white else (COLOR_BLACK, COLOR_WHITE, 1) + for y, row in enumerate(self.pos): + for x, piece in enumerate(row): + + if piece == color_piece['R']: + directions = ((1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newy in range(8) and newx in range(8): + if self.pos[newy][newx] not in {'.', color_piece['R'], color_piece['Q']}: + control[newy][newx].append((y, x, piece)) + break + control[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['N']: + directions = ((1, 2), (1, -2), (2, 1), (2, -1), (-1, 2), (-1, -2), (-2, 1), (-2, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8): + control[newy][newx].append((y, x, piece)) + + elif piece == color_piece['B']: + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + if self.pos[newy][newx] not in {'.', color_piece['Q']}: + control[newy][newx].append((y, x, piece)) + if self.pos[newy][newx] == color_piece['P'] and dy == forwardstep: + control[newy][newx].append((y, x, piece)) + break + control[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['Q']: + directions = ((1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + if self.pos[newy][newx] not in {'.', color_piece['R'], color_piece['Q']}: + control[newy][newx].append((y, x, piece)) + break + control[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + while newx in range(8) and newy in range(8): + if self.pos[newy][newx] not in {'.', color_piece['B'], color_piece['Q']}: + control[newy][newx].append((y, x, piece)) + if self.pos[newy][newx] == color_piece['P'] and dy == forwardstep: + control[newy + dy][newx + dx].append((y, x, piece)) + break + control[newy][newx].append((y, x, piece)) + newy, newx = newy + dy, newx + dx + + elif piece == color_piece['K']: + directions = ((1, 1), (1, -1), (-1, 1), (-1, -1), (1, 0), (0, 1), (-1, 0), (0, -1)) + for dy, dx in directions: + newy, newx = y + dy, x + dx + if newy in range(8) and newx in range(8): + control[newy][newx].append((y, x, piece)) + + elif piece == color_piece['P']: + for dx in (1, -1): + newx = x + dx + if y + forwardstep in range(8) and newx in range(8): + control[y + forwardstep][newx].append((y, x, piece)) + if self.ep_sqr == (y + forwardstep, newx) and self.pos[y][newx] == colorenemypiece['P']: + control[y][newx].append((y, x, piece)) + return control + + def legal_mvs(self, white: bool) -> Generator[mv_struct]: + enemycontrol = self.control(not white) + color_piece = COLOR_WHITE if white else COLOR_BLACK + short_castling, long_castling = ((self.w_short_castling, self.w_long_castling) if white else (self.b_short_castling, self.b_long_castling)) + + for mv in self.all_mvs(white): + if mv == ((7, 4, '.'), (7, 5, color_piece['R']), (7, 6, color_piece['K']), (7, 7, '.')) and (enemycontrol[7][5] or enemycontrol[7][4] or not short_castling): + continue + elif mv == ((7, 0, '.'), (7, 2, color_piece['K']), (7, 3, color_piece['R']), (7, 4, '.')) and (enemycontrol[7][3] or enemycontrol[7][4] or not long_castling): + continue + self.do(mv) + if any(self.pos[single_mv[0]][single_mv[1]] == color_piece['K'] for enemy_mv in self.all_mvs(not white) for single_mv in enemy_mv): + self.undo() + continue + self.undo() + yield mv + + def _fifty_mv_rule(self) -> None: + if self.fifty_mv_ctr == 100: + userinput = input('\n50 moves have been played without capture or pawnpush. Do you want to force a draw? [Y/n] > ') + if userinput in {'', 'yes', 'y', 'Y'}: + self.render(self.white if self.bot is not None else not self.bot) + print('\nDraw due to fifty move rule!\n') + raise SystemExit + + def _mv_rep(self) -> None: + if self.pos_log.count(self.pos_log[-1]) == 3: + self.render(self.white if self.bot is not None else not self.bot) + print('\nDRAW DUE TO MOVE REPITITION!\n') + raise SystemExit + + def check_pos(self) -> None: + for white in {True, False}: + if not [*self.legal_mvs(not white)]: + self.render(self.white if self.bot is not None else not self.bot) + if self._is_checkmate(white): + print('\nWHITE WON BY CHECKMATE!\n') if white else print('\nBLACK WON BY CHECKMATE!\n') + raise SystemExit + print('\nSTALEMATE!\n') + raise SystemExit + self._fifty_mv_rule(); self._mv_rep() + + def do(self, mv: mv_struct) -> None: + save = [] + for single_mv in mv: + save.append((self.pos[single_mv[0]][single_mv[1]], single_mv[0], single_mv[1], single_mv[2])) + self.pos[single_mv[0]][single_mv[1]] = single_mv[2] + self.log.append(tuple(save)) + + def undo(self) -> None: + lastmv = self.log.pop() + for single_mv in lastmv: + self.pos[single_mv[1]][single_mv[2]] = single_mv[0] + + def make_mv(self, mv: mv_struct, white: bool) -> None: + self.asked_draw, self.ep_sqr = False, None + color_piece, forwardstep = (COLOR_WHITE, -1) if white else (COLOR_BLACK, 1) + self.last_move_capture = self._is_capture(mv) + + if mv[1][2] == color_piece['P'] and mv[1][0] == mv[0][0] + (2 * forwardstep): # EP square + self.ep_sqr = (mv[0][0] + forwardstep, mv[0][1]) + + save = [] + pawn_mv = False + + for single_mv in mv: + save.append((self.pos[single_mv[0]][single_mv[1]], single_mv[0], single_mv[1], single_mv[2])) + if single_mv[2] == color_piece['P']: # checks if pawnmove to reset 50-mv-rule + pawn_mv = True + + match single_mv[:2]: + case (7, 0): + self.w_long_castling = False + case (7, 4): + self.w_short_castling, self.w_long_castling = False, False + case (7, 7): + self.w_short_castling = False + case (0, 0): + self.b_long_castling = False + case (0, 4): + self.b_short_castling, self.b_long_castling = False, False + case (0, 7): + self.b_short_castling = False + + self.pos[single_mv[0]][single_mv[1]] = single_mv[2] + + self.fifty_mv_ctr = 0 if pawn_mv or self.last_move_capture else self.fifty_mv_ctr + 1 + self.half_mv_ctr += 1 + self.log.append(tuple(save)) + self.pos_log.append(copy.deepcopy(self.pos)) + self.end_game = self.is_end_game() + self.check_pos() + + def _is_check(self, white: bool, mv: mv_struct | None = None) -> bool: + colorenemypiece = COLOR_BLACK if white else COLOR_WHITE + if not mv: + if any(self.pos[single_mv[0]][single_mv[1]] == colorenemypiece['K'] for mv in self.all_mvs(white) for single_mv in mv): + return True + return False + self.do(mv) + if any(self.pos[single_mv[0]][single_mv[1]] == colorenemypiece['K'] for mv in self.all_mvs(white) for single_mv in mv): + self.undo() + return True + self.undo() + return False + + def _is_checkmate(self, white: bool, mv: mv_struct | None = None) -> bool: + colorenemypiece = COLOR_BLACK if white else COLOR_WHITE + if not mv: + if not [*self.legal_mvs(not white)]: + if not any(self.pos[single_mv[0]][single_mv[1]] == colorenemypiece['K'] for mv in self.all_mvs(white) for single_mv in mv): + return False + return True + self.do(mv) + if not [*self.legal_mvs(not white)]: + if not any(self.pos[single_mv[0]][single_mv[1]] == colorenemypiece['K'] for mv in self.all_mvs(white) for single_mv in mv): + self.undo() + return False + self.undo() + return True + + def _is_capture(self, mv: mv_struct) -> bool: + capture = 0 + for single_mv in mv: + capture += (1 if single_mv[2] == '.' else capture -1) + if not capture: + return True + return False + + def set_fen(self, fen_str: str) -> bool | None: + try: + board_str, color_str, castling_str, ep_str, fifty_mv_str, full_mv_str = fen_str.strip().split() + except ValueError: + return False + + tmp_pos = [] + board_lst = board_str.split('/') + if len(board_lst) != 8: + return False + for row_str in board_lst: + tmp_row = [] + for char in row_str: + if char == '.' or char in WHITEPIECES | BLACKPIECES: + tmp_row.append(char) + elif char in {'1', '2', '3', '4', '5', '6', '7', '8'}: + tmp_row.extend(['.'] * int(char)) + else: + return False + tmp_pos.append(tmp_row) + if len(tmp_pos) != 8: + return False + self.pos = tuple(tmp_pos) + + self.white = color_str == 'w' + if not self.white and color_str != 'b': + return False + + if castling_str != '-' and set(castling_str) <= {'K', 'Q', 'k', 'q'} or sorted(castling_str) != list(castling_str): + return False + self.w_short_castling = 'K' in castling_str + self.w_long_castling = 'Q' in castling_str + self.b_short_castling = 'k' in castling_str + self.b_long_castling = 'q' in castling_str + + if ep_str == '-': + self.ep_sqr = None + elif len(ep_str) == 2 and ep_str[0] in ALPH_TO_INT and ep_str[1] in NUM_TO_INT and NUM_TO_INT[ep_str[1]] in {2, 5}: + self.ep_sqr = (NUM_TO_INT[ep_str[1]], ALPH_TO_INT[ep_str[0]]) + else: + return False + + if fifty_mv_str.isdigit(): + self.fifty_mv_ctr = int(fifty_mv_str) + + if full_mv_str.isdigit(): + self.half_mv_ctr = int(full_mv_str) * 2 + (1 if color_str == 'b' else 0) + + self.check_pos(); return True + + def get_fen(self, white: bool) -> str: + + row_strs = [] + for row in self.pos: + row_str = '' + space_ctr = 0 + for piece in row: + if piece == '.': + space_ctr += 1 + elif piece in WHITEPIECES or piece in BLACKPIECES: + if space_ctr: + row_str += str(space_ctr) + row_str += piece + space_ctr = 0 + if space_ctr: + row_str += str(space_ctr) + row_strs.append(row_str) + board_str = '/'.join(row_strs) + + color_str = 'w' if white else 'b' + + castling_str = ('K' if self.w_short_castling else '') + ('Q' if self.w_long_castling else '') + ('k' if self.b_short_castling else '') + ('q' if self.b_long_castling else '') + castling_str = '-' if not castling_str else castling_str + + ep_str = '-' if self.ep_sqr == None else (INT_TO_ALPH[self.ep_sqr[1]] + INT_TO_NUM[self.ep_sqr[0]]) + + fifty_mv_str = str(self.fifty_mv_ctr) + + full_mv_str = str(self.half_mv_ctr // 2) + + fen_str = ' '.join([board_str, color_str, castling_str, ep_str, fifty_mv_str, full_mv_str]) + return fen_str + + def _game_controls(self, userinput: tuple, white: bool = None) -> bool | None: + if userinput in {'/m', '/menu', '?', 'h', 'help', '/h', '/help', '-h', '-help', '--help'}: + print((''' +/m, /menu > gamecontrols, opens this menu +/q, /quit > quit game +/s, /save > save game to FEN +/t, /turnboard > turn board position +/p, /printboard > reprint board +/d, /draw > offer draw +/r, /resign > resign game +/f, /forcedraw > force draw if 50 move rule applies +/i, /import > import game from FEN + +Read the manual for more information''')) + return True + + elif userinput in {'/q', '/quit'}: + print(f'\nFEN-notation > {self.get_fen(white)}\n') + raise SystemExit + + elif userinput in {'/s', '/save'}: + print('\nFEN-notation >', self.get_fen(white)) + return True + + elif userinput in {'/t', '/turnboard'}: + self.board_turned = not self.board_turned + self.render(self.white if self.bot is not None else not self.bot) + return True + + elif userinput in {'/p', '/printboard'}: + self.render(self.white if self.bot is not None else not self.bot) + return True + + elif userinput in {'/d', '/draw'}: + if not self.asked_draw: + self.asked_draw = True + if (white and self.eval_pos() < 0) or (not white and self.eval_pos() > 0): + print('\nDRAW\n') + raise SystemExit + elif self.eval_pos() == 0: + if random.choices([True, False], weights = [1, 5], k = 1)[0]: + print('\nDRAW\n') + raise SystemExit + print('\nNO DRAW') + return True + print('\nNO DRAW') + return True + print('\nAlready asked for draw this mv') + return True + + elif userinput in {'/r', '/resign'}: + print('\nBLACK WON BY RESIGNATION\n') if white else print('\nWHITE WON BY RESIGNATION\n') + raise SystemExit + + elif userinput in {'/f', '/forcedraw'}: + if self.fifty_mv_ctr > 99: + print('\nWhite forced DRAW\n') if white else print('\nBlack forced DRAW\n') + raise SystemExit + print('\n50-move-rule does not apply') + return True + + elif userinput in {'/i', '/import'}: + if not self.set_fen(input('\nPaste FEN-position here > ')): + print('!INVALID FEN!') + return True + return False + + def find_piece(self, item: str) -> tuple: + for y, row in enumerate(self.pos): + for x, piece in enumerate(row): + if piece == item: + return (y, x) + + def parse(self, userinput: str, white: bool) -> tuple: + color_piece = COLOR_WHITE if white else COLOR_BLACK + short_castling, long_castling, castling_row = (self.w_short_castling, self.w_long_castling, 7) if white else (self.b_short_castling, self.b_long_castling, 0) + + if self._game_controls(userinput, white): + userinput = input('\nEnter your move in algebraic notation > ') + return self.parse(userinput, white) + + userinput = userinput.strip() + check_or_checkmate = '' + capture = False + promotion = '' + + if userinput: + for check_or_checkmate_option, check_code in chessconf.CHECK_OR_CHECKMATE_MAP.items(): + if userinput.endswith(check_or_checkmate_option): + check_or_checkmate = check_code + userinput = userinput[:-len(check_or_checkmate_option)] + break + + if userinput: + if userinput in chessconf.CASTLING_MAP: + if chessconf.CASTLING_MAP[userinput]: + user_mv = ((castling_row, 4, '.'), (castling_row, 5, 'R'), (castling_row, 6, 'K'), (castling_row, 7, '.')) + else: + user_mv = ((castling_row, 0, '.'), (castling_row, 2, 'K'), (castling_row, 3, 'R'), (castling_row, 4, '.')) + if (parsed := self._validate(user_mv, white, check_or_checkmate, capture, promotion)): + return parsed + + promotion = None + + for promotion_option, promotion_code in chessconf.PROMOTION_MAP.items(): + if userinput.endswith(promotion_option): + promotion = color_piece[promotion_code] + userinput = userinput[:-len(promotion_option)] + break + + if len(userinput) >= 2 and userinput[-2] in ALPH_TO_INT and userinput[-1] in NUM_TO_INT: + to_y = NUM_TO_INT[userinput[-1]] + to_x = ALPH_TO_INT[userinput[-2]] + userinput = userinput[:-2] + + from_y = None + from_x = None + from_piece = color_piece['P'] + + if userinput: + abbr_piece = False + for abbr in WHITEPIECES: # need capitalized piece abbreviations + if userinput.startswith(abbr): + from_piece = color_piece[abbr] + abbr_piece = True + userinput = userinput[1:] + break + + if not abbr_piece: + for piece_option, piece_code in chessconf.PIECE_MAP.items(): + if userinput.startswith(piece_option): + from_piece = color_piece[piece_code] + userinput = userinput[len(piece_option):] + break + + if userinput: + for action_option, action_code in chessconf.ACTION_MAP.items(): + if userinput.endswith(action_option): + capture = True if action_code == 'x' else False + userinput = userinput[:-len(action_option)] + break + + if userinput: + from_coords = False + if userinput[-1] in NUM_TO_INT: + from_y = NUM_TO_INT[userinput[-1]] + from_coords = True + userinput = userinput[:-1] + + if userinput: + if userinput[-1] in ALPH_TO_INT: + from_x = ALPH_TO_INT[userinput[-1]] + from_coords = True + userinput = userinput[:-1] + if from_y is not None: + from_piece = self.pos[from_y][from_x] + + if from_coords: + if userinput in {' ', ' on '}: + userinput = '' + + if not userinput: + matchingattackers = [attacker for attacker in self.reach(white)[to_y][to_x] if attacker[2] == from_piece] + if from_y is not None and from_x is not None: + attackers = [attacker for attacker in matchingattackers if attacker[0] == from_y and attacker[1] == from_x] + elif from_y is not None: + attackers = [attacker for attacker in matchingattackers if attacker[0] == from_y] + elif from_x is not None: + attackers = [attacker for attacker in matchingattackers if attacker[1] == from_x] + else: + attackers = matchingattackers + + if len(attackers) == 1: + from_y = attackers[0][0] + from_x = attackers[0][1] + + if promotion: + if from_piece == color_piece['P']: + user_mv = ((from_y, from_x, '.'), (to_y, to_x, promotion)) + validation = self._validate(user_mv, white, check_or_checkmate, capture, promotion) + if validation: + return validation + else: + if from_piece == color_piece['K'] and short_castling and to_y == castling_row and to_x == 6: + user_mv = ((castling_row, 4, '.'), (castling_row, 5, color_piece['R']), (castling_row, 6, color_piece['K']), (castling_row, 7, '.')) + elif from_piece == color_piece['K'] and long_castling and to_y == castling_row and to_x == 2: + user_mv = ((castling_row, 0, '.'), (castling_row, 2, color_piece['K']), (castling_row, 3, color_piece['R']), (castling_row, 4, '.')) + elif from_piece == color_piece['P'] and self.ep_sqr == (to_y, to_x): + user_mv = ((from_y, from_x, '.'), (to_y, to_x, from_piece), (from_y, to_x, '.')) + else: + user_mv = ((from_y, from_x, '.'), (to_y, to_x, from_piece)) + if validation := self._validate(user_mv, white, check_or_checkmate, capture, promotion): + return validation + elif len(attackers) > 1: + userinput = input(''' +Please specify which piece to move. + +Enter your move in algebraic notation > ''') + return self.parse(userinput, white) + + print(('\n!INVALID INPUT! Read the manual for more information.')) + return self.parse(input('\nEnter your move in algebraic notation > '), white) + + def _validate(self, user_mv: mv_struct, white: bool, check_or_checkmate: str = '', capture: bool = False, promotion: str = '') -> tuple | None: + enemycontrol = self.control(not white) + color_piece = COLOR_WHITE if white else COLOR_BLACK + short_castling = ((7, 4, '.'), (7, 5, 'R'), (7, 6, 'K'), (7, 7, '.')) if white else ((0, 4, '.'), (0, 5, 'r'), (0, 6, 'k'), (0, 7, '.')) + long_castling = ((7, 0, '.'), (7, 2, 'K'), (7, 3, 'R'), (7, 4, '.')) if white else ((0, 0, '.'), (0, 2, 'k'), (0, 3, 'r'), (0, 4, '.')) + + if user_mv in self.legal_mvs(white): + notcheck = check_or_checkmate == '+' and not self._is_check(white, user_mv) + notcheckmate = check_or_checkmate == '#' and not self._is_checkmate(white, user_mv) + notcapture = capture and not self._is_capture(user_mv) + + if notcheck or notcheckmate or notcapture: + if notcheck and notcapture: + userinput = input('\nmove is neither a check nor a capture. Do you want to proceed? [Y/n] > ') + elif notcheckmate and notcapture: + userinput = input('\nmove is neither a checkmate nor a capture. Do you want to proceed? [Y/n] > ') + elif notcheck: + userinput = input('\nmove is not a check. Do you want to proceed? [Y/n] > ') + elif notcheckmate: + userinput = input('\nmove is not a checkmate. Do you want to proceed? [Y/n] > ') + elif notcapture: + userinput = input('\nmove is not a capture. Do you want to proceed? [Y/n] > ') + if userinput in {'y', 'yes', '1', '', 'Y'}: + return user_mv + userinput = input('\nEnter your move in algebraic notation > ') + return self.parse(userinput, white) + return user_mv + + if user_mv in self.all_mvs(white): + if any(single_mv[2] == color_piece['K'] for single_mv in user_mv): + if user_mv in (short_castling, long_castling) and enemycontrol[user_mv[0][0]][4]: + print(enemycontrol[user_mv[0][0]][4]) + print('\nKing cannot castle if in check.') + elif (user_mv == short_castling and not enemycontrol[user_mv[0][0]][6]) or (user_mv == long_castling and not enemycontrol[user_mv[0][0]][2]): + print('\nKing cannot castle through enemy controlled square.') + else: + print('\nSquare controlled by enemy.') + else: + y, x = self.find_piece(color_piece['K']) + if enemycontrol[y][x]: + print('\nKing is in check.') + else: + print('\nPiece is pinned to king.') + userinput = input('\nEnter your move in algebraic notation > ') + return self.parse(userinput, white) + + def is_end_game(self) -> bool: + white_queen_ctr = 0 + white_minor_pieces_ctr = 0 + black_queen_ctr = 0 + black_minor_pieces_ctr = 0 + other_pieces = False + for row in self.pos: + for piece in row: + if piece in {'N', 'B'}: + white_minor_pieces_ctr += 1 + if piece == 'Q': + white_queen_ctr += 1 + else: + other_pieces = True + if piece in {'n', 'b'}: + black_minor_pieces_ctr += 1 + if piece == 'q': + black_queen_ctr += 1 + else: + other_pieces = True + + if not white_queen_ctr and not black_queen_ctr: + return True + if white_queen_ctr == 1 and black_queen_ctr == 1: + if white_minor_pieces_ctr <= 1 and black_minor_pieces_ctr <= 1 and not other_pieces: + return True + return False + + def eval_pos(self) -> float: + value = 0 + for y, row in enumerate(self.pos): + for x, piece in enumerate(row): + if piece != '.': + if piece in {'K', 'k'}: + if self.end_game: + value += chessconf.PIECEVALUE[piece] + chessconf.POSTABLES[piece + '_end'][y][x] + else: + value += chessconf.PIECEVALUE[piece] + chessconf.POSTABLES[piece + '_middle'][y][x] + else: + value += chessconf.PIECEVALUE[piece] + chessconf.POSTABLES[piece][y][x] + return value + + def minimax(self, depth: int, ismax: bool, alpha: float | None = -float('inf'), beta: float | None = float('inf')) -> float: + if depth <= 0 and not self.last_move_capture: + return self.eval_pos() + + if ismax: + bestvalue = float('-inf') + for mv in self.all_mvs(True): + self.do(mv) + minimaxvalue = self.minimax(depth - 1, not ismax, alpha, beta) + self.undo() + if minimaxvalue > bestvalue: + bestvalue = minimaxvalue + if bestvalue > alpha: + alpha = bestvalue + if alpha >= beta: + break + return bestvalue + else: + bestvalue = float('inf') + for mv in self.all_mvs(False): + self.do(mv) + minimaxvalue = self.minimax(depth - 1, not ismax, alpha, beta) + self.undo() + if minimaxvalue < bestvalue: + bestvalue = minimaxvalue + if bestvalue < beta: + beta = bestvalue + if alpha >= beta: + break + return bestvalue + + def best_mvs(self, white: bool, depth: int = chessconf.minimax_depth) -> tuple: + if white: + bestvalue, best_mv_list = float('-inf'), [] + for mv in self.legal_mvs(True): + self.do(mv) + minimaxvalue = self.minimax(depth - 1, not white) + self.undo() + if minimaxvalue == bestvalue: + best_mv_list.append(mv) + elif minimaxvalue > bestvalue: + bestvalue, best_mv_list = minimaxvalue, [mv] + legalbest_mvs = [mv for mv in best_mv_list if mv in self.legal_mvs(True)] + return tuple(legalbest_mvs) + else: + bestvalue, best_mv_list = float('inf'), [] + for mv in self.legal_mvs(False): + self.do(mv) + minimaxvalue = self.minimax(depth - 1, not white) + self.undo() + if minimaxvalue == bestvalue: + best_mv_list.append(mv) + elif minimaxvalue < bestvalue: + bestvalue, best_mv_list = minimaxvalue, [mv] + legalbest_mvs = [mv for mv in best_mv_list if mv in self.legal_mvs(False)] + return tuple(legalbest_mvs) + + def stockfish_mv(self, white: bool, depth: int = chessconf.stockfish_depth) -> str | None: + stockfish = subprocess.Popen(chessconf.STOCKFISH_PATH, stdin=subprocess.PIPE, stdout=subprocess.PIPE, text=True) + stockfish.stdin.write(f'position fen {self.get_fen(white)}\ngo depth {depth}\n') + stockfish.stdin.flush() + while True: + output = stockfish.stdout.readline().strip() + if 'bestmove' in output: + mv = output.split(' ')[1] + stockfish.stdin.close() + stockfish.terminate() + return None if mv == '(none)' else mv + + def select_game(self) -> typing.Callable | typing.Callable | typing.Callable | False: + i = 0 + while i < 3: + userinput = input(''' +(1) > play against minimax +(2) > play against stockfish +(3) > pass the board to a friend + +Choose a game mode > ''') + if userinput in {'1', 'one', '(1)', 'ONE', '', 'm', 'minimax', 'M', 'Minimax', 'MINIMAX'}: + return self.play_minimax + elif userinput in {'2', 'two', '(2)', 'TWO', 's', 'stockfish', 'S', 'Stockfish', 'STOCKFISH'}: + return self.play_stockfish + elif userinput in {'3', 'three', '(3)', 'THREE', 'p', 'pass', 'P', 'Pass', 'PASS', 'f', 'friend', 'F', 'FRIEND'}: + return self.pass_and_play + if not self._game_controls(userinput): + i += 1 + return False + + def select_color(self) -> bool: + i = 0 + while i < 3: + userinput = input(''' +(1) > play white +(2) > play black +(3) > play random color + +Choose a color > ''') + if userinput in {'1', 'one', '(1)', 'ONE', 'w', 'white', 'W', 'White', 'WHITE'}: + return True + elif userinput in {'2', 'two', '(2)', 'TWO', 'b', 'black', 'B', 'Black', 'BLACK'}: + return False + elif userinput in {'3', 'three', '(3)', 'THREE', '', 'r', 'random', 'R', 'Random', 'RANDOM'}: + return random.choice(True, False) + if not self._game_controls(userinput): + i += 1 + return random.choice(True, False) + + def pass_and_play(self, white: bool = True) -> None: + while True: + self.render(white) + userinput = input('\nEnter your move in algebraic notation > ') + user_mv = self.parse(userinput, white) + self.make_mv(user_mv, white) + white = not white + + def play_minimax(self, white: bool = True, depth: int = chessconf.minimax_depth) -> None: + self.bot = True + if white: + self.bot = False + self.render(True) + userinput = input('\nEnter your move in algebraic notation > ') + user_mv = self.parse(userinput, True) + self.make_mv(user_mv, True) + while True: + enemy_mv = random.choice(self.best_mvs(not white, depth)) + self.make_mv(enemy_mv, not white) + self.render(white) + userinput = input('\nEnter your move in algebraic notation > ') + user_mv = self.parse(userinput, white) + self.make_mv(user_mv, white) + + def play_stockfish(self, white: bool = True, depth: int = chessconf.stockfish_depth) -> None: + self.bot = True + if white: + self.bot = False + self.render(True) + userinput = input('\nEnter your move in algebraic notation > ') + user_mv = self.parse(userinput, True) + self.make_mv(user_mv, True) + while True: + enemy_return = self.stockfish_mv(not white, depth) + enemy_mv = self.parse(enemy_return, not white) + self.make_mv(enemy_mv, not white) + self.render(white) + userinput = input('\nEnter your move in algebraic notation > ') + user_mv = self.parse(userinput, white) + self.make_mv(user_mv, white) + +if __name__ == '__main__': + board = Board() + game = board.select_game() + color = board.select_color() + game(color)
\ No newline at end of file |
