diff --git a/src/controller/controller.py b/src/controller/controller.py index 88685b8..6083b1e 100644 --- a/src/controller/controller.py +++ b/src/controller/controller.py @@ -55,3 +55,9 @@ class Controller: else: move = legal_moves_positions[0] self._make_move(move) + + if self._board.is_checkmate_for(self._board._turn): + self._view.notify_checkmate(self._board._turn) + + if self._board.is_stalemate_for(self._board._turn): + self._view.notify_stalemate(self._board._turn) diff --git a/src/logic/board.py b/src/logic/board.py index 480a97a..ad97082 100644 --- a/src/logic/board.py +++ b/src/logic/board.py @@ -131,11 +131,27 @@ class Board: if Position.is_within_bounds(x, y): possible_pos.append(Position(x, y)) else: - possible_pos += [move.pos for move in piece.legal_moves(self)] + possible_pos += [move.pos for move in piece.legal_moves(self, looking_for_check=True)] if king.pos in possible_pos: return True return False + def is_checkmate_for(self, colour: Colour) -> bool: + """ Is it checkmate for the defending colour passed as parameter """ + return self.is_check_for(colour) and self._no_legal_moves_for(colour) + + def is_stalemate_for(self, colour: Colour) -> bool: + """ Is it stalemate for the defending colour passed as parameter """ + return not self.is_check_for(colour) and self._no_legal_moves_for(colour) + + def _no_legal_moves_for(self, colour: Colour) -> bool: + """ Return true if there are indeed no legal moves for the given colour (for checkmate or stalemate)""" + pieces = self._white if colour == Colour.WHITE else self._black + for piece in pieces.values(): + if len(piece.legal_moves(self)) > 0: + return False + return True + def castling_writes_for(self, colour: Colour) -> set[CastleSide]: return self._white_castling_write if colour == Colour.WHITE else self._black_castling_write diff --git a/src/logic/pieces/bishop.py b/src/logic/pieces/bishop.py index c1a3f47..4099fc1 100644 --- a/src/logic/pieces/bishop.py +++ b/src/logic/pieces/bishop.py @@ -2,7 +2,7 @@ from logic.move import Move from .piece import Piece class Bishop(Piece): - def legal_moves(self, board: "Board") -> list[Move]: + def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]: ret = [] # looking north east @@ -17,5 +17,8 @@ class Bishop(Piece): # looking north west ret.extend(self._look_direction(board, -1, 1)) + if not looking_for_check and board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + return ret diff --git a/src/logic/pieces/king.py b/src/logic/pieces/king.py index 33ec573..006090c 100644 --- a/src/logic/pieces/king.py +++ b/src/logic/pieces/king.py @@ -19,11 +19,13 @@ class King(Piece): if not board_after_move.is_check_for(self.colour): ret.append(move) + if board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + # -- Castles castling_writes = board.castling_writes_for(self.colour) if len(castling_writes) == 0: return ret - print(castling_writes) if CastleSide.King in castling_writes: clear = True @@ -62,8 +64,6 @@ class King(Piece): if clear: ret.append(Move(self, Position(2, self.pos.y), castle_side=CastleSide.Queen)) - print(ret) - return ret diff --git a/src/logic/pieces/knight.py b/src/logic/pieces/knight.py index 9aac2d2..4294ad9 100644 --- a/src/logic/pieces/knight.py +++ b/src/logic/pieces/knight.py @@ -4,7 +4,7 @@ class Knight(Piece): def letter(self): return "n" - def legal_moves(self, board: "Board") -> list["Move"]: + def legal_moves(self, board: "Board", / , looking_for_check = False) -> list["Move"]: ret = [] for dx, dy in [ (+2, +1), (+1, +2), # north east @@ -15,5 +15,9 @@ class Knight(Piece): move = self._move_for_position(board, self.pos.x + dx, self.pos.y + dy) if move is not None: ret.append(move) + + if not looking_for_check and board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + return ret diff --git a/src/logic/pieces/pawn.py b/src/logic/pieces/pawn.py index 8574984..c9dccf3 100644 --- a/src/logic/pieces/pawn.py +++ b/src/logic/pieces/pawn.py @@ -3,7 +3,7 @@ from logic.pieces.piece import Colour, Piece from logic.position import Position class Pawn(Piece): - def legal_moves(self, board) -> list[Move]: + def legal_moves(self, board, / , looking_for_check = False) -> list[Move]: ret = [] # can we capture to the left? @@ -37,4 +37,7 @@ class Pawn(Piece): pos = Position(self.pos.x, self.pos.y - dy) ret.append(Move(self, pos)) + if not looking_for_check and board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + return ret diff --git a/src/logic/pieces/piece.py b/src/logic/pieces/piece.py index bd0741c..76ce781 100644 --- a/src/logic/pieces/piece.py +++ b/src/logic/pieces/piece.py @@ -7,6 +7,9 @@ class Colour(Enum): WHITE = "white" BLACK = "black" + def __str__(self) -> str: + return self.value + class Piece: def __init__(self, pos: Position, colour: Colour) -> None: self.pos = pos @@ -16,6 +19,14 @@ class Piece: def letter(self): return type(self).__name__[0].lower() + def keep_only_blocking(self, candidates: list[Move], board: "Board") -> list[Move]: + ret = [] + for move in candidates: + board_after_move = board.make_move(move) + if not board_after_move.is_check_for(self.colour): + ret.append(move) + return ret + def _look_direction(self, board: "Board", mult_dx: int, mult_dy: int): ret = [] for d in range(1, 8): @@ -50,5 +61,5 @@ class Piece: ret = type(self)(pos, self.colour) return ret - def legal_moves(self, board: "Board") -> list["Move"]: + def legal_moves(self, board: "Board", / , looking_for_check = False) -> list["Move"]: raise NotImplementedError(f"Can't say what the legal moves are for {type(self).__name__}, the method hasn't been implemented yet") diff --git a/src/logic/pieces/queen.py b/src/logic/pieces/queen.py index bfe2f48..0626e1d 100644 --- a/src/logic/pieces/queen.py +++ b/src/logic/pieces/queen.py @@ -2,7 +2,7 @@ from logic.move import Move from .piece import Piece class Queen(Piece): - def legal_moves(self, board: "Board") -> list[Move]: + def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]: ret = [] # looking north east @@ -29,4 +29,7 @@ class Queen(Piece): # looking north ret.extend(self._look_direction(board, 0, 1)) + if not looking_for_check and board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + return ret diff --git a/src/logic/pieces/rook.py b/src/logic/pieces/rook.py index 4570682..ce6d6f4 100644 --- a/src/logic/pieces/rook.py +++ b/src/logic/pieces/rook.py @@ -2,7 +2,7 @@ from logic.move import Move from .piece import Piece class Rook(Piece): - def legal_moves(self, board: "Board") -> list[Move]: + def legal_moves(self, board: "Board", / , looking_for_check = False) -> list[Move]: ret = [] # looking east @@ -17,4 +17,7 @@ class Rook(Piece): # looking north ret.extend(self._look_direction(board, 0, 1)) + if not looking_for_check and board.is_check_for(self.colour): + return self.keep_only_blocking(ret, board) + return ret diff --git a/src/view/gui.py b/src/view/gui.py index c4068dd..9bcf91c 100644 --- a/src/view/gui.py +++ b/src/view/gui.py @@ -1,4 +1,5 @@ import tkinter as tk +from tkinter import messagebox from typing import Type from PIL import ImageTk, Image import os @@ -60,6 +61,12 @@ class GUI(View): self._controller.on_tile_selected(x, y) + def notify_checkmate(self, colour: Colour) -> None: + messagebox.showinfo("Checkmate", f"{colour} is currently checkmated") + + def notify_stalemate(self, colour: Colour) -> None: + messagebox.showinfo("Stalemate", f"{colour} is currently stalemated") + def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None: self.canvas.delete("all") diff --git a/src/view/view.py b/src/view/view.py index 8c1d357..33581e0 100644 --- a/src/view/view.py +++ b/src/view/view.py @@ -1,6 +1,6 @@ from logic.board import Board from logic.move import Move -from logic.pieces.piece import Piece +from logic.pieces.piece import Colour, Piece class View: @@ -13,6 +13,12 @@ class View: def update_board(self, board: Board, selected_piece: Piece, legal_moves: list[Move]) -> None: raise NotImplementedError(f"Can't update the board, the update_board() method of {type(self)} is not implemented") + def notify_checkmate(self, colour: Colour) -> None: + raise NotImplementedError(f"Can't notify of the checkmate, the notify_checkmate() method of {type(self)} is not implemented") + + def notify_stalemate(self, colour: Colour) -> None: + raise NotImplementedError(f"Can't notify of the stalemate, the notify_stalemate() method of {type(self)} is not implemented") + def set_controller(self, controller: "Controller") -> None: self._controller = controller