def test_check_turn(self): rc = RuleChecker() empty_board = Board() player = Avatar("white") tile = HandTile([('s1', 'e2'), ('s2', 'w1'), ('e1', 'n2'), ('n1', 'w2')]) tile_2 = HandTile([('n1', 's2'), ('n2', 'e2'), ('e1', 'w2'), ('s1', 'w1')]) suicide_tile = HandTile([('n1', 'w1'), ('n2', 'e1'), ('s2', 'e2'), ('s1', 'w2')]) empty_board.first_turn(player, tile, "a1", "n2") self.assertAlmostEqual(player.current_port, "e1") self.assertAlmostEqual(player.current_tile, "a1") player.tiles = [tile_2, suicide_tile] # Tile has to be at the correct coordinate self.assertAlmostEqual( rc.check_turn(empty_board, tile_2, "a1", player)["legal"], False) self.assertAlmostEqual( rc.check_turn(empty_board, tile_2, "a2", player)["legal"], False) self.assertAlmostEqual( rc.check_turn(empty_board, tile_2, "b1", player)["legal"], True) # Tile cannot cause a suicide if there are other options self.assertAlmostEqual( rc.check_turn(empty_board, suicide_tile, "b1", player)["legal"], False) player.tiles = [suicide_tile] self.assertAlmostEqual( rc.check_turn(empty_board, suicide_tile, "b1", player)["legal"], True)
class Board: # Constructor def __init__(self): self.tiles = {} self.rule_checker = RuleChecker() # returns true if the given board coordinate has a tile on it def is_coordinate_occupied(self, board_coordinate): return board_coordinate in self.tiles.keys() # returns dictionary of north, south, east, west neighbors of the given square def get_board_coordinate_neighbors(self, board_coordinate): neighbors = {NORTH: None, SOUTH: None, WEST: None, EAST: None} for side in neighbors.keys(): neighbor = get_coordinate_neighbor(board_coordinate, side) if self.is_coordinate_occupied(neighbor): neighbor = self.tiles[neighbor] elif neighbor != EDGE: neighbor = None neighbors[side] = neighbor return neighbors # Converts hand_tile to a BoardTile and initializes its neighbors def convert_to_board_tile(self, hand_tile, board_coordinate): tile_neighbors = self.get_board_coordinate_neighbors(board_coordinate) board_tile = BoardTile(hand_tile.paths, tile_neighbors) return board_tile # hand_tile is a HandTile , board_coordinate is a string of 2 characters # in battleship coordiates -> 10x10 board is A-J 1-10 # returns the generated board tile def place_tile(self, hand_tile, board_coordinate): board_tile = self.convert_to_board_tile(hand_tile, board_coordinate) self.tiles[board_coordinate] = board_tile # Link the newly created board tile to any of its neighbors currently on the board sides = [NORTH, SOUTH, EAST, WEST] for side in sides: new_neighbor_coordinate = get_coordinate_neighbor( board_coordinate, side) if self.is_coordinate_occupied(new_neighbor_coordinate): new_neighbor = self.tiles[new_neighbor_coordinate] new_neighbor.set_neighbor(get_opposite_side(side), board_tile) return board_tile # given a board coordinate and a starting port on the tile at that location # return the end board coordinate and port of that path on this board # or EDGE if they fall off the board # board_coordinate must have a tile on it (be in board.tiles) def end_of_path(self, board_coordinate, starting_port): board_tile = self.tiles[board_coordinate] end_port = board_tile.end_of_path(starting_port) neighboring_side = port_to_side(end_port) neighbor_to_move_to = board_tile.neighbors[neighboring_side] if neighbor_to_move_to == EDGE: return {TILE: EDGE, PORT: EDGE} elif neighbor_to_move_to == None: return {TILE: board_coordinate, PORT: end_port} else: new_tile_location = get_coordinate_neighbor( board_coordinate, neighboring_side) starting_port_on_new_tile = get_connecting_port(end_port) return self.end_of_path(new_tile_location, starting_port_on_new_tile) # moves given player to given board_coordinate and moves them along the path # throws exception if player cannot be moved (if there is no tile at the connecting side) def move_player(self, player): starting_port = player.current_port new_coordiate = get_connecting_coordinate(player.current_tile, starting_port) if self.is_coordinate_occupied(new_coordiate): port_on_new_tile = get_connecting_port(starting_port) ending_location = self.end_of_path(new_coordiate, port_on_new_tile) player.current_tile = ending_location[TILE] player.current_port = ending_location[PORT] else: logging.info("Player cannot be moved, no tile at: %s", new_coordiate) sys.exit(1) # places a player and a handtile on the board at the given coordinate and port # this is part of the players intial turn def first_turn(self, player, hand_tile, board_coordinate, starting_port): # Rule Checker : sees if tile location is valid first_turn_check = self.rule_checker.check_first_turn( self, hand_tile, board_coordinate, starting_port)[LEGAL] if first_turn_check: board_tile = self.place_tile(hand_tile, board_coordinate) end_port = board_tile.end_of_path(starting_port) player.place_avatar(board_coordinate, end_port) else: logging.info("Invalid initial tile placement: %s", board_coordinate) sys.exit(1) def take_turn(self, hand_tile, board_coordinate, player): # Rule Checker : sees if turn is valid turn_check = self.rule_checker.check_turn(self, hand_tile, board_coordinate, player)[LEGAL] if turn_check: self.place_tile(hand_tile, board_coordinate) self.move_player(player) else: logging.info("Invalid turn: %s", board_coordinate) sys.exit(1) def get_port_coordinate(self, port): return (0, 0)
class GameState: def __init__(self): self.deck = Deck() self.board = Board() self.player_dictionary = {} self.rule_checker = RuleChecker() self.dead_players = [] #################### Public methods ################################### # checks if an initial tile placement is legal # tile_index -> int : 0 - 34 (represents the index in all_tiles) # rotation -> int : 0, 90, 180, 270 (represents the number of roations for the given tile) # boar_coordinate -> string : valid_cooridinate() (coordinate the tile is placed at) # starting_port -> string : valid_port() (port on the edge of the board the player starts at) def initial_placement_check(self, tile_index, rotation, board_coordinate, starting_port): tile = self.deck.get_tile(tile_index) number_of_rotations = get_number_of_rotations(rotation) tile.rotate(number_of_rotations) return self.rule_checker.check_first_turn(self.board, tile, board_coordinate, starting_port) # performs a players first turn, returns the validity of that turn (if not valid kills player) # player_color -> string : valid_color() (the color of the player making the turn) # tile_index -> int : 0 - 34 (represents the index in all_tiles) # rotation -> int : 0, 90, 180, 270 (represents the number of roations for the given tile) # boar_coordinate -> string : valid_cooridinate() (coordinate the tile is placed at) # starting_port -> string : valid_port() (port on the edge of the board the player starts at) def player_first_turn(self, player_color, tile_index, rotation, board_coordinate, starting_port): new_player = Avatar(player_color) self.player_dictionary[player_color] = new_player first_turn_rule_check = self.initial_placement_check( tile_index, rotation, board_coordinate, starting_port) if first_turn_rule_check[LEGAL]: tile = self.deck.get_tile(tile_index) number_of_rotations = get_number_of_rotations(rotation) tile.rotate(number_of_rotations) self.board.first_turn(new_player, tile, board_coordinate, starting_port) else: self.dead_players.append(player_color) return first_turn_rule_check # checks if an players turn is legal # player_color -> string : valid_color() (the color of the player making the turn) # tile_index -> int : 0 - 34 (represents the index in all_tiles) # rotation -> int : 0 - 3 (represents the number of roations for the given tile) # boar_coordinate -> string : valid_cooridinate() (coordinate the tile is placed at) # tile_choices -> array[int] : 2 - 3 (tile options the player had to choose from) def rule_check(self, player_color, tile_index, rotation, board_coordinate, tile_choices): player = self.player_dictionary[player_color] players_tiles = self.deck.get_tiles(tile_choices) player.tiles = players_tiles tile = self.deck.get_tile(tile_index) number_of_rotations = get_number_of_rotations(rotation) tile.rotate(number_of_rotations) return self.rule_checker.check_turn(self.board, tile, board_coordinate, player) # performs a players turn, returns the result or validity of that turn (if not valid kills player) # player_color -> string : valid_color() (the color of the player making the turn) # tile_index -> int : 0 - 34 (represents the index in all_tiles) # rotation -> int : 0 - 3 (represents the number of roations for the given tile) # boar_coordinate -> string : valid_cooridinate() (coordinate the tile is placed at) # tile_choices -> array[int] : 2 - 3 (tile options the player had to choose from) def player_take_turn(self, player_color, tile_index, rotation, board_coordinate, tile_choices): rule_check = self.rule_check(player_color, tile_index, rotation, board_coordinate, tile_choices) if rule_check[LEGAL]: player = self.player_dictionary[player_color] tile = self.deck.get_tile(tile_index) number_of_rotations = get_number_of_rotations(rotation) tile.rotate(number_of_rotations) self.board.take_turn(tile, board_coordinate, player) players_killed = self.update_other_players_positions( player, board_coordinate) result = self.get_turn_results(players_killed) self.dead_players.extend(players_killed) return result else: self.dead_players.append(player_color) return rule_check #################### Private methods ####################### # returns a set of the remaining living player colors def get_living_players(self): players_set = set(self.player_dictionary.keys()) dead_players_set = set(self.dead_players) living_players_set = players_set.difference(dead_players_set) return living_players_set # given a list of player colors move those players # expectation that these players are all moveable otherwise # throws an exception def move_players(self, players_to_move): for player_color in players_to_move: player = self.player_dictionary[player_color] self.board.move_player(player) # updates the positions of the other players still on the board # given the current player and where they placed the tile on their turn # returns the players killed in this turn def update_other_players_positions(self, current_player, board_coordinate): players_killed = [] if current_player.is_player_dead(): players_killed.append(current_player.color) living_players = self.get_living_players() living_players.discard(current_player.color) players_to_move = [] for player_color in living_players: player = self.player_dictionary[player_color] if player.get_connecting_coordinate() == board_coordinate: players_to_move.append(player.color) self.move_players(players_to_move) for player_color in players_to_move: if self.player_dictionary[player_color].is_player_dead(): players_killed.append(player_color) return players_killed # given the players killed during a turn return the results of a legal move def get_turn_results(self, players_killed): result = {LEGAL: True, PLAYERS_KILLED: [], GAME_OVER: False} is_game_over = len(self.player_dictionary) - len(self.dead_players) < 2 is_tie = len(self.player_dictionary) == len(self.dead_players) if is_game_over: result[GAME_OVER] = True if is_tie: result[WINNERS] = players_killed result[LOSERS] = self.dead_players else: result[WINNERS] = self.get_living_players() result[LOSERS] = self.dead_players else: self.dead_players.extend(players_killed) result[PLAYERS_KILLED] = players_killed return result