-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmove.py
383 lines (322 loc) · 16.8 KB
/
move.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
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
from board import Board, Dice
from bgexceptions import BackgammonException, IllegalMoveException
#from numba import jit
class BarMove(object):
""" Methods for removing checkers from the Bar. """
def __init__(self, player, die, board, end):
self.player = player
self.other_player = Board.get_opponent(player)
self.die = die
self.board = Board(board)
# check if end is on the board
if not Board.on_board(end):
raise IllegalMoveException("End is not on the Board!")
else:
self.end = end
def move_possible(self):
""" Checks if"""
def make_move(self):
""" Validates the movement given the provided board situation. """
if not (self.die == abs(Board.get_home(self.other_player) - self.end)):
raise IllegalMoveException("Off-Bar not possible: \
Die cannot be used to perform this movement!")
# check if bar is empty and raise error if yes
if self.board.get_bar(self.player) == 0:
raise IllegalMoveException("Off-Bar not possible: \
No checkers on the Bar!")
# check if end position is in the homeboard of the other player
# if not raise Error, checkers leaving the bar must be placed in the
# homeboard of the opponent (your starting quadrant)
if not Board.in_home_board(self.other_player, self.end):
raise IllegalMoveException("Off-Bar not possible: \
Checkers must go into the opponents home board!")
# check if there is more than one opponent checker at end position
# and raise error when yes
if self.board.get_checkers(self.end, self.other_player) > 1:
raise IllegalMoveException("Off-Bar not possible: \
Location occupied by other player")
# now make the movement:
# first kick enemy checkers onto the bar if there are any
if self.board.get_checkers(self.end, self.other_player) == 1:
self.board.remove_from_location(self.other_player, self.end)
self.board.move_to_bar(self.other_player)
self.board.remove_from_bar(self.player)
self.board.move_to_location(self.player, self.end)
# update move history of the board
self.board.update_move_history(self.__str__())
return self.board
def __str__(self):
""" Returns string representation of this movement. """
return "bar --> %s" %(self.end + 1)
def __eq__(self, other):
""" Returns whether this movement is equal to the provided object. """
if isinstance(other, BarMovement):
return other.end == self.end
else:
return False
def __ne__(self, other):
return not self == other
class BearOffMove(object):
""" Methods for bearing off a checker. """
# rules for bear-off:
# - all checkers must be in the homeboard of a color (end area)
# - endboard numbered 1...6 with 1 closest to end of gameboard
# and 6 closest to bar
# - roll a 1, bear off checker from 1
# - roll a 6, bear off checker from 6
# - if no checker at rolled position in homeboard, make legal move
# within the homeboard
# - when number is rolled that is higher than the highest point with checkers,
# bear off next highest checker
def __init__(self, player, die, board, start):
self.player = player
self.die = die
self.board = Board(board)
if not Board.on_board(start):
raise IllegalMoveException("Start is not on the board!")
else:
self.start = start
def can_use(self):
""" Returns whether or not this movement can use the given
dice roll to perform its movement. """
if self.die == abs(self.start - Board.get_home(self.player)):
return True
# if no checker in rolled number, the player is required to remove
# a checker from the highest point
elif self.die > abs(self.start - Board.get_home(self.player)):
direction = Board.get_direction(self.player)
# check if there are no other checkers left to start point
# loop from start towards the bar
for i in range(self.start - direction, Board.get_home(self.player) \
- (7 * direction), - direction):
if self.board.get_checkers(i, self.player) > 0:
return False
return True
else:
return False
def make_move(self):
""" Validates this movement given the provided
board situation. """
if not self.can_use():
raise IllegalMoveException("Bear-off not possible: \
Cannot use dice for this movement!")
if self.board.get_checkers(self.start, self.player) == 0:
raise IllegalMoveException("Bear-off not possible: \
No checkers at location!")
if self.board.get_bar(self.player) > 0:
raise IllegalMoveException("Bear-off not possible: \
Checkers from the bar must be moved first!")
# loop over whole board and check whether all checkers are in the homeboard
# if not all checkers are in the homeboard, bear-off is NOT allowed
for i in range(Board.NUM_POINTS):
if (self.board.get_checkers(i, self.player) > 0) and \
(not Board.in_home_board(self.player, i)):
raise IllegalMoveException("Bear-off not possible: \
Still checkers outside of the home board!")
# now make the move
self.board.remove_from_location(self.player, self.start)
self.board.move_off(self.player)
# update move history of the board
self.board.update_move_history(self.__str__())
return self.board
def __str__(self):
""" Returns string representation of this movement. """
return "%s --> off" %(self.start + 1)
def __eq__(self, other):
""" Returns whether this movement is equal to the provided object. """
if isinstance(other, BearOffMovement):
return other.start == self.start
else:
return False
def __ne__(self, other):
return not self == other
class NormalMove(object):
""" Methods for a normal movement. """
def __init__(self, player, die, board, start, end):
self.player = player
self.other_player = Board.get_opponent(player)
self.die = die
self.board = Board(board)
if (not Board.on_board(start)) or (not Board.on_board(end)):
raise IllegalMoveException("Start or end is not on the board!")
else:
self.start = start
self.end = end
def make_move(self):
""" Validates this movement given the provided
board situation. """
if not (self.die == abs(self.start - self.end)):
#print "make_move2"
raise IllegalMoveException("Normal move not possible: \
Cannot use die for this movement!")
if self.start == self.end:
raise IllegalMoveException("Normal move not possible: \
Start and end must not be the same position!")
if abs(self.start - self.end) > Dice.MAX_VALUE:
raise IllegalMoveException("Normal move not possible: \
Move distance larger than 6!")
if self.board.get_bar(self.player) > 0:
raise IllegalMoveException("Normal move not possible:: \
Checkers from the bar must be moved first!")
if self.board.get_checkers(self.start, self.player) == 0:
raise IllegalMoveException("Normal move not possible: \
No checkers at the start location!")
if ((self.start > self.end) and (Board.get_direction(self.player) > 0) or
(self.start < self.end) and (Board.get_direction(self.player) < 0)):
raise IllegalMoveException("Normal move not possible: \
Backward movements are not allowed!")
if self.board.get_checkers(self.end, self.other_player) > 1:
raise IllegalMoveException("Normal move not possible: \
End location already occupied by opponent checkers!")
# now perform movement:
# check first whether the move bumps the opponent
if self.board.get_checkers(self.end, self.other_player) == 1:
self.board.remove_from_location(self.other_player, self.end)
self.board.move_to_bar(self.other_player)
# now perform the move
self.board.remove_from_location(self.player, self.start)
self.board.move_to_location(self.player, self.end)
# update move history of the board
self.board.update_move_history(self.__str__())
return self.board
def __str__(self):
""" Returns a String representation of this movement. """
return "%s --> %s" %(self.start + 1, self.end + 1)
def __eq__(self, other):
""" Returns whether this movement is equal to the provided object. """
if isinstance(other, NormalMovement):
return (other.start == self.start) and (other.end == self.end)
else:
return False
def __ne__(self, other):
return not self == other
class BoardFactory(object):
""" Generates all distinct boards for the given gammon situation
and the provided dice roll. """
@classmethod
def generate_all_boards(cls, player, dice, board):
""" Function takes an initial backgammon situation (player, dice, board),
and generates all possible moves and the resulting boards.
Returns a list of all possible moves from all dice combinations. """
board.reset_move_history()
# check if dice are doubles:
if dice.is_doubles():
all_dice_combinations = [[dice.get_die1()] * 4]
else:
all_dice_combinations = []
# make all dice combinations by sorting and put them in the list
all_dice_combinations.append(sorted(dice.get_dice(), reverse=True))
all_dice_combinations.append(sorted(dice.get_dice(), reverse=False))
# all_boards will contain all possible boards in the end
all_boards = []
# loop over the two dice combinations
# if doubles, all_dice_combinations contains list of 4 numbers
for all_dice in all_dice_combinations:
# set boards to the starting board
boards = [board]
# loop over die in one dice combination
for die in all_dice:
# compute all boards for die on all previously generated boards
# or the initial board of this turn
computed_boards = cls.compute_boards(player, die, boards)
# check if there were any boards created and set these as
# the starting boards for the next moves using the next die
if computed_boards is not None:
boards = computed_boards
# store all generated boards in all_boards
# now repeat this process for the next dice combination
all_boards.append(boards)
# list comprehension for transforming list of board-lists into list of boards
lst = [item for sublist in all_boards for item in sublist]
# transform list to set to get rid of duplicate boards
# boards entering a set get key depending on their hash value and
# identical boards will have identical hashes and removed from the set
board_set = set(lst)
# now transfrom back into list, because sets are unordered and cant
# be accessed by keys directly
board_list = list(board_set)
# filter out incomplete boards:
# sometimes a putative board omits a possible move,
# this is due to the construction of compute_boards() which only takes
# one die into account and disregards the other die
# determine number of moves, that all boards must contain
max_moves = max([len(item.move_history) for item in board_list])
# only keep those boards in the list that have max amount of moves
board_list = [b for b in board_list if len(b.move_history) == max_moves]
return board_list
@staticmethod
def compute_boards(player, die, boards):
""" Function takes a starting board and replaces it with all possible
boards. Finally returns a list of all possible boards,
or the starting board(s) if no boards could be created. """
# boards is a list containing starting boards
# loop over the boards in boards list
for i, brd in enumerate(boards):
new_boards = []
# check if bar move must be made
if brd.get_bar(player) > 0:
try:
# make a bar move, and return the resulting board
destination = Board.get_home(Board.get_opponent(player)) + \
die * Board.get_direction(player)
bar_move = BarMove(player, die, brd, destination)
tmp_board = bar_move.make_move()
except IllegalMoveException:
tmp_board = None
# make sure a bar move was legal and a new board was generated
# if yes, append new_boards
if tmp_board is not None:
new_boards.append(tmp_board)
# try normal or bear-off moves:
else:
# loop over whole board
for pos in range(Board.NUM_POINTS):
# pos occupied by player
if brd.get_checkers(pos, player) > 0:
try:
# try to make a normal move and return resulting board
destination = pos + (die * Board.get_direction(player))
normal_move = NormalMove(player, die, brd, pos, destination)
tmp_board = normal_move.make_move()
except IllegalMoveException, e:
tmp_board = None
# make sure normal move was legal and a new board was generated
# if yes, append new_boards
if tmp_board is not None:
new_boards.append(tmp_board)
# loop over players homeboard, ie the target quadrant
# of a player (white: 23...18 or black: 0...5)
for pos in range(Board.get_home(player) - Board.get_direction(player),\
Board.get_home(player) - (7 * Board.get_direction(player)),\
- Board.get_direction(player)):
try:
# try to bear off checkers and return resulting board
bear_off_move = BearOffMove(player, die, brd, pos)
tmp_board = bear_off_move.make_move()
except IllegalMoveException, e:
tmp_board = None
# make sure bearoff move was legal and a new board was generated
# if yes, append new_boards
if tmp_board is not None:
new_boards.append(tmp_board)
# check if new boards were created
# and if yes, replace each starting board in boards with
# a list of possible boards originating from each starting board
if len(new_boards) > 0:
boards[i] = new_boards
# lets say three boards go in and only one of these can generate a
# new board. boards would look something like [b0, b1, [b31,..., b3n]]
# this will give an error when trying to iterate b0 or b1
# so instead put the initial board in a list and iteration works again
elif len(new_boards) == 0:
boards[i] = [boards[i]]
# check if any new boards were created:
# if yes flatten and return
if len(new_boards) >= 0:
# flatten boards, takes sublist elements and puts them a new list
# and returns a list containing all possible boards
return [item for sublist in boards for item in sublist]
# if no new boards were created:
# return None
else:
return None