Exemple #1
0
def test_get_spiral():
    # For radius of 3
    expected = []

    r = 2
    expected.append((r - 0) * hx.W + 0 * hx.NW)
    expected.append((r - 1) * hx.W + 1 * hx.NW)

    expected.append((r - 0) * hx.NW + 0 * hx.NE)
    expected.append((r - 1) * hx.NW + 1 * hx.NE)

    expected.append((r - 0) * hx.NE + 0 * hx.E)
    expected.append((r - 1) * hx.NE + 1 * hx.E)

    expected.append((r - 0) * hx.E + 0 * hx.SE)
    expected.append((r - 1) * hx.E + 1 * hx.SE)

    expected.append((r - 0) * hx.SE + 0 * hx.SW)
    expected.append((r - 1) * hx.SE + 1 * hx.SW)

    expected.append((r - 0) * hx.SW + 0 * hx.W)
    expected.append((r - 1) * hx.SW + 1 * hx.W)

    r += 1
    expected.append((r - 0) * hx.W + 0 * hx.NW)
    expected.append((r - 1) * hx.W + 1 * hx.NW)
    expected.append((r - 2) * hx.W + 2 * hx.NW)

    expected.append((r - 0) * hx.NW + 0 * hx.NE)
    expected.append((r - 1) * hx.NW + 1 * hx.NE)
    expected.append((r - 2) * hx.NW + 2 * hx.NE)

    expected.append((r - 0) * hx.NE + 0 * hx.E)
    expected.append((r - 1) * hx.NE + 1 * hx.E)
    expected.append((r - 2) * hx.NE + 2 * hx.E)

    expected.append((r - 0) * hx.E + 0 * hx.SE)
    expected.append((r - 1) * hx.E + 1 * hx.SE)
    expected.append((r - 2) * hx.E + 2 * hx.SE)

    expected.append((r - 0) * hx.SE + 0 * hx.SW)
    expected.append((r - 1) * hx.SE + 1 * hx.SW)
    expected.append((r - 2) * hx.SE + 2 * hx.SW)

    expected.append((r - 0) * hx.SW + 0 * hx.W)
    expected.append((r - 1) * hx.SW + 1 * hx.W)
    expected.append((r - 2) * hx.SW + 2 * hx.W)

    assert(np.array_equal(
        hx.get_spiral([0, 0, 0], 2, 3),
        np.array(expected)))
