more cool stuff
chess, woah
This commit is contained in:
28
abc.py
Normal file
28
abc.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
def split_and_join(line):
|
||||||
|
full = []
|
||||||
|
current = ''
|
||||||
|
line_len = len(line)
|
||||||
|
for c in range(line_len):
|
||||||
|
char = line[c]
|
||||||
|
if char == ' ':
|
||||||
|
full.append(current)
|
||||||
|
current = ''
|
||||||
|
else:
|
||||||
|
current = current + char
|
||||||
|
|
||||||
|
if c == line_len - 1:
|
||||||
|
full.append(str(current))
|
||||||
|
|
||||||
|
full_final = ''
|
||||||
|
full_len = len(full)
|
||||||
|
for v in range(full_len):
|
||||||
|
val = full[v]
|
||||||
|
full_final += val + ('' if v == full_len-1 else '-')
|
||||||
|
|
||||||
|
return full_final
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
line = input()
|
||||||
|
result = split_and_join(line)
|
||||||
|
print(result)
|
||||||
|
|
||||||
43
book_stuff/student.py
Normal file
43
book_stuff/student.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
class Student:
|
||||||
|
def __init__(self, name: str, gNumber: int, gpa: float):
|
||||||
|
self.set_name(name)
|
||||||
|
self.set_gNumber(gNumber)
|
||||||
|
self.set_gpa(gpa)
|
||||||
|
|
||||||
|
# --- Name ---
|
||||||
|
def get_name(self) -> str:
|
||||||
|
return self._name
|
||||||
|
|
||||||
|
def set_name(self, value: str) -> None:
|
||||||
|
if not isinstance(value, str):
|
||||||
|
raise TypeError("Name must be a string")
|
||||||
|
if not value.isalpha():
|
||||||
|
raise ValueError("Name must contain only alphabetic characters")
|
||||||
|
if not (1 <= len(value) <= 255):
|
||||||
|
raise ValueError("Name must be between 1 and 255 characters")
|
||||||
|
self._name = value
|
||||||
|
|
||||||
|
# --- gNumber ---
|
||||||
|
def get_gNumber(self) -> int:
|
||||||
|
return self._gNumber
|
||||||
|
|
||||||
|
def set_gNumber(self, value: int) -> None:
|
||||||
|
if not isinstance(value, int):
|
||||||
|
raise TypeError("gNumber must be an integer")
|
||||||
|
if not (100000 <= value <= 999999):
|
||||||
|
raise ValueError("gNumber must be a 6-digit number between 100000 and 999999")
|
||||||
|
self._gNumber = value
|
||||||
|
|
||||||
|
# --- GPA ---
|
||||||
|
def get_gpa(self) -> float:
|
||||||
|
return self._gpa
|
||||||
|
|
||||||
|
def set_gpa(self, value: float) -> None:
|
||||||
|
if not isinstance(value, (int, float)):
|
||||||
|
raise TypeError("GPA must be a number")
|
||||||
|
if not (0.0 <= value <= 6.0):
|
||||||
|
raise ValueError("GPA must be between 0.0 and 6.0")
|
||||||
|
self._gpa = float(value)
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"Student(Name: {self._name}, gNumber: {self._gNumber}, GPA: {self._gpa:.2f})"
|
||||||
90
book_stuff/test_student.py
Normal file
90
book_stuff/test_student.py
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import pytest
|
||||||
|
from student import Student # assumes Student class is in student.py
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- FIXTURES ----------
|
||||||
|
@pytest.fixture
|
||||||
|
def valid_student():
|
||||||
|
"""Fixture for a valid student object"""
|
||||||
|
return Student("Alice", 123456, 4.0)
|
||||||
|
|
||||||
|
def test_create_valid_student(valid_student):
|
||||||
|
assert valid_student.get_name() == "Alice"
|
||||||
|
assert valid_student.get_gNumber() == 123456
|
||||||
|
assert valid_student.get_gpa() == 4.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_name(valid_student):
|
||||||
|
valid_student.set_name("Bob")
|
||||||
|
assert valid_student.get_name() == "Bob"
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_gNumber(valid_student):
|
||||||
|
valid_student.set_gNumber(654321)
|
||||||
|
assert valid_student.get_gNumber() == 654321
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_gpa(valid_student):
|
||||||
|
valid_student.set_gpa(5.5)
|
||||||
|
assert valid_student.get_gpa() == 5.5
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- NAME VALIDATION ----------
|
||||||
|
@pytest.mark.parametrize("invalid_name", [
|
||||||
|
"", # empty string
|
||||||
|
"A" * 256, # too long
|
||||||
|
"Alice123", # contains digits
|
||||||
|
"Alice!", # contains special character
|
||||||
|
123, # not a string
|
||||||
|
])
|
||||||
|
def test_invalid_name_raises(invalid_name):
|
||||||
|
with pytest.raises((ValueError, TypeError)):
|
||||||
|
Student(invalid_name, 123456, 3.0)
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- gNumber VALIDATION ----------
|
||||||
|
@pytest.mark.parametrize("invalid_gNumber", [
|
||||||
|
99999, # too small
|
||||||
|
1000000, # too large
|
||||||
|
"123456", # not an int
|
||||||
|
12.34, # float
|
||||||
|
])
|
||||||
|
def test_invalid_gNumber_raises(invalid_gNumber):
|
||||||
|
with pytest.raises((ValueError, TypeError)):
|
||||||
|
Student("Charlie", invalid_gNumber, 3.5)
|
||||||
|
|
||||||
|
|
||||||
|
def test_boundary_gNumber_valid():
|
||||||
|
s1 = Student("David", 100000, 2.0)
|
||||||
|
s2 = Student("Eve", 999999, 3.0)
|
||||||
|
assert s1.get_gNumber() == 100000
|
||||||
|
assert s2.get_gNumber() == 999999
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- GPA VALIDATION ----------
|
||||||
|
@pytest.mark.parametrize("invalid_gpa", [
|
||||||
|
-0.1, # below range
|
||||||
|
6.1, # above range
|
||||||
|
"4.0", # string instead of number
|
||||||
|
None, # NoneType
|
||||||
|
])
|
||||||
|
def test_invalid_gpa_raises(invalid_gpa):
|
||||||
|
with pytest.raises((ValueError, TypeError)):
|
||||||
|
Student("Frank", 222222, invalid_gpa)
|
||||||
|
|
||||||
|
|
||||||
|
def test_boundary_gpa_valid():
|
||||||
|
s1 = Student("Grace", 333333, 0.0)
|
||||||
|
s2 = Student("Heidi", 444444, 6.0)
|
||||||
|
assert s1.get_gpa() == 0.0
|
||||||
|
assert s2.get_gpa() == 6.0
|
||||||
|
|
||||||
|
|
||||||
|
# ---------- STRING REPRESENTATION ----------
|
||||||
|
def test_str_representation(valid_student):
|
||||||
|
s = str(valid_student)
|
||||||
|
assert "Alice" in s
|
||||||
|
assert "123456" in s
|
||||||
|
assert "4.00" in s
|
||||||
|
|
||||||
|
|
||||||
164
chess/chess_gui_small_view.py
Normal file
164
chess/chess_gui_small_view.py
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import copy
|
||||||
|
from enum import Enum
|
||||||
|
import pygame as pg
|
||||||
|
import pygame_gui as gui
|
||||||
|
from chess_model import ChessModel, MoveValidity, UndoException
|
||||||
|
from move import Move
|
||||||
|
from player import Player
|
||||||
|
|
||||||
|
IMAGE_SIZE = 52 #small format - images 52 X 52
|
||||||
|
|
||||||
|
|
||||||
|
class SpriteType(Enum):
|
||||||
|
King = 0
|
||||||
|
Queen = 1
|
||||||
|
Bishop = 2
|
||||||
|
Knight = 3
|
||||||
|
Rook = 4
|
||||||
|
Pawn = 5
|
||||||
|
|
||||||
|
class SpriteColor(Enum):
|
||||||
|
WHITE = 0
|
||||||
|
BLACK = 1
|
||||||
|
|
||||||
|
class GUI:
|
||||||
|
first = True
|
||||||
|
def __init__(self) -> None:
|
||||||
|
pg.init()
|
||||||
|
self.__model = ChessModel()
|
||||||
|
self._screen = pg.display.set_mode((800, 600))
|
||||||
|
pg.display.set_caption("Laker Chess")
|
||||||
|
self._ui_manager = gui.UIManager((800, 600))
|
||||||
|
self._side_box = gui.elements.UITextBox('<b>Laker Chess</b><br /><br />White moves first.<br />',
|
||||||
|
relative_rect=pg.Rect((500, 100), (400, 500)),
|
||||||
|
manager=self._ui_manager)
|
||||||
|
self._undo_button = gui.elements.UIButton(relative_rect = pg.Rect((700, 50), (100, 50)),
|
||||||
|
text='Undo',
|
||||||
|
manager=self._ui_manager)
|
||||||
|
self._restart_button = gui.elements.UIButton(relative_rect = pg.Rect((600, 50), (100, 50)),
|
||||||
|
text='Reset',
|
||||||
|
manager=self._ui_manager)
|
||||||
|
self._piece_selected = False
|
||||||
|
self._first_selected = (0, 0)
|
||||||
|
self._second_selected = (0, 0)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_images(cls):
|
||||||
|
def load_image(color, ptype):
|
||||||
|
SS = pg.image.load('./images/pieces.png')
|
||||||
|
a = 105
|
||||||
|
surf = pg.Surface((a,a), pg.SRCALPHA)
|
||||||
|
surf.blit(SS, (0, 0), pg.rect.Rect(a*ptype.value, color.value*a, a, a))
|
||||||
|
surf_scaled = pg.transform.scale(surf, (IMAGE_SIZE, IMAGE_SIZE))
|
||||||
|
return surf_scaled
|
||||||
|
cls.white_sprites = {}
|
||||||
|
cls.black_sprites = {}
|
||||||
|
for st in SpriteType:
|
||||||
|
cls.white_sprites[st.name] = load_image(SpriteColor.WHITE, st)
|
||||||
|
cls.black_sprites[st.name] = load_image(SpriteColor.BLACK, st)
|
||||||
|
|
||||||
|
def run_game(self) -> None:
|
||||||
|
running = True
|
||||||
|
time_delta = 0
|
||||||
|
clock = pg.time.Clock()
|
||||||
|
while running:
|
||||||
|
for event in pg.event.get():
|
||||||
|
if event.type == pg.QUIT:
|
||||||
|
running = False
|
||||||
|
if event.type == pg.MOUSEBUTTONDOWN:
|
||||||
|
x, y = pg.mouse.get_pos()
|
||||||
|
y, x = self.__get_coords__(y, x)
|
||||||
|
piece = self.__model.piece_at(y, x)
|
||||||
|
if not self._piece_selected and piece:
|
||||||
|
if piece.player != self.__model.current_player:
|
||||||
|
msg = 'Not your turn!'
|
||||||
|
self._side_box.append_html_text(msg + '<br />')
|
||||||
|
else:
|
||||||
|
self._piece_selected = True
|
||||||
|
self._first_selected = y, x
|
||||||
|
self._piece_selected = piece
|
||||||
|
elif self._piece_selected:
|
||||||
|
mv = Move(self._first_selected[0], self._first_selected[1], y, x)
|
||||||
|
if self.__model.is_valid_move(mv):
|
||||||
|
target = self.__model.piece_at(y, x)
|
||||||
|
self.__model.move(mv)
|
||||||
|
if target is not None:
|
||||||
|
msg = f'Moved {self._piece_selected} and captured {target}'
|
||||||
|
else:
|
||||||
|
msg = f'Moved {self._piece_selected}'
|
||||||
|
self._side_box.append_html_text(msg + '<br />')
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._side_box.append_html_text(f'{self.__model.messageCode}<br />')
|
||||||
|
incheck = self.__model.in_check(self.__model.current_player)
|
||||||
|
complete = self.__model.is_complete()
|
||||||
|
|
||||||
|
if incheck:
|
||||||
|
player_color = self.__model.current_player.name
|
||||||
|
if complete:
|
||||||
|
self._side_box.append_html_text(f'{player_color} is in CHECKMATE!<br />GAME OVER!')
|
||||||
|
else:
|
||||||
|
self._side_box.append_html_text(f'{player_color} is in CHECK!<br />')
|
||||||
|
|
||||||
|
self._piece_selected = False
|
||||||
|
else:
|
||||||
|
self._piece_selected = False
|
||||||
|
if event.type == gui.UI_BUTTON_PRESSED:
|
||||||
|
if event.ui_element == self._restart_button:
|
||||||
|
self.__model = ChessModel()
|
||||||
|
self._side_box.set_text("Restarting game...<br />")
|
||||||
|
if event.ui_element == self._undo_button:
|
||||||
|
try:
|
||||||
|
self.__model.undo()
|
||||||
|
self._side_box.append_html_text('Undoing move.<br />')
|
||||||
|
except UndoException as e:
|
||||||
|
self._side_box.append_html_text(f'{e}<br />')
|
||||||
|
self._ui_manager.process_events(event)
|
||||||
|
|
||||||
|
self._screen.fill((255, 255, 255))
|
||||||
|
self.__draw_board__()
|
||||||
|
self._ui_manager.draw_ui(self._screen)
|
||||||
|
self._ui_manager.update(time_delta)
|
||||||
|
|
||||||
|
pg.display.flip()
|
||||||
|
time_delta = clock.tick(30) / 1000.0
|
||||||
|
|
||||||
|
def __get_coords__(self, y, x):
|
||||||
|
grid_x = x // IMAGE_SIZE
|
||||||
|
grid_y = y // IMAGE_SIZE
|
||||||
|
return grid_y, grid_x
|
||||||
|
|
||||||
|
def __draw_board__(self) -> None:
|
||||||
|
count = 0
|
||||||
|
color = (255, 255, 255)
|
||||||
|
for x in range(0, 8):
|
||||||
|
for y in range(0, 8):
|
||||||
|
if count % 2 == 0:
|
||||||
|
color = (255, 255, 255)
|
||||||
|
else:
|
||||||
|
color = (127, 127, 127)
|
||||||
|
count = count + 1
|
||||||
|
pg.draw.rect(self._screen, color, pg.rect.Rect(x * IMAGE_SIZE, y * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE))
|
||||||
|
if self._piece_selected and (y, x) == self._first_selected:
|
||||||
|
pg.draw.rect(self._screen, (255, 0, 0), pg.rect.Rect(x * IMAGE_SIZE, y * IMAGE_SIZE, IMAGE_SIZE, IMAGE_SIZE), 2)
|
||||||
|
draw_piece = self.__model.piece_at(y, x)
|
||||||
|
if draw_piece is not None:
|
||||||
|
if draw_piece.player == Player.BLACK:
|
||||||
|
d = GUI.black_sprites
|
||||||
|
else:
|
||||||
|
d = GUI.white_sprites
|
||||||
|
self._screen.blit(copy.deepcopy(d[draw_piece.type()]), (x * IMAGE_SIZE, y * IMAGE_SIZE))
|
||||||
|
count = count + 1
|
||||||
|
pg.draw.line(self._screen, (0, 0, 0), (0, 840), (840, 840))
|
||||||
|
pg.draw.line(self._screen, (0, 0, 0), (840, 840), (840, 0))
|
||||||
|
GUI.first = False
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
GUI.load_images()
|
||||||
|
g = GUI()
|
||||||
|
g.run_game()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
35
chess/chess_model.py
Normal file
35
chess/chess_model.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from player import Player
|
||||||
|
from move import Move
|
||||||
|
from chess_piece import ChessPiece
|
||||||
|
from pawn import Pawn
|
||||||
|
from rook import Rook
|
||||||
|
from knight import Knight
|
||||||
|
from bishop import Bishop
|
||||||
|
from queen import Queen
|
||||||
|
from king import King
|
||||||
|
from move import Move
|
||||||
|
|
||||||
|
class MoveValidity(Enum):
|
||||||
|
Valid = 1
|
||||||
|
Invalid = 2
|
||||||
|
MovingIntoCheck = 3
|
||||||
|
StayingInCheck = 4
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.value == 2:
|
||||||
|
return 'Invalid move.'
|
||||||
|
|
||||||
|
if self.value == 3:
|
||||||
|
return 'Invalid -- cannot move into check.'
|
||||||
|
|
||||||
|
if self.value == 4:
|
||||||
|
return 'Invalid -- must move out of check.'
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: create UndoException
|
||||||
|
|
||||||
|
|
||||||
|
class ChessModel:
|
||||||
|
# TODO: fill in this class
|
||||||
|
pass
|
||||||
52
chess/chess_piece.py
Normal file
52
chess/chess_piece.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
from player import Player
|
||||||
|
from move import Move
|
||||||
|
from typing import TypeVar
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
|
||||||
|
ChessPieceT = TypeVar('ChessPieceT')
|
||||||
|
|
||||||
|
# my list of custom exceptions
|
||||||
|
class PieceOutOfBoundsError(Exception): pass
|
||||||
|
class StartEndPositionMismatch(Exception): pass
|
||||||
|
|
||||||
|
class ChessPiece:
|
||||||
|
def __init__(self, piece_color: Player):
|
||||||
|
self.player = piece_color
|
||||||
|
|
||||||
|
@property
|
||||||
|
def player(self):
|
||||||
|
return self.__player
|
||||||
|
|
||||||
|
@player.setter
|
||||||
|
def player(self, new_val):
|
||||||
|
if not isinstance(new_val, Player):
|
||||||
|
raise TypeError(f'new value for player is not of type Player')
|
||||||
|
self.__player = new_val
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
# im not making this abstract, attributes amongst each piece are the same, str repr is also dynamic for the class name
|
||||||
|
return f'[{self.__class__.__name__} player={self.player}]'
|
||||||
|
|
||||||
|
def is_valid_move(self, move: Move, board: list[list[ChessPieceT]]) -> bool:
|
||||||
|
if not isinstance(board, list):
|
||||||
|
raise TypeError(f'board must be a list')
|
||||||
|
|
||||||
|
for arr in board:
|
||||||
|
if not isinstance(arr, list):
|
||||||
|
raise TypeError(f'each element in the board list bust be another list')
|
||||||
|
|
||||||
|
for v in arr:
|
||||||
|
if not isinstance(v, ChessPiece):
|
||||||
|
raise TypeError(f'each element in each row of the board must be of type ChessPiece')
|
||||||
|
|
||||||
|
board_dim = len(board)
|
||||||
|
board_orig: ChessPiece = board[move.to_row][move.to_col]
|
||||||
|
board_dest: ChessPiece = board[move.from_row][move.from_col]
|
||||||
|
within_bounds = board_dim <= move.to_col <= board_dim and board_dim <= move.to_row <= board_dim
|
||||||
|
different_position = move.from_col != move.to_col and move.from_row != move.to_row
|
||||||
|
at_position = board_orig == self
|
||||||
|
is_piece_class = isinstance(board_dest, ChessPiece)
|
||||||
|
taking_friendly_piece = board_dest.player != self.player
|
||||||
|
|
||||||
|
print(f'within_bounds={within_bounds}, different_position={different_position}, at_position={at_position}, is_piece_class={is_piece_class}, taking_friendly_piece={taking_friendly_piece}')
|
||||||
|
return within_bounds and different_position and at_position and is_piece_class and taking_friendly_piece
|
||||||
BIN
chess/images/pieces.png
Normal file
BIN
chess/images/pieces.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
BIN
chess/images/small_pieces.png
Normal file
BIN
chess/images/small_pieces.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
121
chess/move.py
Normal file
121
chess/move.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Move:
|
||||||
|
def __init__(self, from_row, from_col, to_row, to_col):
|
||||||
|
self.from_row = from_row
|
||||||
|
self.from_col = from_col
|
||||||
|
self.to_row = to_row
|
||||||
|
self.to_col = to_col
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
output = f'Move [from_row={self.from_row}, from_col={self.from_col}'
|
||||||
|
output += f', to_row={self.to_row}, to_col={self.to_col}]'
|
||||||
|
return output
|
||||||
|
|
||||||
|
# kinda just guessing, but on prarielearn it only showed the file names included in the project and the
|
||||||
|
# ones we need to create for the submission, i was gonna put all these in their own files, but now i'm
|
||||||
|
# just putting it here because i dont wanna risk not being able to submit
|
||||||
|
|
||||||
|
class PieceType:
|
||||||
|
# piece type for each piece
|
||||||
|
PAWN = 0
|
||||||
|
ROOK = 1
|
||||||
|
KING = 2
|
||||||
|
QUEEN = 3
|
||||||
|
KNIGHT = 4
|
||||||
|
BISHOP = 5
|
||||||
|
|
||||||
|
# check if something is a valid move set element ( (y, x) tuple )
|
||||||
|
def valid_move_set_element(move_set: tuple[int, int]) -> bool:
|
||||||
|
# check if move set is a tuple
|
||||||
|
if not isinstance(move_set, tuple):
|
||||||
|
raise TypeError(f'each move set in move sets must be a tuple ({move_set})')
|
||||||
|
|
||||||
|
# check if the length of the tuple is 2, because it needs to have a y and x
|
||||||
|
ms_len = len(move_set)
|
||||||
|
if ms_len != 2:
|
||||||
|
raise ValueError(f'length of move set ({move_set}) is {ms_len}, must be 2 (y and x)')
|
||||||
|
|
||||||
|
# check if each element is an int
|
||||||
|
for i in range(ms_len):
|
||||||
|
p = move_set[i]
|
||||||
|
if not isinstance(p, int):
|
||||||
|
raise TypeError(f'tuple element at index {i} ({p}) must be an int')
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
# general move set list class for each piece type
|
||||||
|
class MoveSets:
|
||||||
|
def __init__(self, *move_sets: tuple[int, int]):
|
||||||
|
# loop over indices of move_sets, checking if each element at index i is valid, exception thrown by valid_move_set if not
|
||||||
|
for i in range(len(move_sets)):
|
||||||
|
valid_move_set_element(move_sets[i])
|
||||||
|
|
||||||
|
# set all the stuff equal
|
||||||
|
self.__move_sets = move_sets
|
||||||
|
|
||||||
|
@property
|
||||||
|
def move_sets(self):
|
||||||
|
return self.__move_sets
|
||||||
|
|
||||||
|
def is_valid_move(self, move: Move) -> bool:
|
||||||
|
# is the move valid, i dunno
|
||||||
|
raise NotImplementedError('u gotta implement me bruh')
|
||||||
|
|
||||||
|
def valid_range(max: int) -> list[int]:
|
||||||
|
return [-i if max < 0 else i for i in range(1, abs(max)+1)]
|
||||||
|
|
||||||
|
# class for static move sets
|
||||||
|
class StaticMoveSet(MoveSets):
|
||||||
|
def is_valid_move(self, move: Move) -> bool:
|
||||||
|
from_to_row_diff = move.to_row - move.from_row
|
||||||
|
from_to_col_diff = move.to_col - move.from_col
|
||||||
|
for ms in self.move_sets:
|
||||||
|
if from_to_row_diff == ms[0] and from_to_col_diff == ms[1]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# in these lists, the move sets are dynamic, so the y and x are a range of times they can move on the x and y
|
||||||
|
rook_valid_move_sets = [(0, 8), (8, 0), (0, -8), (-8, 0)]
|
||||||
|
|
||||||
|
# class for dynamic move sets
|
||||||
|
class DynamicMoveSet(MoveSets):
|
||||||
|
def is_valid_move(self, move: Move) -> bool:
|
||||||
|
from_to_row_diff = move.to_row - move.from_row
|
||||||
|
from_to_col_diff = move.to_col - move.from_col
|
||||||
|
# check if the to and from actually moved
|
||||||
|
if from_to_row_diff == 0 and from_to_col_diff == 0:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for ms in self.move_sets:
|
||||||
|
possible_valid_row_moves, possible_valid_col_moves = [valid_range(mse) for mse in ms]
|
||||||
|
|
||||||
|
# check if move in row is possible, only if there are valid moves for row movement
|
||||||
|
if len(possible_valid_row_moves) > 0:
|
||||||
|
if from_to_row_diff not in possible_valid_row_moves:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# if theres no valid moves for rows, make sure theres no change in the from to row difference
|
||||||
|
if from_to_row_diff != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# check if move in column is possible, only if there are valid moves for column movement
|
||||||
|
if len(possible_valid_col_moves) > 0:
|
||||||
|
if from_to_col_diff not in possible_valid_col_moves:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# if theres no valid moves for columns, make sure theres no change in the from to column difference
|
||||||
|
if from_to_col_diff != 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
# create move sets
|
||||||
|
|
||||||
|
# static move sets
|
||||||
|
pawn_move_sets = StaticMoveSet((0, 1), (0, 2))
|
||||||
|
|
||||||
|
# dynamic move sets
|
||||||
12
chess/pawn.py
Normal file
12
chess/pawn.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from chess_piece import ChessPiece
|
||||||
|
from move import Move
|
||||||
|
from player import Player
|
||||||
|
from move_sets import pawn_valid_move_sets
|
||||||
|
|
||||||
|
class Pawn(ChessPiece):
|
||||||
|
def __init__(self, piece_color: Player):
|
||||||
|
super().__init__(piece_color)
|
||||||
|
|
||||||
|
def is_valid_move(self, move: Move, board: list[list[ChessPiece]]) -> bool:
|
||||||
|
# run original check and other piece specific checks
|
||||||
|
orig_is_valid = super().is_valid_move(move, board)
|
||||||
14
chess/player.py
Normal file
14
chess/player.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class Player(Enum):
|
||||||
|
BLACK = 0
|
||||||
|
WHITE = 1
|
||||||
|
|
||||||
|
def next(self):
|
||||||
|
cls = self.__class__
|
||||||
|
members = list(cls)
|
||||||
|
index = members.index(self) + 1
|
||||||
|
if index >= len(members):
|
||||||
|
index = 0
|
||||||
|
return members[index]
|
||||||
|
|
||||||
104
chess/test.py
Normal file
104
chess/test.py
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
from chess_piece import ChessPiece
|
||||||
|
from pytest import fixture, mark
|
||||||
|
from player import Player
|
||||||
|
from move import StaticMoveSet, Move, DynamicMoveSet, valid_range
|
||||||
|
from random import randint, choice
|
||||||
|
|
||||||
|
# chess piece tests
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def valid_piece():
|
||||||
|
return ChessPiece(Player.WHITE)
|
||||||
|
|
||||||
|
def test_update_player(valid_piece: ChessPiece):
|
||||||
|
valid_piece.player = Player.BLACK
|
||||||
|
assert valid_piece.player == Player.BLACK
|
||||||
|
|
||||||
|
valid_piece.player = Player.WHITE
|
||||||
|
assert valid_piece.player == Player.WHITE
|
||||||
|
|
||||||
|
def test_repr_str(valid_piece: ChessPiece):
|
||||||
|
rep = str(valid_piece)
|
||||||
|
assert 'player='
|
||||||
|
|
||||||
|
# move set testing (kinda separate from main project)
|
||||||
|
|
||||||
|
_init_val = 4
|
||||||
|
|
||||||
|
# static move sets
|
||||||
|
|
||||||
|
_static_move_sets = [(1, 0), (-3, 0), (0, 2), (0, -1), (4, 4), (-2, -2), (1, -3), (-3, 4)]
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def valid_static_move_set():
|
||||||
|
return StaticMoveSet(*_static_move_sets)
|
||||||
|
|
||||||
|
# test valid
|
||||||
|
|
||||||
|
def test_valid_static_moves(valid_static_move_set: StaticMoveSet):
|
||||||
|
for ms in _static_move_sets:
|
||||||
|
assert valid_static_move_set.is_valid_move(Move(_init_val, _init_val, _init_val+ms[0], _init_val+ms[1]))
|
||||||
|
|
||||||
|
# test invalid
|
||||||
|
|
||||||
|
def test_invalid_static_moves(valid_static_move_set: StaticMoveSet):
|
||||||
|
for ms in _static_move_sets:
|
||||||
|
assert not valid_static_move_set.is_valid_move(Move(_init_val, _init_val, _init_val+ms[0]+(-1 if ms[0] < 0 else 1), _init_val+ms[1]+(-1 if ms[1] < 0 else 1)))
|
||||||
|
|
||||||
|
# dynamic move sets
|
||||||
|
|
||||||
|
_dynamic_move_sets = [(4, 0), (-2, 0), (0, 8), (0, -6), (4, 4), (-4, -4), (2, -5), (-3, 4)]
|
||||||
|
|
||||||
|
@fixture
|
||||||
|
def valid_dynamic_move_set():
|
||||||
|
return DynamicMoveSet(*_dynamic_move_sets)
|
||||||
|
|
||||||
|
def test_valid_dynamic_moves(valid_dynamic_move_set: DynamicMoveSet):
|
||||||
|
for ms in _dynamic_move_sets:
|
||||||
|
row = ms[0]
|
||||||
|
col = ms[1]
|
||||||
|
|
||||||
|
# find a valid range on numbers to select from using the row and column
|
||||||
|
valid_range_row = valid_range(row)
|
||||||
|
valid_range_col = valid_range(col)
|
||||||
|
|
||||||
|
# check if the ranges for rows and columns are empty individually, if so;
|
||||||
|
# set random value to 0, if not, set it to a random element from it's respective list
|
||||||
|
if len(valid_range_row) == 0:
|
||||||
|
rnd_row = 0
|
||||||
|
else:
|
||||||
|
rnd_row = choice(valid_range_row)
|
||||||
|
|
||||||
|
if len(valid_range_col) == 0:
|
||||||
|
rnd_col = 0
|
||||||
|
else:
|
||||||
|
rnd_col = choice(valid_range_col)
|
||||||
|
|
||||||
|
# test dat thing
|
||||||
|
assert valid_dynamic_move_set.is_valid_move(Move(_init_val, _init_val, _init_val+rnd_row, _init_val+rnd_col))
|
||||||
|
|
||||||
|
_RND_MIN = 10
|
||||||
|
_RND_MAX = 20
|
||||||
|
|
||||||
|
def test_invalid_dynamic_moves(valid_dynamic_move_set: DynamicMoveSet):
|
||||||
|
for ms in _dynamic_move_sets:
|
||||||
|
row = ms[0]
|
||||||
|
col = ms[1]
|
||||||
|
|
||||||
|
# check if the row and column ranges are equal to zero, if so;
|
||||||
|
# set random value to 0, if not, create random number between _rnd_min and _rnd_max
|
||||||
|
# then, add the random number, making it negative if the column or row in that instance > 0
|
||||||
|
if row == 0:
|
||||||
|
row_rnd_add = 0
|
||||||
|
else:
|
||||||
|
rnd = randint(_RND_MIN, _RND_MAX)
|
||||||
|
row_rnd_add = row + (rnd if row > 0 else -rnd)
|
||||||
|
|
||||||
|
if col == 0:
|
||||||
|
col_rnd_add = 0
|
||||||
|
else:
|
||||||
|
rnd = randint(_RND_MIN, _RND_MAX)
|
||||||
|
col_rnd_add = col + (rnd if col > 0 else -rnd)
|
||||||
|
|
||||||
|
#print(f'{ms}, ({_init_val}+{row_rnd_add}, {_init_val}+{col_rnd_add}) = ({_init_val+row_rnd_add}, {_init_val+col_rnd_add})')
|
||||||
|
assert not valid_dynamic_move_set.is_valid_move(Move(_init_val, _init_val, _init_val+row_rnd_add, _init_val+col_rnd_add))
|
||||||
Reference in New Issue
Block a user