class Model: class Gamestate(Enum): invalidMove = -1 inProgress = 0 whiteWon = 1 blackWon = 2 tie = 3 def new_game(self): self.__init__() def __init__(self): self.board = Gameboard() self.chips = {Coordinate.a1: Chip(Chip.Color.white), Coordinate.c1: Chip(Chip.Color.white), Coordinate.e1: Chip(Chip.Color.white), Coordinate.g1: Chip(Chip.Color.white), Coordinate.b2: Chip(Chip.Color.white), Coordinate.d2: Chip(Chip.Color.white), Coordinate.f2: Chip(Chip.Color.white), Coordinate.h2: Chip(Chip.Color.white), Coordinate.a3: Chip(Chip.Color.white), Coordinate.c3: Chip(Chip.Color.white), Coordinate.e3: Chip(Chip.Color.white), Coordinate.g3: Chip(Chip.Color.white), Coordinate.b6: Chip(Chip.Color.black), Coordinate.d6: Chip(Chip.Color.black), Coordinate.f6: Chip(Chip.Color.black), Coordinate.h6: Chip(Chip.Color.black), Coordinate.a7: Chip(Chip.Color.black), Coordinate.c7: Chip(Chip.Color.black), Coordinate.e7: Chip(Chip.Color.black), Coordinate.g7: Chip(Chip.Color.black), Coordinate.b8: Chip(Chip.Color.black), Coordinate.d8: Chip(Chip.Color.black), Coordinate.f8: Chip(Chip.Color.black), Coordinate.h8: Chip(Chip.Color.black)} for k in self.chips.keys(): self.board.set_content(k,self.chips[k]) self.turn = Chip.Color.white self._current_chip = None def _neighbor_in_direction(self, square, direction): neighborSquare = self.board.neighbor_in_direction(square, direction) return neighborSquare def _next_neighbor_in_direction(self, square, direction): neighbor_square = self.board.neighbor_in_direction(square, direction) if neighbor_square is not None: # check the next new_neighbor = \ self.board.neighbor_in_direction(neighbor_square, direction) if new_neighbor is not None: return new_neighbor return None def _enemy_in_neighbor(self, square, direction): neighbor = self._neighbor_in_direction(square, direction) return neighbor is not None and \ self.board.get_content(neighbor) is not None and \ self.board.get_content(neighbor).color != self.turn def _directions_for_soldier(self): white_directions = [Direction.top_left, Direction.top_right] black_directions = [Direction.btm_left, Direction.btm_right] return white_directions \ if self.turn == Chip.Color.white \ else black_directions def _soldier_available_jumps(self, square): jumps = set() for direction in self._directions_for_soldier(): if self._enemy_in_neighbor(square, direction): next_neighbor = \ self._next_neighbor_in_direction(square, direction) if next_neighbor is not None and \ self.board.get_content(next_neighbor) is None: jumps.add((square, next_neighbor)) return jumps def _soldier_available_regular_moves(self, square): moves = set() for direction in self._directions_for_soldier(): neighbor = self._neighbor_in_direction(square, direction) if neighbor is not None and \ self.board.get_content(neighbor) is None: # empty square, valid move moves.add((square, neighbor)) return moves def _soldier_can_jump(self, square): return bool(self._soldier_available_jumps(square)) def _soldier_chip_available_moves(self, square): moves = self._soldier_available_jumps(square) if len(moves) > 0: return moves, True return self._soldier_available_regular_moves(square), False def _queen_rival_found_moves(self, origin, square, direction, moves, can_jump): my_moves = moves neighbor = self._neighbor_in_direction(square, direction) if neighbor is not None: content = self.board.get_content(neighbor) if content is None and can_jump: # another empty square after a jump my_moves.add((origin, neighbor)) return my_moves, True elif content is None and not can_jump: # just found out queen can jump my_moves = set([(origin, neighbor)]) return my_moves, True return moves, can_jump # two chips in a row or out of bounds def _queen_moves_in_direction(self, square, direction): moves, can_jump = set(), False neighbor = self._neighbor_in_direction(square, direction) while neighbor is not None: content = self.board.get_content(neighbor) if content is None: # empty moves.add((square, neighbor)) elif content.color != self. turn: # rival # rival chip found old_moves = moves moves, can_jump = self._queen_rival_found_moves(square, neighbor, direction, moves, can_jump) neighbor = self._neighbor_in_direction(neighbor, direction) if moves == old_moves: break # two chips in a row or out of bounds else: break # ally chip found neighbor = self._neighbor_in_direction(neighbor, direction) return moves, can_jump def _queen_can_jump(self, square): moves, can_jump = self._queen_chip_available_moves(square) return can_jump def _queen_chip_available_moves(self, square): directions = [Direction.top_left, Direction.top_right, Direction.btm_left, Direction.btm_right] moves, can_jump = set(), False for d in directions: new_moves, new_can_jump = self._queen_moves_in_direction(square, d) if can_jump == new_can_jump: moves = moves | new_moves elif not can_jump and new_can_jump: moves = new_moves can_jump = True return moves, can_jump def _chip_can_jump(self, square): if square in self.chips: if self.chips[square].type == Chip.Type.soldier: return self._soldier_can_jump(square) else: return self._queen_can_jump(square) return False def chip_available_moves(self, square): """Return a tuple (set[available_moves], bool can_jump) Args: square (Coordinate): the square where the chip is/should be Returns: set: tuple of Coordinate values of valid moves for the chip. They have the form (Coordinate.origin, Coordinate.destination) bool: True if the chip can jump, False otherwise """ if not isinstance(square, Coordinate): raise TypeError("square variable must be from Coordinate enum") if square not in self.chips.keys() or \ self.board.get_content(square) is None: # chip is not in the game anymore return set(), False chip = self.chips[square] if chip.color != self.turn: return set(), False if chip.type == Chip.Type.soldier: return self._soldier_chip_available_moves(square) return self._queen_chip_available_moves(square) def available_moves(self): """Return a set with tuples of Coordinate values of all available moves Returns: set: tuple of Coordinate values of valid moves for the chip. They have the form (Coordinate.origin, Coordinate.destination) """ moves = set() if self._current_chip is not None: moves, can_jump = self.chip_available_moves(self._current_chip) return moves can_jump = False for coord, chip in self.chips.items(): newMoves, newcan_jump = self.chip_available_moves(coord) if can_jump == newcan_jump: moves = moves | newMoves elif not can_jump and newcan_jump: # found a jump, delete old moves moves = newMoves can_jump = True # else found regular move, but jump found previously return moves def _promote(self, square): startIndex = 0 if self.turn == Chip.Color.white else 7 promo_squares = [] for i in range(startIndex, 64, 8): promo_squares.append(Coordinate(i)) if square in promo_squares: self.chips[square].promote() def _next_turn(self): self.turn = Chip.Color.black \ if self.turn == Chip.Color.white \ else Chip.Color.white def _gamestate(self): if len(self.available_moves()) == 0: return self.Gamestate.whiteWon \ if self.turn == Chip.Color.black \ else self.Gamestate.blackWon return self.Gamestate.inProgress def _remove_chips(self, origin, destination): removed = [] direction = self._direction_of_move(origin, destination) squares_jumped = self.board.path_in_direction(origin, destination, direction) for s in squares_jumped: if self.board.get_content(s) != None: self.board.clear_square(s) del self.chips[s] removed.append(s) return removed def _direction_of_move(self, origin, destination): distance = destination - origin direction = None if distance < 0: # moved left if distance % 7 == 0: # moved top direction = Direction.top_left else: # distance % 9 == 0, moved btm direction = Direction.btm_left else: # moved right if distance % 9 == 0: direction = Direction.top_right else: direction = Direction.btm_right return direction def move(self, origin, destination): """Perform the requested move and returns a tuple (Gamestate, list) Args: origin (Coordinate): the square where the chip is currently destination (Direction): the square where the chip will end Returns: Gamestate: value from enum list: Coordinate values indicating the chip(s) removed Raises: TypeError: if origin or destination is not Coordinate """ if not isinstance(origin, Coordinate): raise TypeError("origin variable must be from Coordinate enum") if not isinstance(destination, Coordinate): raise TypeError("destination must be from Coordinate enum") if not (origin, destination) in self.available_moves(): return self.Gamestate.invalidMove, [] turnFinished = True _, jumped = self.chip_available_moves(origin) # move chip self.board.move(origin, destination) self.chips[destination] = self.chips[origin] del self.chips[origin] self._promote(destination) # remove chips if jump occured distance = destination - origin removed = [] if jumped: removed = self._remove_chips(origin, destination) if self._chip_can_jump(destination): turnFinished = False self._current_chip = destination if turnFinished: self._next_turn() self._current_chip = None self._promote(destination) return (self._gamestate(), removed) def square_contains_teammate(self, square): """Returns True if the chip belongs to the team whose turn it is Args: square (Coordinate): the square to check for an ally chip Returns: bool: True if the chip belongs to the team whose turn it is Raises: TypeError: if square is not Coordinate """ if not isinstance(square, Coordinate): raise TypeError("square variable must be from Coordinate enum") # Python's lazy evaluation makes sure this expression will never # throw KeyError because if the key is not in the dictionary, the # second expression will not be evaluated return square in self.chips.keys() and \ self.chips[square].color == self.turn
class TestBoard(unittest.TestCase): def setUp(self): self.board = Gameboard() def test_64_squares(self): self.assertEqual(len(self.board._squares),64) def test_index_raises_TypeError(self): self.assertRaises(TypeError,self.board._indexOf,"notCoordinate") def test_index_of_coordinate_corner(self): index = self.board._indexOf(Coordinate.a1) self.assertEqual(index,(7,0)) def test_index_of_coordinate_top(self): index = self.board._indexOf(Coordinate.c8) self.assertEqual(index,(0,2)) def test_index_of_coordinate_right(self): index = self.board._indexOf(Coordinate.h7) self.assertEqual(index,(1,7)) def test_index_of_coordinate_bottom(self): index = self.board._indexOf(Coordinate.d1) self.assertEqual(index,(7,3)) def test_index_of_coordinate_left(self): index = self.board._indexOf(Coordinate.a5) self.assertEqual(index,(3,0)) def test_index_of_coordinate_center(self): index = self.board._indexOf(Coordinate.d3) self.assertEqual(index,(5,3)) def test_neighbor_top_with_top_square(self): n = self.board._neighbor_top(Coordinate.a8) self.assertEqual(n, None) def test_neighbor_top(self): n = self.board._neighbor_top(Coordinate.d3) self.assertEqual(n, Coordinate.d4) def test_neighbor_top_right_with_top_square(self): n = self.board._neighbor_top_right(Coordinate.b8) self.assertEqual(n, None) def test_neighbor_top_right_with_right_square(self): n = self.board._neighbor_top_right(Coordinate.h5) self.assertEqual(n, None) def test_neighbor_top_right(self): n = self.board._neighbor_top_right(Coordinate.f3) self.assertEqual(n, Coordinate.g4) def test_neighbor_right_with_right_square(self): n = self.board._neighbor_right(Coordinate.h5) self.assertEqual(n, None) def test_neighbor_right(self): n = self.board._neighbor_right(Coordinate.d3) self.assertEqual(n, Coordinate.e3) def test_neighbor_btm_right_with_btm_square(self): n = self.board._neighbor_btm_right(Coordinate.b1) self.assertEqual(n, None) def test_neighbor_btm_right_with_right_square(self): n = self.board._neighbor_btm_right(Coordinate.h5) self.assertEqual(n, None) def test_neighbor_btm_right(self): n = self.board._neighbor_btm_right(Coordinate.f3) self.assertEqual(n, Coordinate.g2) def test_neighbor_btm_with_btm_square(self): n = self.board._neighbor_btm(Coordinate.a1) self.assertEqual(n, None) def test_neighbor_btm(self): n = self.board._neighbor_btm(Coordinate.d3) self.assertEqual(n, Coordinate.d2) def test_neighbor_btm_left_with_btm_square(self): n = self.board._neighbor_btm_left(Coordinate.b1) self.assertEqual(n, None) def test_neighbor_btm_left_with_left_square(self): n = self.board._neighbor_btm_left(Coordinate.a6) self.assertEqual(n, None) def test_neighbor_btm_left(self): n = self.board._neighbor_btm_left(Coordinate.f3) self.assertEqual(n, Coordinate.e2) def test_neighbor_left_with_left_square(self): n = self.board._neighbor_left(Coordinate.a5) self.assertEqual(n, None) def test_neighbor_left(self): n = self.board._neighbor_left(Coordinate.d3) self.assertEqual(n, Coordinate.c3) def test_neighbor_top_left_with_top_square(self): n = self.board._neighbor_top_left(Coordinate.b8) self.assertEqual(n, None) def test_neighbor_top_left_with_left_square(self): n = self.board._neighbor_top_left(Coordinate.a5) self.assertEqual(n, None) def test_neighbor_top_left(self): n = self.board._neighbor_top_left(Coordinate.f3) self.assertEqual(n, Coordinate.e4) def test_neighbor_in_direction_raises_TypeError(self): self.assertRaises(TypeError, self.board.neighbor_in_direction, square = "notCoordinate", direction = Direction.top) self.assertRaises(TypeError, self.board.neighbor_in_direction, square = Coordinate.a1, direction = "notDirection") def test_neighbor_in_direction_top(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.top) self.assertEqual(n, Coordinate.d4) def test_neighbor_in_direction_top_right(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.top_right) self.assertEqual(n, Coordinate.e4) def test_neighbor_in_direction_right(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.right) self.assertEqual(n, Coordinate.e3) def test_neighbor_in_direction_btm_right(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.btm_right) self.assertEqual(n, Coordinate.e2) def test_neighbor_in_direction_btm(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.btm) self.assertEqual(n, Coordinate.d2) def test_neighbor_in_directino_btm_left(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.btm_left) self.assertEqual(n, Coordinate.c2) def test_neighbor_in_direction_left(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.left) self.assertEqual(n, Coordinate.c3) def test_neighbor_in_direction_top_left(self): n = self.board.neighbor_in_direction(Coordinate.d3, Direction.top_left) self.assertEqual(n, Coordinate.c4) def test_neighbors_raises_TypeError(self): self.assertRaises(TypeError,self.board.neighbors,"notCoordinate") def test_neighbors_corner(self): n = self.board.neighbors(Coordinate.a1) correct = {Direction.top: Coordinate.a2, Direction.top_right: Coordinate.b2, Direction.right: Coordinate.b1, Direction.btm_right: None, Direction.btm: None, Direction.btm_left: None, Direction.left: None, Direction.top_left: None} self.assertEqual(n, correct) def test_neighbors_top(self): n = self.board.neighbors(Coordinate.c8) correct = {Direction.top: None, Direction.top_right: None, Direction.right: Coordinate.d8, Direction.btm_right: Coordinate.d7, Direction.btm: Coordinate.c7, Direction.btm_left: Coordinate.b7, Direction.left: Coordinate.b8, Direction.top_left: None} self.assertEqual(n, correct) def test_neighbors_right(self): n = self.board.neighbors(Coordinate.h7) correct = {Direction.top: Coordinate.h8, Direction.top_right: None, Direction.right: None, Direction.btm_right: None, Direction.btm: Coordinate.h6, Direction.btm_left: Coordinate.g6, Direction.left: Coordinate.g7, Direction.top_left: Coordinate.g8} self.assertEqual(n, correct) def test_neighbors_bottom(self): n = self.board.neighbors(Coordinate.d1) correct = {Direction.top: Coordinate.d2, Direction.top_right: Coordinate.e2, Direction.right: Coordinate.e1, Direction.btm_right: None, Direction.btm: None, Direction.btm_left: None, Direction.left: Coordinate.c1, Direction.top_left: Coordinate.c2} self.assertEqual(n, correct) def test_neighbors_left(self): n = self.board.neighbors(Coordinate.a5) correct = {Direction.top: Coordinate.a6, Direction.top_right: Coordinate.b6, Direction.right: Coordinate.b5, Direction.btm_right: Coordinate.b4, Direction.btm: Coordinate.a4, Direction.btm_left: None, Direction.left: None, Direction.top_left: None} self.assertEqual(n, correct) def test_neighbors_center(self): n = self.board.neighbors(Coordinate.d3) correct = {Direction.top: Coordinate.d4, Direction.top_right: Coordinate.e4, Direction.right: Coordinate.e3, Direction.btm_right: Coordinate.e2, Direction.btm: Coordinate.d2, Direction.btm_left: Coordinate.c2, Direction.left: Coordinate.c3, Direction.top_left: Coordinate.c4} self.assertEqual(n, correct) def test_squares_in_direction_raises_TypeError(self): self.assertRaises(TypeError,self.board.squares_in_direction, origin = "notCoordinate", direction = Direction.top) self.assertRaises(TypeError, self.board.squares_in_direction, origin = Coordinate.a1, direction = "notDirection") def test_squares_in_direction_center_to_edge(self): s = self.board.squares_in_direction(Coordinate.d4, Direction.top) correct = [Coordinate.d5, Coordinate.d6, Coordinate.d7, Coordinate.d8] self.assertEqual(s, correct) def test_squares_in_direction_along_edge(self): s = self.board.squares_in_direction(Coordinate.a5, Direction.btm) correct = [Coordinate.a4, Coordinate.a3, Coordinate.a2, Coordinate.a1] self.assertEqual(s, correct) def test_squares_in_direction_edge_to_outside(self): s = self.board.squares_in_direction(Coordinate.a1, Direction.left) self.assertEqual(s, []) def test_squares_in_direction_with_non_empty_square_ignored(self): self.board.set_content(Coordinate.g5, "boo") s = self.board.squares_in_direction(Coordinate.a5, Direction.right) correct = [Coordinate.b5, Coordinate.c5, Coordinate.d5, Coordinate.e5, Coordinate.f5] self.assertEqual(s, correct) def test_squares_in_direction_with_non_empty_square_included(self): self.board.set_content(Coordinate.g5, "boo") s = self.board.squares_in_direction(Coordinate.d2, Direction.top_right, include_last_non_empty_square = True) correct = [Coordinate.e3, Coordinate.f4, Coordinate.g5] self.assertEqual(s, correct) def test_path_in_direction_raises_TypeError(self): self.assertRaises(TypeError, self.board.path_in_direction, origin = "notCoordinate", destination = Coordinate.a3, direction = Direction.top) self.assertRaises(TypeError, self.board.path_in_direction, origin = Coordinate.a1, destination = "notCoordinate", direction = Direction.btm) self.assertRaises(TypeError, self.board.path_in_direction, origin = Coordinate.a1, destination = Coordinate.b4, direction = "notDirection") def test_path_in_direction_top(self): path = self.board.path_in_direction(Coordinate.d3, Coordinate.d7, \ Direction.top) correctPath = [Coordinate.d4, Coordinate.d5, Coordinate.d6] self.assertEqual(path, correctPath) def test_path_in_direction_top_right(self): path = self.board.path_in_direction(Coordinate.a1, Coordinate.h8, \ Direction.top_right) correctPath = [Coordinate.b2, Coordinate.c3, Coordinate.d4, Coordinate.e5, Coordinate.f6, Coordinate.g7] self.assertEqual(path, correctPath) def test_path_in_direction_unreachable(self): path = self.board.path_in_direction(Coordinate.a8, Coordinate.h8, \ Direction.btm) correctPath = [] self.assertEqual(path, correctPath) def test_path_in_direction_out_of_bounds(self): path = self.board.path_in_direction(Coordinate.a8, Coordinate.a1, \ Direction.left) correctPath = [] self.assertEqual(path, correctPath) def test_square_content_raises_TypeError(self): self.assertRaises(TypeError,self.board.get_content,"notCoordinate") def test_square_content(self): self.board.set_content(Coordinate.a3, "white piece") self.assertEqual(self.board.get_content(Coordinate.a3), "white piece") def test_is_square_emtpy_raises_TypeError(self): self.assertRaises(TypeError,self.board.is_empty,"notCoordinate") def test_is_square_empty_true(self): self.assertTrue(self.board.is_empty(Coordinate.d2)) def test_is_square_empty_false(self): self.board.set_content(Coordinate.e3, 2) self.assertFalse(self.board.is_empty(Coordinate.e3)) def test_clear_square_raises_TypeError(self): self.assertRaises(TypeError,self.board.clear_square,"notCoordinate") def test_clear_square(self): self.board.set_content(Coordinate.h4, [1,2]) self.board.clear_square(Coordinate.h4) self.assertTrue(self.board.is_empty(Coordinate.h4)) def test_clear_board(self): for i in range(10): square = Coordinate(random.randrange(1,64)) self.board.set_content(square,"pawn") self.board.clear_board() for square in Coordinate: self.assertTrue(self.board.is_empty(square)) def test_move_content_raises_TypeError(self): self.assertRaises(TypeError,self.board.move, origin = Coordinate.a3, destination = "notCoordinate") self.assertRaises(TypeError,self.board.move, origin = "notCoordinate", destination = Coordinate.a3) def test_move_content(self): self.board.set_content(Coordinate.g8, "knight") self.board.move(Coordinate.g8, Coordinate.f6) self.assertEqual(self.board.get_content(Coordinate.f6),"knight")