forked from iSchool-597DS/2022Spring_Projects
-
Notifications
You must be signed in to change notification settings - Fork 0
/
game_module.py
341 lines (286 loc) · 13.1 KB
/
game_module.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
"""
IS 597 Final Project
YuWei Lai
04.29.2022
game_module.py deals with the game status' and moves' information. Includes two classes: Move and GameBoard.
Also, numerous functions deals with status changing and move generating: generate_moves(), is_valid(), make_a_move()
"""
# %%
from copy import deepcopy
import random
### ---------------------------------------- ###
class Move:
def __init__(self, start_pos=None, end_pos=None, removed_opponent_pos=None, jump_through_pos=[]) -> None:
self.start_pos = start_pos
self.end_pos = end_pos
self.removed_opponent_pos = removed_opponent_pos
self.jump_through_pos = jump_through_pos
# self.move_score = 0
### ---------------------------------------- ###
class GameBoard:
_board_size = 6 # default is an 8*8 game
_init_pos_size = 3 # 4
def __init__(self, **kwag) -> None:
self.p1_position = set([(i, j)
for i in range(0, self._init_pos_size) for j in range(0, self._init_pos_size-i)])
self.p2_position = set([(self._board_size-1-i, self._board_size-1-j)
for i in range(0, self._init_pos_size) for j in range(0, self._init_pos_size-i)])
self.turn_player = 'player_1'
self.valid_move_list = []
# Pre-assigned values
if kwag:
if 'p1_position' in kwag.keys():
self.p1_position = set(kwag['p1_position'])
if 'p2_position' in kwag.keys():
self.p2_position = set(kwag['p2_position'])
if 'turn_player' in kwag.keys():
self.turn_player = kwag['turn_player']
# For evaluations
self.board_score_depth = 0
self.board_score = 0
self.opponent_score = 0
def encoder(self) -> str:
key = '' # a key to represent a board would be a string in length 1 + 128
if self.turn_player == 'player_1':
key = key + '0'
else:
key = key + '1'
for i in range(0, self._board_size):
for j in range(0, self._board_size):
pos = (i, j)
pos_str = '00'
if pos in self.p1_position:
pos_str = '01'
elif pos in self.p2_position:
pos_str = '11'
key = key + pos_str
return key
@staticmethod
def decoder(key):
turn_player = ''
if key[0] == '0':
turn_player = 'player_1'
elif key[0] == '1':
turn_player = 'player_2'
i, j = 0, 0
pos_pointer = 1
p1_position = []
p2_position = []
while pos_pointer < len(key):
if key[pos_pointer:pos_pointer+2] == '01':
p1_position = p1_position + [(i, j)]
elif key[pos_pointer:pos_pointer+2] == '11':
p2_position = p2_position + [(i, j)]
pos_pointer += 2
j += 1
if j % GameBoard._board_size == 0:
j = 0
i += 1
return GameBoard(p1_position=p1_position, p2_position=p2_position, turn_player=turn_player)
def generate_round_moves(self):
"""
::return:: current_position and new_position
"""
# --------- #
valid_move_list = []
current_position = []
if self.turn_player == 'player_2':
current_position = self.p2_position
else:
current_position = self.p1_position
for pos in current_position:
valid_move_list = valid_move_list + \
generate_moves(self, start_pos=pos)
self.valid_move_list = valid_move_list
def is_winning(self):
if self.turn_player == 'player_1':
if ((self._board_size - 1, self._board_size - 1) in self.p1_position) or (len(self.p2_position) == 1 and len(self.p1_position) > 1):
return True
elif self.turn_player == 'player_2':
if ((0, 0) in self.p2_position) or (len(self.p1_position) == 1 and len(self.p2_position) > 1):
return True
return False
def is_loosing(self):
if self.turn_player == 'player_1':
if (0, 0) in self.p2_position or (len(self.p1_position) == 1 and len(self.p2_position) > 1):
return True
elif self.turn_player == 'player_2':
if (self._board_size - 1, self._board_size - 1) in self.p1_position or (len(self.p2_position) == 1 and len(self.p1_position) > 1):
return True
return False
def is_draw(self):
# both only had one piece left -> not possible
if len(self.p1_position) == 1 and len(self.p2_position) == 1:
return True
# no move is possible
if self.valid_move_list == []:
self.generate_round_moves()
if self.valid_move_list == []:
return True
return False
def list_all_valid_moves(self):
# valid_moves_dict = {}
# for move in self.valid_move_list:
# key_str = '{}->{}'.format(move.start_pos, move.end_pos)
# valid_moves_dict[key_str] = move
# return valid_moves_dict
return self.valid_move_list
def evaluation(self, depth=3):
self_score = 0
oppo_score = 0
# If self winning
if self.is_loosing():
self_score += -9999999
# self_score += -1000
oppo_score += 1000
# If self loosing
if self.is_winning():
self_score += 1000
oppo_score += -9999999
# oppo_score += -1000
# If the game draw
if self.is_draw():
self_score += 100
oppo_score += 100
if self.turn_player == 'player_1':
self_target = (self._board_size - 1, self._board_size - 1)
oppo_target = (0, 0)
self_position = self.p1_position
oppo_position = self.p2_position
# How many piece is still on the board
self_piece_cnt = len(self.p1_position)
oppo_piece_cnt = len(self.p2_position)
elif self.turn_player == 'player_2':
self_target = (0, 0)
oppo_target = (self._board_size - 1, self._board_size - 1)
self_position = self.p2_position
oppo_position = self.p1_position
# How many piece is still on the board
self_piece_cnt = len(self.p2_position)
oppo_piece_cnt = len(self.p1_position)
# SELF - How each piece close to the target posotion => add weight
for pos in self_position:
diff = (abs(self_target[0]-pos[0]), abs(self_target[1]-pos[1]))
pos_score = (self._board_size -
diff[0]) ** 2 + (self._board_size - diff[1]) ** 2
self_score += pos_score
# OPPO - How each piece close to the target posotion => add weight
for pos in oppo_position:
diff = (abs(oppo_target[0]-pos[0]), abs(oppo_target[1]-pos[1]))
pos_score = (self._board_size - 1 -
diff[0]) ** 2 + (self._board_size - 1 - diff[1]) ** 2
oppo_score += pos_score
self.board_score = self_score + self_piece_cnt * 10
self.opponent_score = oppo_score + oppo_piece_cnt * 10
### ---------------------------------------- ###
# Functions for playing the game
def is_valid(status: GameBoard, start_pos: tuple, end_pos: tuple, last_pos: tuple = None, jump_through: tuple = None, previous_steps: Move = None):
# if move is out of border
if end_pos[0] >= status._board_size or end_pos[1] >= status._board_size or end_pos[0] < 0 or end_pos[1] < 0:
return False
# if the move pos is occupied (included intermediate points and end points)
if end_pos in status.p1_position or end_pos in status.p2_position:
return False
# Prevent jumping back to the start pos
if last_pos:
if end_pos == last_pos:
return False
# Prevent jumping into back patterns
if jump_through and previous_steps:
if jump_through in previous_steps.jump_through_pos or end_pos == previous_steps.start_pos:
return False
return True
def generate_moves(status: GameBoard, start_pos: tuple,
previous_steps: Move = None, is_jumping: bool = False, last_pos: tuple = None):
"""
start_pos: the original position the piece stands
previous_steps: a valid move
"""
valid_moves = []
# status 1: a initial start (when start jumping, we cannot use the normal move method)
if not is_jumping:
# normal move
# --- for player 1: only can move downward or right
if status.turn_player == 'player_1':
move_set = [(start_pos[0]+1, start_pos[1]), (start_pos[0],
start_pos[1]+1), (start_pos[0]+1, start_pos[1]+1)]
# --- for player 2: only can move upward or left
elif status.turn_player == 'player_2':
move_set = [(start_pos[0]-1, start_pos[1]), (start_pos[0],
start_pos[1]-1), (start_pos[0]-1, start_pos[1]-1)]
for move_pos in move_set:
if is_valid(status, start_pos, move_pos):
# valid_moves = valid_moves + [move_pos]
move = Move(start_pos, move_pos)
valid_moves = valid_moves + [move]
# status 1 & 2: a initial start
# jumping moves (start jumping): 8 possible directions
neighbor_set = [(start_pos[0]-1, start_pos[1]), (start_pos[0], start_pos[1]+1), (start_pos[0]-1, start_pos[1]+1), (start_pos[0]+1, start_pos[1]+1),
(start_pos[0]+1, start_pos[1]), (start_pos[0], start_pos[1]-1), (start_pos[0]+1, start_pos[1]-1), (start_pos[0]-1, start_pos[1]-1)]
for neighbor in neighbor_set:
# Start a jump
if neighbor in status.p1_position or neighbor in status.p2_position:
diff = (neighbor[0]-start_pos[0],
neighbor[1]-start_pos[1])
# Next position -- jump cross the neighbor
move_pos = (neighbor[0]+diff[0], neighbor[1]+diff[1])
# print('try the neighbor:', neighbor, diff, 'jump moves: ', move_pos)
if is_valid(status, start_pos, move_pos, last_pos=last_pos,
jump_through=neighbor, previous_steps=previous_steps):
new_status = deepcopy(status)
# If previous jumps had remove a pos
if previous_steps is not None:
remove_pos = previous_steps.removed_opponent_pos
else:
remove_pos = None
# Prepare for keep jumping
if status.turn_player == 'player_1':
new_status.p1_position.remove(start_pos)
new_status.p1_position.add(move_pos)
if (not remove_pos) and neighbor in new_status.p2_position:
remove_pos = neighbor
new_status.p2_position.remove(remove_pos)
elif status.turn_player == 'player_2':
new_status.p2_position.remove(start_pos)
new_status.p2_position.add(move_pos)
if (not remove_pos) and neighbor in new_status.p1_position:
remove_pos = neighbor
new_status.p1_position.remove(remove_pos)
# Add the option to the result set
if not previous_steps:
move = Move(start_pos, move_pos,
removed_opponent_pos=remove_pos, jump_through_pos=[neighbor])
valid_moves = valid_moves + [move]
else:
jump_through_pos = previous_steps.jump_through_pos + \
[neighbor]
move = Move(previous_steps.start_pos, move_pos,
removed_opponent_pos=remove_pos, jump_through_pos=jump_through_pos)
valid_moves = valid_moves + [move]
# Try to keep jumping: Create a new game status for making the move:
new_start_pos = move_pos
valid_jump_moves = generate_moves(
new_status, new_start_pos, previous_steps=move, is_jumping=True,
last_pos=start_pos)
valid_moves = valid_moves + valid_jump_moves
return valid_moves
def make_a_move(board: GameBoard, move: Move):
new_board = deepcopy(board)
if new_board.turn_player == 'player_1':
new_board.p1_position.remove(move.start_pos)
new_board.p1_position.add(move.end_pos)
new_board.turn_player = 'player_2'
new_board.opponent_score = 'player_1'
if move.removed_opponent_pos:
new_board.p2_position.remove(move.removed_opponent_pos)
elif new_board.turn_player == 'player_2':
new_board.p2_position.remove(move.start_pos)
new_board.p2_position.add(move.end_pos)
new_board.turn_player = 'player_1'
new_board.opponent_score = 'player_2'
if move.removed_opponent_pos:
new_board.p1_position.remove(move.removed_opponent_pos)
new_board.valid_move_list = []
return new_board
def generate_random_game(seed):
pass