Exemple #2
0
    def __init__(self,
                 size=(600, 600),
                 hex_radius=22,
                 caption="ExampleHexMap"):
        self.caption = caption
        self.size = np.array(size)
        self.width, self.height = self.size
        self.center = self.size / 2

        self.hex_radius = hex_radius
        self.hex_apothem = hex_radius * np.sqrt(3) / 2
        self.hex_offset = np.array(
            [self.hex_radius * np.sqrt(3) / 2, self.hex_radius])

        self.hex_map = hx.HexMap()
        self.max_coord = 6

        self.rad = 3

        self.selected_hex_image = make_hex_surface((128, 128, 128, 160),
                                                   self.hex_radius,
                                                   (255, 255, 255),
                                                   hollow=True)

        self.selection_type = 3
        self.clicked_hex = np.array([0, 0, 0])

        # Get all possible coordinates within `self.max_coord` as radius.
        coords = hx.get_spiral(np.array((0, 0, 0)), 1, self.max_coord)

        # Convert coords to axial coordinates, create hexes and randomly filter out some hexes.
        hexes = []
        num_hexes = np.random.binomial(len(coords), .9)
        axial_coords = hx.cube_to_axial(coords)
        axial_coords = axial_coords[np.random.choice(len(axial_coords),
                                                     num_hexes,
                                                     replace=False)]

        for i, axial in enumerate(axial_coords):
            colo = list(COLORS[COL_IDX[i]])
            colo.append(255)
            hexes.append(ExampleHex(axial, colo, hex_radius))
            hexes[-1].set_value(i)  # the number at the center of the hex

        self.hex_map[np.array(axial_coords)] = hexes

        self.main_surf = None
        self.font = None
        self.clock = None
        self.init_pg()
    def get_selection(selection_type, max_range, hex_radius):
        hex_map = hx.HexMap()
        hexes = []
        axial_coordinates = []

        if selection_type == Selection.Type.RECT:
            for r in range(-max_range, max_range + 1):
                r_offset = r >> 1
                for q in range(-max_range - r_offset, max_range - r_offset):
                    c = [q, r]
                    axial_coordinates.append(c)
                    hexes.append(ExampleHex(c, [141, 207, 104, 255], hex_radius))      
  
        elif selection_type == Selection.Type.HEX:
            spiral_coordinates = hx.get_spiral(np.array((0, 0, 0)), 1, max_range) # Gets spiral coordinates from center
            axial_coordinates = hx.cube_to_axial(spiral_coordinates)

            for i, axial in enumerate(axial_coordinates):
                hex_color = list(COLORS[3]) # set color of hex
                hex_color.append(255) #set alpha to 255
                hexes.append(ExampleHex(axial, hex_color, hex_radius)) 

        elif selection_type == Selection.Type.TRIANGLE:
            top = int(max_range / 2)
            for q in range(0, max_range + 1):
                for r in range(0, max_range - q + 1):
                    c = [q, r]
                    axial_coordinates.append(c)
                    hexes.append(ExampleHex(c, [141, 207, 104, 255], hex_radius))

               

        elif selection_type == Selection.Type.RHOMBUS:
            q1 = int(-max_range / 2)
            q2 = int(max_range / 2)
            r1 = -max_range
            r2 = max_range

            # Parallelogram
            for q in range(q1, q2 + 1):
                for r in range(r1, r2 + 1):
                    axial_coordinates.append([q, r])
                    hexes.append(ExampleHex([q, r], [141, 207, 104, 255], hex_radius)) 

        elif selection_type == Selection.Type.CUSTOM:
            axial_coordinates.append([0, 0])
            hexes.append(ExampleHex([0, 0], [141, 207, 104, 255], hex_radius))

        hex_map[np.array(axial_coordinates)] = hexes # create hex map  
        return hex_map
Exemple #4
0
    def __init__(self,
                 size=(600, 600),
                 hex_radius=22,
                 caption="ExampleHexMap"):
        self.caption = caption
        self.size = np.array(size)
        self.width, self.height = self.size
        self.center = self.size / 2

        self.hex_radius = hex_radius

        self.hex_map = hx.HexMap()
        self.max_coord = 6

        self.rad = ClampedInteger(3, 1, 5)

        self.selected_hex_image = make_hex_surface((128, 128, 128, 160),
                                                   self.hex_radius,
                                                   (255, 255, 255),
                                                   hollow=True)

        self.selection_type = CyclicInteger(3, 0, 3)
        self.clicked_hex = np.array([0, 0, 0])

        # Get all possible coordinates within `self.max_coord` as radius.
        spiral_coordinates = hx.get_spiral(np.array((0, 0, 0)), 1,
                                           self.max_coord)

        # Convert `spiral_coordinates` to axial coordinates, create hexes and randomly filter out some hexes.
        hexes = []
        num_shown_hexes = np.random.binomial(len(spiral_coordinates), .9)
        axial_coordinates = hx.cube_to_axial(spiral_coordinates)
        axial_coordinates = axial_coordinates[np.random.choice(
            len(axial_coordinates), num_shown_hexes, replace=False)]

        for i, axial in enumerate(axial_coordinates):
            hex_color = list(COLORS[COL_IDX[i]])
            hex_color.append(255)
            hexes.append(ExampleHex(axial, hex_color, hex_radius))
            hexes[-1].set_value(i)  # the number at the center of the hex

        self.hex_map[np.array(axial_coordinates)] = hexes

        # pygame specific variables
        self.main_surf = None
        self.font = None
        self.clock = None
        self.init_pg()
