introduced testing
This commit is contained in:
+131
-18
@@ -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(
|
||||||
@@ -77,9 +108,9 @@ class ChessBoard:
|
|||||||
|
|
||||||
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()
|
||||||
|
|||||||
Reference in New Issue
Block a user