Skip to content

Commit c5cd9a5

Browse files
committed
refactored data types and util funcions
1 parent 287ab72 commit c5cd9a5

7 files changed

+229
-162
lines changed

domino_common_knowledge.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# from domino_game_analyzer import GameState, PlayerPosition, DominoTile, setup_game_state, PlayerPosition_SOUTH, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST
2-
from get_best_move2 import GameState, PlayerPosition, DominoTile, setup_game_state, PlayerPosition_SOUTH, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST
2+
from domino_data_types import GameState, PlayerPosition, DominoTile, PlayerPosition_SOUTH, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST
3+
# from get_best_move2 import GameState, PlayerPosition, DominoTile, setup_game_state, PlayerPosition_SOUTH, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST
4+
from domino_utils import setup_game_state
35
# from typing import List, Tuple, Optional, Set, Dict
46
import copy
57

domino_data_types.py

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
from dataclasses import dataclass
2+
from collections import namedtuple
3+
4+
type move = tuple[DominoTile, bool]|None
5+
type PlayerPosition = int
6+
7+
PlayerPosition_SOUTH = 0
8+
PlayerPosition_EAST = 1
9+
PlayerPosition_NORTH = 2
10+
PlayerPosition_WEST = 3
11+
12+
def next_player(pos: PlayerPosition)-> PlayerPosition:
13+
return (pos + 1) % 4
14+
15+
PlayerPosition_names = ['SOUTH', 'EAST', 'NORTH', 'WEST']
16+
17+
PlayerTiles = namedtuple('PlayerTiles', ['N', 'E', 'W'])
18+
19+
# @dataclass(frozen=True)
20+
@dataclass
21+
class DominoTile:
22+
top: int
23+
bottom: int
24+
25+
@classmethod
26+
def new_tile(cls, top: int, bottom: int) -> 'DominoTile':
27+
return cls(min(top, bottom), max(top, bottom))
28+
29+
@classmethod
30+
def loi_to_domino_tiles(cls, tuple_list: list[tuple[int, int]]) -> list['DominoTile']:
31+
return [DominoTile.new_tile(left, right) for left, right in tuple_list]
32+
33+
def __hash__(self) -> int:
34+
# return hash((self.top, self.bottom))
35+
return (self.top << 3) + self.bottom
36+
37+
def __eq__(self, other: object) -> bool:
38+
if isinstance(other, DominoTile):
39+
return self.top == other.top and self.bottom == other.bottom
40+
return False
41+
42+
def __repr__(self) -> str:
43+
return f"{self.top}|{self.bottom}"
44+
45+
def can_connect(self, end: int|None) -> bool:
46+
if end is None: # This allows any tile to be played on an empty board
47+
return True
48+
return self.top == end or self.bottom == end
49+
50+
def get_other_end(self, connected_end: int) -> int:
51+
return self.bottom if connected_end == self.top else self.top
52+
53+
def get_pip_sum(self) -> int:
54+
return self.top + self.bottom
55+
56+
# @dataclass(frozen=True)
57+
@dataclass
58+
class GameState:
59+
player_hands: tuple[frozenset[DominoTile], ...]
60+
current_player: PlayerPosition
61+
left_end: int|None
62+
right_end: int|None
63+
consecutive_passes: int
64+
65+
def __hash__(self) -> int:
66+
return hash((self.player_hands, self.current_player, self.left_end, self.right_end, self.consecutive_passes))
67+
68+
@classmethod
69+
def new_game(cls, player_hands: list[list[DominoTile]]) -> 'GameState':
70+
return cls(
71+
# player_hands=tuple(frozenset(DominoTile(top, bottom) for top, bottom in hand) for hand in player_hands),
72+
player_hands=tuple(frozenset(tile for tile in hand) for hand in player_hands),
73+
current_player=PlayerPosition_SOUTH,
74+
left_end=None,
75+
right_end=None,
76+
consecutive_passes=0
77+
)
78+
79+
def play_hand(self, tile: DominoTile, left: bool) -> 'GameState':
80+
new_hands = list(self.player_hands)
81+
new_hands[self.current_player] = self.player_hands[self.current_player] - frozenset([tile])
82+
83+
if self.left_end is None or self.right_end is None:
84+
new_left_end, new_right_end = tile.top, tile.bottom
85+
elif left:
86+
new_left_end = tile.get_other_end(self.left_end)
87+
new_right_end = self.right_end
88+
else:
89+
new_left_end = self.left_end
90+
new_right_end = tile.get_other_end(self.right_end)
91+
92+
return GameState(
93+
player_hands=tuple(new_hands),
94+
# current_player=self.current_player.next(),
95+
current_player=next_player(self.current_player),
96+
left_end=new_left_end,
97+
right_end=new_right_end,
98+
consecutive_passes=0
99+
)
100+
101+
def pass_turn(self) -> 'GameState':
102+
return GameState(
103+
player_hands=self.player_hands,
104+
# current_player=self.current_player.next(),
105+
current_player=next_player(self.current_player),
106+
left_end=self.left_end,
107+
right_end=self.right_end,
108+
consecutive_passes=self.consecutive_passes + 1
109+
)
110+
111+
def is_game_over(self) -> bool:
112+
return any(len(hand) == 0 for hand in self.player_hands) or self.consecutive_passes == 4
113+
# def is_game_over(self) -> bool:
114+
# cy_state = GameStateCy(
115+
# self.player_hands,
116+
# self.current_player.value,
117+
# self.left_end if self.left_end is not None else -1,
118+
# self.right_end if self.right_end is not None else -1,
119+
# self.consecutive_passes
120+
# )
121+
# return cy_state.is_game_over()
122+
# def is_game_over(self) -> bool:
123+
# return GameStateCy.static_is_game_over(self.player_hands, self.consecutive_passes)
124+
125+
def get_current_hand(self) -> frozenset[DominoTile]:
126+
# return self.player_hands[self.current_player.value]
127+
return self.player_hands[self.current_player]