Exemple #5
0
class Board:
    tile_coords = hx.get_spiral(np.array((0, 0, 0)), 1, BOARD_RADIUS)
    x_tiles = [i for i in tile_coords if i[0] == 0]
    y_tiles = [i for i in tile_coords if i[1] == 0]
    z_tiles = [i for i in tile_coords if i[2] == 0]
    suns = np.array([
        np.array([0, 1, -1]),  # N
        np.array([1, 0, -1]),  # NE
        np.array([1, -1, 0]),  # SE
        np.array([0, -1, 1]),  # S
        np.array([-1, 0, 1]),  # SW
        np.array([-1, 1, 0]),  # NW
    ])

    @time_function
    def __init__(self, players: List[Player]):
        self.players = players
        self.player_count = len(self.players)
        self.tuple_tile_coords = tuple(
            [tuple(coord) for coord in self.tile_coords])
        self.reset()
        # self.round_number = 0
        # self.sun_position = 5
        # self.pg_active = False
        # trees = self._get_initial_trees()
        # self.data = self._get_initial_data(trees)
        # self.tree_of_trees = self._get_tree_index(trees)

    def _get_initial_data(self, trees):
        Data = namedtuple("data", "tiles trees players tokens")
        data = Data(
            tiles=[
                Tile(tree=None,
                     coords=coord,
                     index=self.get_tile_index(tuple(coord)))
                for i, coord in enumerate(self.tile_coords)
            ],
            trees=np.array(trees),
            players=self.players,
            tokens=[
                Token(richness=key, value=value) for key in TOKENS
                for value in TOKENS[key]
            ],
        )
        return data

    def _get_initial_trees(self) -> List:
        trees = []
        for i, p in enumerate(self.players):
            for tree_type in TREES.keys():
                tree_count = len(trees)
                tree = TREES[tree_type]
                trees += [
                    Tree(
                        id=tree_count + j,
                        owner=i + 1,
                        size=tree["size"],
                        is_bought=True,
                        tree_type=tree_type,
                        score=tree["score"],
                    ) for j in range(tree["starting"])
                ]
                tree_count = len(trees)
                trees += [
                    Tree(
                        id=tree_count + j,
                        owner=i + 1,
                        size=tree["size"],
                        is_bought=False,
                        cost=cost,
                        tree_type=tree_type,
                        score=tree["score"],
                    ) for j, cost in enumerate(tree["cost"])
                ]
        return trees

    def _get_tree_index(self, trees):
        # index these separately so don't need to calculate many times
        tree_of_trees = {
            player.number: {
                "bought": {
                    tree.id: tree
                    for tree in trees if (tree.owner == player.number)
                    & (not tree.tile) & tree.is_bought
                },
                "in_shop": {
                    tree.id: tree
                    for tree in trees if (tree.owner == player.number)
                    & (not tree.tile) & (not tree.is_bought)
                },
                "on_board": {
                    tree.id: tree
                    for tree in trees
                    if (tree.owner == player.number) & (tree.tile is not None)
                },
            }
            for player in self.players
        }
        return tree_of_trees

    def reset(self):
        self.round_number = 0  # number of rounds
        self.round_turn = 0  # mid round turns - number between 0 and len(players) -1
        self.sun_position = 5
        self.pg_active = False
        trees = self._get_initial_trees()
        self.data = self._get_initial_data(trees)
        self.tree_of_trees = self._get_tree_index(trees)

    #########
    # UTILS #
    #########

    @classmethod
    @lru_cache(maxsize=None)
    @time_function
    def _get_tile_index_from_coords(cls, coords: Tuple[int]) -> int:
        mask = find_array_in_2D_array(np.array(coords), cls.tile_coords)
        index = np.where(mask)[0][0]
        return index

    @lru_cache(maxsize=None)
    @time_function
    def get_surrounding_tiles(self, coords: Tuple[int],
                              radius: int) -> Tuple[Tile]:
        surrounding_tile_coords = get_surrounding_coords(
            coords, radius, BOARD_RADIUS)
        surrounding_tiles = [
            self.data.tiles[self._get_tile_index_from_coords(coord)]
            for coord in surrounding_tile_coords
        ]
        return tuple(surrounding_tiles)

    @classmethod
    @lru_cache(maxsize=None)
    @time_function
    def get_tile_index(cls, coord: Tuple) -> int:
        mask = find_array_in_2D_array(np.array(coord), cls.tile_coords)
        return int(np.where(mask)[0])

    @time_function
    def get_empty_unlocked_tiles(self) -> List[Tile]:
        return [
            tile for tile in self.data.tiles
            if (not tile.tree) & (not tile.is_locked)
        ]

    @lru_cache(maxsize=None)
    @time_function
    def _get_tiles_at_sun_edge(self, sun: Tuple) -> List[Tile]:
        edge_coords = get_coords_at_sun_edge(tuple(sun),
                                             self.tuple_tile_coords)
        tiles = [
            self.data.tiles[self._get_tile_index_from_coords(tuple(coords))]
            for coords in edge_coords
        ]
        return tiles

    @lru_cache(maxsize=None)
    @time_function
    def _get_tiles_along_same_axis(self, start_tile_coords: Tuple[int],
                                   axis: Tuple[int]) -> Tuple[Tile]:
        tiles = [
            self.data.tiles[self._get_tile_index_from_coords(tuple(coords))]
            for coords in get_coords_along_same_axis(start_tile_coords, axis,
                                                     BOARD_RADIUS)
        ]
        return tuple(tiles)

    ######################
    # ROUND CALCULATIONS #
    ######################

    @time_function
    def end_round(self):
        if self.round_number in [0, 1]:
            self._set_shadows()
            self.round_number += 1
            return
        self.rotate_sun()
        self._set_shadows()
        for tile in self.data.tiles:
            tile.is_locked = False
            if (not tile.is_shadow) & (tile.tree is not None):
                player = [
                    player for player in self.data.players
                    if player.number == tile.tree.owner
                ][0]
                player.l_points += tile.tree.score
                player.l_points_earned_history[self.round_number].append(
                    tile.tree.score)

    @time_function
    def rotate_sun(self):
        self.sun_position = (self.sun_position + 1) % 6
        self.round_number += 1
        return

    @time_function
    def _set_shadows_along_axis(self, tile: Tile, axis: np.ndarray):
        axis_tiles = self._get_tiles_along_same_axis(tuple(tile.coords),
                                                     tuple(axis))
        current_shadow_size = 0
        for axis_tile in axis_tiles:
            axis_tile.is_shadow = True if current_shadow_size > 0 else False
            current_shadow_size = (current_shadow_size -
                                   1) if current_shadow_size > 0 else 0
            if not axis_tile.tree:
                continue
            current_shadow_size = max(
                [axis_tile.tree.shadow, current_shadow_size])

    @time_function
    def _set_shadows(self):
        sun = self.suns[self.sun_position]
        edge_tiles = self._get_tiles_at_sun_edge(tuple(sun))
        for tile in edge_tiles:
            self._set_shadows_along_axis(tile, sun)

    ##################
    # MOVE EXECUTION #
    ##################

    @time_function
    def get_next_token(self, richness: int) -> Token:
        # in two player game, richness 4 is removed fom the game
        if (self.player_count == 2) & (richness == 4):
            richness = 3

        while richness > 0:
            tokens = [
                token for token in self.data.tokens
                if (token.richness == richness) & (token.owner is None)
            ]
            if tokens:
                return tokens[0]
            richness -= 1
        raise ValueError("Ran out of tokens")

    @time_function
    def grow_tree(self, from_tree: Tree, to_tree: Tree, cost: int):
        # should wrap these into unit tests
        if not from_tree.tile:
            raise ValueError("This tree isn't on the board")
        if from_tree.size == 3:
            raise ValueError("This tree can't grow anymore!")
        if (to_tree.size - from_tree.size) != 1:
            raise ValueError(
                "The tree is trying to grow to a size that isn't one bigger!")
        if not to_tree.is_bought:
            raise ValueError("The tree hasn't been bought yet")
        if to_tree.tile:
            raise ValueError("The tree growing to is already on the board")
        tile = from_tree.tile
        to_tree.tile = tile
        from_tree.tile = None
        tile.tree = to_tree
        tile.is_locked = True
        player = [
            player for player in self.data.players
            if player.number == tile.tree.owner
        ][0]
        player.l_points -= cost

        # put back in shop
        num_trees_of_type_in_store = len([
            board_tree for board_tree in self.data.trees
            if (from_tree.size == board_tree.size)
            & (not board_tree.is_bought)
            & (board_tree.owner == from_tree.owner)
        ])
        # if shop is full remove tree from game
        if num_trees_of_type_in_store >= len(
                TREES[from_tree.tree_type]["cost"]):
            from_tree.is_deleted = True
        else:
            tree_cost = TREES[
                from_tree.tree_type]["cost"][num_trees_of_type_in_store]
            from_tree.cost = tree_cost
            from_tree.is_bought = False
            self.tree_of_trees[player.number]["in_shop"].update(
                {from_tree.id: from_tree})
        self.tree_of_trees[player.number]["on_board"].update(
            {to_tree.id: to_tree})
        self.tree_of_trees[player.number]["on_board"].pop(from_tree.id)
        self.tree_of_trees[player.number]["bought"].pop(to_tree.id)

    @time_function
    def plant_tree(self, tile: Tile, tree: Tree, from_tile: Tile, cost: int):
        if tile.tree:
            raise ValueError("There is already a tree here")
        if tree.tile:
            raise ValueError("This tree is already planted")
        if not tree.is_bought:
            raise ValueError("The tree hasn't been bought yet")
        tile.tree = tree
        tree.tile = tile
        self.data.tiles[tile.index].tree = tree
        tree.tile = tile
        tile.is_locked = True
        if from_tile:
            print(1)
            from_tile.is_locked = True
        player = [
            player for player in self.data.players
            if player.number == tile.tree.owner
        ][0]
        player.l_points -= cost
        self.tree_of_trees[player.number]["on_board"].update({tree.id: tree})
        self.tree_of_trees[player.number]["bought"].pop(tree.id)

    @time_function
    def collect_tree(self, tree: Tree, cost: int):
        if not tree.tile:
            raise ValueError("This tree isn't on the board")
        if tree.size != 3:
            raise ValueError("The tree isn't fully grown")
        tile = tree.tile
        tile.tree = None
        tree.tile = None

        tile.is_locked = True

        # put tree back in store
        num_trees_of_type_in_store = len([
            board_tree for board_tree in self.data.trees
            if (tree.size == board_tree.size) & (not board_tree.is_bought)
            & (board_tree.owner == tree.owner)
        ])
        tree_cost = TREES[tree.tree_type]["cost"][num_trees_of_type_in_store]
        tree.cost = tree_cost
        tree.is_bought = False

        player = [
            player for player in self.data.players
            if player.number == tree.owner
        ][0]
        player.l_points -= cost
        token = self.get_next_token(tile.richness)
        token.owner = tree.owner
        player.score += token.value
        player.score_earned_history[self.round_number].append(token.value)
        self.tree_of_trees[player.number]["in_shop"].update({tree.id: tree})
        self.tree_of_trees[player.number]["on_board"].pop(tree.id)

    @time_function
    def buy_tree(self, tree: Tree, cost: int):
        if tree.is_bought:
            raise ValueError("The tree has already been bought")
        tree.is_bought = True
        player = [
            player for player in self.data.players
            if player.number == tree.owner
        ][0]
        player.l_points -= cost
        tree.cost = 0
        self.tree_of_trees[player.number]["bought"].update({tree.id: tree})
        self.tree_of_trees[player.number]["in_shop"].pop(tree.id)

    @time_function
    def end_go(self, player_number):
        player = [
            player for player in self.data.players
            if player.number == player_number
        ][0]
        player.go_active = False
        st = self.round_turn
        if self.round_turn == len(self.players) - 1:
            self.end_round()
            self.round_turn = 0
        else:
            self.round_turn += 1

    ##################
    # VISUALISAIION #
    ##################

    def show(self):
        player_colors = {0: "green", 1: "red", 2: "blue"}
        colors = [
            player_colors[tile.tree.owner] if tile.tree else "green"
            for tile in self.data.tiles
        ]
        shadows = [
            "black" if tile.is_shadow else "orange" for tile in self.data.tiles
        ]
        labels = [
            tile.tree.size if tile.tree else "" for tile in self.data.tiles
        ]
        hcoord = [c[0] for c in self.tile_coords]
        vcoord = [
            2.0 * np.sin(np.radians(60)) * (c[1] - c[2]) / 3.0
            for c in self.tile_coords
        ]

        fig, ax = plt.subplots(1)
        fig.set_size_inches(12.5, 8.5)
        plt.axis([-5, 10, -5, 5])
        ax.set_aspect("equal")
        # Add some coloured hexagons
        for x, y, c, l in zip(hcoord, vcoord, colors, labels):
            hexagon = RegularPolygon(
                (x, y),
                numVertices=6,
                radius=2.0 / 3.0,
                orientation=np.radians(30),
                facecolor=c,
                alpha=0.2,
                edgecolor="k",
            )
            ax.add_patch(hexagon)
            # Add a text label
            ax.text(x, y + 0.3, l, ha="center", va="center", size=10)
        sun_coords = self.suns[self.sun_position] * 4
        sun_y_coord = 2 * np.sin(
            np.radians(60)) * (sun_coords[1] - sun_coords[2]) / 3
        ax.text(sun_coords[0],
                sun_y_coord,
                "SUN",
                ha="center",
                va="center",
                size=20,
                color="orange")

        for player in self.data.players:
            ax.text(
                7,
                2 - player.number / 2,
                f"Player {player.name}: Light Points: {player.l_points}, score: {player.score}",
                ha="center",
                va="center",
                size=10,
                color="black",
            )

        ax.text(
            7,
            4,
            f"Round number {self.round_number}.{self.round_turn}",
            ha="center",
            va="center",
            size=10,
            color="black",
        )

        # Add scatter points in hexagon centres
        ax.scatter(hcoord, vcoord, c=shadows, alpha=0.5)
        plt.axis("off")
        plt.show()
Exemple #6
0
import numpy as np
import hexy as hx

hm = hx.HexMap()
radius = 10
coords = hx.get_spiral(np.array((0, 0, 0)), 1, 10)


def test_hexes_amount_to_radius_conversion():
    hexes = 1 + 3 * radius * (radius + 1)
    found_radius = hx.radius_from_hexes(hexes)

    assert found_radius == radius


def test_axial_to_cube_conversion():
    axial_coords = hx.cube_to_axial(coords)
    cube_coords = hx.axial_to_cube(axial_coords)

    assert np.array_equal(coords, cube_coords)


def test_axial_to_pixel_conversion():
    axial_coords = hx.cube_to_axial(coords)
    pixel_coords = hx.axial_to_pixel(axial_coords, radius)
    pixel_to_axial_coords = hx.pixel_to_axial(pixel_coords, radius)

    assert np.array_equal(axial_coords, pixel_to_axial_coords)


def test_cube_to_pixel_conversion():