Skip to content
Snippets Groups Projects
Unverified Commit e0e5f53f authored by Gabriel Santamaria's avatar Gabriel Santamaria
Browse files

Hashing of the grid, strategy class

- Added the hashing for the grid using xxhash
- Added requirements.txt
- Added a little strategy module
parent a39f8785
Branches feature/gsantama/strategy
No related tags found
1 merge request!4Hashing of the grid, strategy class
__pycache__
\ No newline at end of file
__pycache__
dogopher
.venv
\ No newline at end of file
"""
Dogopher: A Gopher and Dodo AI (IA02 P24 Project)
"""
from api import *
from hexgrid.grid import HexGrid
from game.rules import Dodo
def initialize(
game: str, state: State, player: Player, hex_size: int, total_time: Time
) -> Environment:
"""
Initialize the game environment.
Args:
game: The name of the game.
state: The initial state of the game.
player: The player number.
hex_size: The size of the hexagons.
total_time: The total time for the game.
Returns:
The game environment.
API from: https://gitlab.utc.fr/ia02/gopher-and-dodo
"""
pass
def strategy(
env: Environment, state: State, player: Player, time_left: Time
) -> tuple[Environment, Action]:
"""
The strategy of the player. Launched every time it's the turn of our AI to play.
Args:
env: The game environment.
state: The current state of the game.
player: The player number.
time_left: The time left for the player.
Returns:
The updated game environment and the action to play.
API from: https://gitlab.utc.fr/ia02/gopher-and-dodo
"""
pass
if __name__ == "__main__":
pass
a: HexGrid = HexGrid.split_state(4, 1, 2)
grid = HexGrid(a.get_state(), a.size[0])
grid.debug_plot(bold=grid.get_neighbours((0, 0), True))
<div align='center'>
<img src=https://gitlab.utc.fr/uploads/-/system/project/avatar/14278/dogopher.png alt="logo" width=90 height=90 />
<h1>Dogopher</h1>
<p>Petites optimisations rigolotes pour le Hashing</p>
</div>
## Code pour le profiling
Nous utiliserons ce code pour le profiling de notre function de hashage.
```python
import cProfile
import pstats
def profile_hashing(n=10**5):
grid = HexGrid.random_state(100)
for _ in range(n):
hash(grid)
cProfile.run("profile_hashing()", "hashing_profile")
p = pstats.Stats("hashing_profile")
p.sort_stats("cumtime").print_stats(10)
```
## Candidats pour la fonction de hashage
Nous avons retenus deux canditats pour la fonction de hashage que nous décrivons ci-dessous.
### Le builtin `hash`
L'implémentation la plus logique et la plus directe est d'utiliser le builtin `hash` de Python.
#### Le code
```python
def __hash__(self) -> int:
return self.grid.tobytes().__hash__()
```
Pour se faire nous avons néanmoins besoin de convertir la grille (qui est un `numpy.ndarray`) en une chaîne d'octets pour pouvoir calculer son `hash`.
#### Le résultat
Le graphe ci dessous répertorie les temps d'exécution:
<div align="center">
<img src="images/hash_builtin.png" alt="Résultat des performances de la méthode builtin">
</div>
Comme nous pouvons le voir, les résultats sont bons et largement suffisants pour notre utilisation, néanmoins, comme la compétition est sous contrainte de temps, nous pouvons mieux faire et gagner quelques secondes de temps de calcul sur la fonction de hashage.
### La librairie `xxhash`
La librairie `xxhash` est une librairie de hashage performante qui propose des fonctions de hashage non-sécurisée (non-cryptographique) très rapides.
Comme nous utilisons le hashage exclusivement pour des accès à un dictionnaire, ce n'est pas dérangeant que nos fonctions soient non-cryptographiques.
- Référence: https://stackoverflow.com/a/75870138
#### Le code
```python
def __hash__(self) -> int:
return xxh32(self.grid).intdigest()
```
Ce code suppose que notre grille (qui est un `numpy.ndarray`) suit un layout de C-array, ce qu'il est possible de faire en utilisant `numpy.ascontiguousarray` lors de la création de la grille dans la classe `HexGrid`.
```python
def __array_of_state(self, state: State) -> np.ndarray:
# Code de __array_of_state
return np.ascontiguousarray(array)
```
#### Le résultat
Le graphe ci dessous répertorie les temps d'exécution:
<div align="center">
<img src="images/hash_xxhash.png" alt="Résultat des performances de la méthode xxhash">
</div>
Soit un gain d'environs 7 secondes sur la méthode avec le builtin de Python sur une chaîne d'octets.
\ No newline at end of file
doc/images/hash_builtin.png

30.6 KiB

doc/images/hash_xxhash.png

31.9 KiB

......@@ -50,7 +50,7 @@ def strategy(
pass
a: HexGrid = HexGrid.empty_state(4)
a: HexGrid = HexGrid.random_state(4)
print(a.is_empty())
......@@ -58,11 +58,4 @@ print(str(a))
grid = HexGrid(a.get_state(), a.size[0])
dodo = Gopher()
grid[1, 1] = 1
grid[0, 1] = 2
legal_moves = dodo.get_legal_moves(grid, 1)
print(legal_moves)
grid.debug_plot(bold=list(legal_moves))
grid.debug_plot()
"""
Strategy representation and implementation (minimax, MCTS, etc...)
"""
from api import Player, Action
class Strategy:
"""
Base class for a strategy.
"""
def get_action(self, ply: Player) -> Action:
"""
Get the action to play for a player.
Args:
ply: The player
Returns:
The action to play
"""
raise NotImplementedError
......@@ -8,6 +8,8 @@ import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import RegularPolygon
from xxhash import xxh32
from api import State, Cell, Player
......@@ -61,7 +63,8 @@ class HexGrid:
array[cell[0]][cell[1]] = players[key]
return array.T
# To make the hashing run faster, we directly convert to a contiguous array
return np.ascontiguousarray(array)
def __state_of_array(self, array: np.ndarray) -> State:
"""
......@@ -166,6 +169,14 @@ class HexGrid:
res += "-" * (ncols * cell_size + 2)
return str(res)
def __eq__(self, other):
return isinstance(other, HexGrid) and np.array_equal(self.grid, other.grid)
def __hash__(self) -> int:
# Fast hashing of the grid (which is the state of the game btw)
# See: https://stackoverflow.com/a/75870138
return xxh32(self.grid).intdigest()
def iter(self) -> Generator[tuple[Cell, Player], None, None]:
"""
Iterate over the hexagonal grid.
......@@ -289,6 +300,23 @@ class HexGrid:
coordinates.append(((q, r), bot))
return cls(coordinates, size)
@classmethod
def random_state(cls, size: int):
"""
Generate a random hex grid for a given size.
Args:
size: The size of the grid
Returns:
A random hex grid
"""
coordinates = []
for q in range(-size, size + 1):
for r in range(max(-size, -q - size), min(size, -q + size) + 1):
coordinates.append(((q, r), np.random.randint(0, 3)))
return cls(coordinates, size + 1)
@staticmethod
def __add_hexagon(ax, x_center, y_center, player=0, size=1, **kwargs):
color = "#faebd7" # a nice beige
......
contourpy==1.2.1
cycler==0.12.1
fonttools==4.52.4
kiwisolver==1.4.5
matplotlib==3.9.0
numpy>=1.26.4
packaging==24.0
pillow==10.3.0
pyparsing==3.1.2
python-dateutil==2.9.0.post0
six==1.16.0
xxhash>=3.4.1
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment