introduced testing

This commit is contained in:
simoncreates
2026-02-24 18:58:11 +01:00
parent d1f05ba3c3
commit ff3e99382e
+133 -20
View File
@@ -1,6 +1,6 @@
from enum import Enum, auto from enum import Enum, auto
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional from typing import Optional, List, Tuple
class PieceType(Enum): class PieceType(Enum):
@@ -12,38 +12,69 @@ class PieceType(Enum):
KING = auto() KING = auto()
class Color(Enum): class Color(Enum):
WHITE = auto() WHITE = auto()
BLACK = auto() BLACK = auto()
@dataclass @dataclass
class BoardPos: class BoardPos:
p: tuple[int, int] p: Tuple[int, int]
def __str__(self) -> str:
return f"({self.p[0]},{self.p[1]})"
@dataclass @dataclass
class Piece: class Piece:
type: PieceType type: PieceType
color: Color color: Color
def char(self) -> str:
"""Single-character representation. Uppercase = White, lowercase = Black."""
mapping = {
PieceType.PAWN: "p",
PieceType.KNIGHT: "n",
PieceType.BISHOP: "b",
PieceType.ROOK: "r",
PieceType.QUEEN: "q",
PieceType.KING: "k",
}
ch = mapping[self.type]
return ch.upper() if self.color == Color.WHITE else ch
def __str__(self) -> str:
return self.char()
def __repr__(self) -> str:
return f"Piece({self.type.name},{self.color.name})"
@dataclass @dataclass
class BoardMove: class BoardMove:
m_from: BoardPos m_from: BoardPos
m_to: BoardPos m_to: BoardPos
def __str__(self) -> str:
return f"{self.m_from}->{self.m_to}"
@dataclass @dataclass
class BoardField: class BoardField:
piece: Optional[Piece] = None piece: Optional[Piece] = None
def __str__(self) -> str:
return str(self.piece) if self.piece is not None else "."
@dataclass @dataclass
class ChessBoard: class ChessBoard:
fields: list[list[BoardField]] fields: List[List[BoardField]]
def place(self, row: int, col: int, piece_type: PieceType, color: Color): def place(self, row: int, col: int, piece_type: PieceType, color: Color):
self.fields[row][col].piece = Piece(piece_type, color) self.fields[row][col].piece = Piece(piece_type, color)
# wir lieben python <3 :( # initialize default starting position
@classmethod @classmethod
def init_default(cls) -> "ChessBoard": def init_default(cls) -> "ChessBoard":
brd = cls( brd = cls(
@@ -75,11 +106,11 @@ class ChessBoard:
for col, piece_type in enumerate(back_rank): for col, piece_type in enumerate(back_rank):
brd.place(7, col, piece_type, Color.WHITE) brd.place(7, col, piece_type, Color.WHITE)
return brd return brd
def moves_unchecked(self, piece: Piece, pos: BoardPos) -> list[list[tuple[int, int]]]: def moves_unchecked(self, piece: Piece, pos: BoardPos) -> List[List[Tuple[int, int]]]:
row, column = pos.p row, column = pos.p
rays: list[list[tuple[int, int]]] = [] rays: List[List[Tuple[int, int]]] = []
if piece.type == PieceType.KING: if piece.type == PieceType.KING:
king_offsets = [(1, 1), (1, 0), (0, 1), (-1, -1), (-1, 0), (0, -1), (1, -1), (-1, 1)] king_offsets = [(1, 1), (1, 0), (0, 1), (-1, -1), (-1, 0), (0, -1), (1, -1), (-1, 1)]
@@ -99,7 +130,7 @@ class ChessBoard:
forward = 1 forward = 1
start_row = 1 start_row = 1
forward_ray: list[tuple[int, int]] = [] forward_ray: List[Tuple[int, int]] = []
forward_ray.append((row + forward, column)) forward_ray.append((row + forward, column))
# if the pawn is at the start row, include the double-step # if the pawn is at the start row, include the double-step
if row == start_row: if row == start_row:
@@ -139,12 +170,15 @@ class ChessBoard:
def on_board(self, rr: int, cc: int) -> bool: def on_board(self, rr: int, cc: int) -> bool:
return 0 <= rr < 8 and 0 <= cc < 8 return 0 <= rr < 8 and 0 <= cc < 8
def moves_basic_checked(self) -> list[BoardMove]: # takes the color of the player whos possible moves will be returned
moves: list[BoardMove] = [] def moves_basic_checked(self, color: Color) -> List[BoardMove]:
moves: List[BoardMove] = []
for i, row in enumerate(self.fields): for i, row in enumerate(self.fields):
for j, field in enumerate(row): for j, field in enumerate(row):
if field.piece is None: if field.piece is None:
continue continue
if field.piece.color != color:
continue
src_pos = BoardPos((i, j)) src_pos = BoardPos((i, j))
piece = field.piece piece = field.piece
rays = self.moves_unchecked(piece, src_pos) rays = self.moves_unchecked(piece, src_pos)
@@ -182,20 +216,99 @@ class ChessBoard:
# opponent piece # opponent piece
moves.append(BoardMove(src_pos, BoardPos((tr, tc)))) moves.append(BoardMove(src_pos, BoardPos((tr, tc))))
break break
return moves return moves
def possible_moves(self) -> list[BoardMove]: def __str__(self) -> str:
"""
example string repr of starting pos
rnbqkbnr
pppppppp
........
........
........
........
PPPPPPPP
RNBQKBNR
"""
lines: List[str] = []
for r in range(8):
row_chars = [str(self.fields[r][c]) for c in range(8)]
lines.append("".join(row_chars))
return "\n".join(lines)
@classmethod
def from_string(cls, s: str) -> "ChessBoard":
lines = s.strip().split("\n")
brd = cls([[BoardField() for _ in range(8)] for _ in range(8)])
char_to_piece = {
'p': PieceType.PAWN,
'n': PieceType.KNIGHT,
'b': PieceType.BISHOP,
'r': PieceType.ROOK,
'q': PieceType.QUEEN,
'k': PieceType.KING,
}
for r, line in enumerate(lines):
for c, ch in enumerate(line):
if ch == '.':
continue
lower = ch.lower()
if lower not in char_to_piece:
raise ValueError(f"Invalid piece character '{ch}' at ({r},{c})")
piece_type = char_to_piece[lower]
color = Color.WHITE if ch.isupper() else Color.BLACK
brd.place(r, c, piece_type, color)
return brd
return self.moves_basic_checked()
# used only for testing purposes # used only for testing purposes
def main(): def main():
default_brd = ChessBoard.init_default() default_brd = ChessBoard.init_default()
mvs = default_brd.possible_moves()
for mv in mvs:
print(mv)
print(f"len of moves: { len(mvs)}")
main() # standardized expected string for initial position
expected_start = (
"rnbqkbnr\n"
"pppppppp\n"
"........\n"
"........\n"
"........\n"
"........\n"
"PPPPPPPP\n"
"RNBQKBNR"
)
# test string representation of board
actual_start = str(default_brd)
if actual_start != expected_start:
raise AssertionError(f"initial board mismatch:\nexpected:\n{expected_start}\n\nactual:\n{actual_start}")
# test num moves
mvs = default_brd.moves_basic_checked(Color.BLACK)
expected_move_count = 20 # 16 pawn moves and 4 knight
if len(mvs) != expected_move_count:
raise AssertionError(f"Initial move count for black mismatch: expected {expected_move_count}, got {len(mvs)}")
mvs = default_brd.moves_basic_checked(Color.WHITE)
expected_move_count = 20 # 16 pawn moves and 4 knight
if len(mvs) != expected_move_count:
raise AssertionError(f"Initial move count for white mismatch: expected {expected_move_count}, got {len(mvs)}")
# test loading
loaded_brd = ChessBoard.from_string(expected_start)
if str(loaded_brd) != expected_start:
raise AssertionError(
"board loaded from string does not match original representation"
)
print(f"all tests passed")
if __name__ == '__main__':
main()