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 ]
0 commit comments