domino_game_tracker.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from statistics import mode, mean, variance, stdev, median
22
from collections import defaultdict, Counter
33
# from domino_game_analyzer import get_best_move_alpha_beta, list_possible_moves, PlayerPosition_SOUTH, next_player, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST
4-
from get_best_move2 import get_best_move_alpha_beta, list_possible_moves, GameState, DominoTile, PlayerPosition, PlayerPosition_SOUTH, next_player, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST, move
4+
from get_best_move2 import get_best_move_alpha_beta, list_possible_moves
5+
from domino_data_types import GameState, DominoTile, PlayerPosition, PlayerPosition_SOUTH, next_player, PlayerPosition_names, PlayerPosition_NORTH, PlayerPosition_EAST, PlayerPosition_WEST, move, PlayerTiles
56

67

78
# from typing import Optional
89
from collections import defaultdict
910
# from domino_game_analyzer import GameState, PlayerPosition, DominoTile, setup_game_state
1011
from domino_common_knowledge import CommonKnowledgeTracker
11-
from domino_probability_calc import calculate_tile_probabilities, PlayerTiles, generate_sample
12+
from domino_probability_calc import calculate_tile_probabilities, generate_sample
1213
import copy
1314

1415

domino_probability_calc.py

+51-6
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@
33
import itertools
44
# from typing import List, Dict, Tuple, Set
55
# from domino_game_analyzer import DominoTile, PlayerPosition
6-
from get_best_move2 import DominoTile, PlayerPosition
6+
# from get_best_move2 import DominoTile, PlayerPosition
7+
from domino_data_types import DominoTile, PlayerPosition, PlayerTiles
78
from dataclasses import dataclass
89
import copy
910
import random
1011

1112
# Scenario = namedtuple('Scenario', ['N', 'E', 'W'])
1213
# Scenario = namedtuple('Scenario', [('N', set[DominoTile]), ('E', set[DominoTile]), ('W', set[DominoTile])])
13-
PlayerTiles = namedtuple('PlayerTiles', ['N', 'E', 'W'])
1414

15-
@dataclass(frozen=True)
15+
# @dataclass(frozen=True)
16+
@dataclass
1617
class Scenario:
1718
N: set[DominoTile]
1819
E: set[DominoTile]
@@ -463,7 +464,7 @@ def generate_sample(
463464
player_tiles: PlayerTiles
464465
) -> dict[str, set[DominoTile]]:
465466

