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)
Exemple #2
0
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