466-
assert len(remaining_tiles) == sum(e for e in player_tiles)
467+
# assert len(remaining_tiles) == sum(e for e in player_tiles)
467468
assert 'S' not in not_with
468469
assert all(p in not_with for p in 'NEW')
469470
assert all(isinstance(not_with[p],set) for p in 'NEW')
@@ -560,6 +561,28 @@ def generate_sample(
560561

561562
return sample
562563

564+
# import multiprocessing as mp
565+
# from functools import partial
566+
567+
# def worker(_, remaining_tiles_converted, not_with_converted, player_tiles):
568+
# return generate_sample(set(remaining_tiles_converted), not_with_converted, player_tiles)
569+
570+
# def parallel_generate_samples(remaining_tiles_converted, not_with_converted, player_tiles, num_samples=1000, num_processes=None):
571+
# if num_processes is None:
572+
# num_processes = mp.cpu_count() # Use all available CPU cores by default
573+
574+
# with mp.Pool(processes=num_processes) as pool:
575+
# partial_worker = partial(worker, remaining_tiles_converted=remaining_tiles_converted,
576+
# not_with_converted=not_with_converted, player_tiles=player_tiles)
577+
# samples = pool.map(partial_worker, range(num_samples))
578+
# return samples
579+
580+
581+
import concurrent.futures
582+
583+
def generate_sample_parallel(remaining_tiles_converted: list[DominoTile], not_with_converted: dict[str, set[DominoTile]], player_tiles: PlayerTiles) -> dict[str, set[DominoTile]]:
584+
return generate_sample(set(remaining_tiles_converted), not_with_converted, player_tiles)
585+
563586
# Example usage
564587
def main() -> None:
565588
remaining_tiles = [
@@ -577,7 +600,9 @@ def main() -> None:
577600

578601
not_with = {
579602
'E': {'0-1', '0-2'},
580-
# 'W': {'3-6'}
603+
'W': set(),
604+
# 'W': {'3-6'},
605+
'N': set()
581606
}
582607
# not_with = {
583608
# 'E': {'3-6'},
@@ -605,12 +630,32 @@ def main() -> None:
605630
# break
606631
# print_probabilities(probabilities)
607632

633+
# List to store the results
634+
results = []
635+
608636
# Example usage of generate_sample
609637
for _ in range(1000):
610638
# sample = generate_sample(remaining_tiles_converted, not_with_converted, known_with, player_tiles)
611639
sample = generate_sample(set(remaining_tiles_converted), not_with_converted, player_tiles)
612-
break
640+
results.append(sample)
641+
# break
642+
643+
# samples = parallel_generate_samples(remaining_tiles_converted, not_with_converted, player_tiles)
644+
645+
646+
# Use ThreadPoolExecutor to parallelize the loop
647+
# with concurrent.futures.ThreadPoolExecutor() as executor:
648+
# with concurrent.futures.ProcessPoolExecutor() as executor:
649+
# # Submit tasks to the executor
650+
# futures = [executor.submit(generate_sample_parallel, remaining_tiles_converted, not_with_converted, player_tiles) for _ in range(1000)]
651+
652+
# # Collect the results as they complete
653+
# for future in concurrent.futures.as_completed(futures):
654+
# results.append(future.result())
655+
656+
print('len(results)',len(results))
613657
print("Generated sample:")
658+
sample = results[-1]
614659
for player, tiles in sample.items():
615660
print(f"{player}: {tiles}")
616661

domino_utils.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# from domino_game_analyzer import DominoTile
2-
from get_best_move2 import DominoTile
2+
from domino_data_types import DominoTile, GameState, PlayerPosition, move
33

44
def history_to_domino_tiles_history(move_list: list[tuple[int, tuple[tuple[int, int], str]|None]]) -> list[tuple[DominoTile, bool]|None]:
55
result: list[tuple[DominoTile, bool]|None] = []
@@ -9,4 +9,44 @@ def history_to_domino_tiles_history(move_list: list[tuple[int, tuple[tuple[int,
99
domino_tile = DominoTile.new_tile(tile_tuple[0], tile_tuple[1])
1010
is_left = side == 'l'
1111
result.append((domino_tile, is_left) if move_details is not None else None)
12-
return result
12+
return result
13+
14+
15+
def setup_game_state(
16+
initial_hands: list[list[DominoTile]],
17+
starting_player: PlayerPosition,
18+
first_moves: list[move]
19+
) -> GameState:
20+
"""
21+
Set up a game state based on initial hands, starting player, and first moves.
22+
23+
:param initial_hands: List of initial hands for each player, where each hand is a list of DominoTile objects
24+
:param starting_player: The player who starts the game
25+
:param first_moves: List of first moves. Each move is a tuple (DominoTile, bool) or None for pass
26+
:return: The resulting GameState after applying the first moves
27+
"""
28+
# Initialize the game state with the given hands
29+
state = GameState.new_game([[tile for tile in hand] for hand in initial_hands])
30+
31+
# Set the starting player
32+
state = GameState(
33+
player_hands=state.player_hands,
34+
current_player=starting_player,
35+
left_end=state.left_end,
36+
right_end=state.right_end,
37+
consecutive_passes=state.consecutive_passes
38+
)
39+
40+
# Apply the first moves
41+
for move in first_moves:
42+
if move is None:
43+
state = state.pass_turn()
44+
else:
45+
tile, left = move
46+
# Find and play the tile from the current player's hand
47+
if tile not in state.get_current_hand():
48+
print('state.get_current_hand()',state.get_current_hand())
49+
raise ValueError(f"Tile {tile} not found in current player's hand")
50+
state = state.play_hand(tile, left)
51+
52+
return state

get-best-move-alpha-beta-tests.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import unittest
22
from unittest.mock import patch
33
# from get_best_move import get_best_move_alpha_beta, GameState, DominoTile, PlayerPosition_SOUTH, PlayerPosition_WEST, PlayerPosition_NORTH, PlayerPosition_EAST
4-
from get_best_move2 import get_best_move_alpha_beta, GameState, DominoTile, PlayerPosition_SOUTH, PlayerPosition_WEST, PlayerPosition_NORTH, PlayerPosition_EAST
4+
from domino_data_types import GameState, DominoTile, PlayerPosition_SOUTH, PlayerPosition_WEST, PlayerPosition_NORTH, PlayerPosition_EAST
5+
from get_best_move2 import get_best_move_alpha_beta
56
# from get_best_move import PlayerPosition_SOUTH, PlayerPosition_WEST, PlayerPosition_NORTH, PlayerPosition_EAST
67
# from get_best_move3 import get_best_move_alpha_beta, GameState, DominoTile
78

0 commit comments

Comments
 (0)