@@ -41,6 +41,9 @@ def min_value(state):
41
41
42
42
# ______________________________________________________________________________
43
43
44
+ dice_rolls = list (itertools .combinations_with_replacement ([1 , 2 , 3 , 4 , 5 , 6 ], 2 ))
45
+ direction = {'W' : - 1 , 'B' : 1 }
46
+
44
47
def expectiminimax (state , game ):
45
48
"""Return the best move for a player after dice are thrown. The game tree
46
49
includes chance nodes along with min and max nodes. [Figure 5.11]"""
@@ -66,21 +69,19 @@ def chance_node(state, action):
66
69
return game .utility (res_state , player )
67
70
sum_chances = 0
68
71
num_chances = 21
69
- dice_rolls = list (itertools .combinations_with_replacement ([1 , 2 , 3 , 4 , 5 , 6 ], 2 ))
70
- if res_state .to_move == 'W' :
71
- for val in dice_rolls :
72
- game .dice_roll = (- val [0 ], - val [1 ])
73
- sum_chances += max_value (res_state ,
74
- (- val [0 ], - val [1 ])) * (1 / 36 if val [0 ] == val [1 ] else 1 / 18 )
75
- elif res_state .to_move == 'B' :
76
- for val in dice_rolls :
77
- game .dice_roll = val
78
- sum_chances += min_value (res_state , val ) * (1 / 36 if val [0 ] == val [1 ] else 1 / 18 )
72
+ for val in dice_rolls :
73
+ game .dice_roll = tuple (map ((direction [res_state .to_move ]).__mul__ , val ))
74
+ util = 0
75
+ if res_state .to_move == player :
76
+ util = max_value (res_state , game .dice_roll )
77
+ else :
78
+ util = min_value (res_state , game .dice_roll )
79
+ sum_chances += util * (1 / 36 if val [0 ] == val [1 ] else 1 / 18 )
79
80
return sum_chances / num_chances
80
81
81
82
# Body of expectiminimax:
82
83
return argmax (game .actions (state ),
83
- key = lambda a : chance_node (state , a ))
84
+ key = lambda a : chance_node (state , a ), default = None )
84
85
85
86
86
87
def alphabeta_search (state , game ):
@@ -181,18 +182,21 @@ def query_player(game, state):
181
182
game .display (state )
182
183
print ("available moves: {}" .format (game .actions (state )))
183
184
print ("" )
184
- move_string = input ('Your move? ' )
185
- try :
186
- move = eval (move_string )
187
- except NameError :
188
- move = move_string
185
+ move = None
186
+ if game .actions (state ):
187
+ move_string = input ('Your move? ' )
188
+ try :
189
+ move = eval (move_string )
190
+ except NameError :
191
+ move = move_string
192
+ else :
193
+ print ('no legal moves: passing turn to next player' )
189
194
return move
190
195
191
196
192
197
def random_player (game , state ):
193
198
"""A player that chooses a legal move at random."""
194
- return random .choice (game .actions (state ))
195
-
199
+ return random .choice (game .actions (state )) if game .actions (state ) else None
196
200
197
201
def alphabeta_player (game , state ):
198
202
return alphabeta_search (state , game )
@@ -396,47 +400,45 @@ class Backgammon(Game):
396
400
397
401
def __init__ (self ):
398
402
"""Initial state of the game"""
399
- self .dice_roll = ( - random . randint ( 1 , 6 ), - random .randint ( 1 , 6 ))
403
+ self .dice_roll = tuple ( map (( direction [ 'W' ]). __mul__ , random .choice ( dice_rolls ) ))
400
404
# TODO : Add bar to Board class where a blot is placed when it is hit.
401
- point = {'W' :0 , 'B' :0 }
402
- self .board = [point .copy () for index in range (24 )]
403
- self .board [0 ]['B' ] = self .board [23 ]['W' ] = 2
404
- self .board [5 ]['W' ] = self .board [18 ]['B' ] = 5
405
- self .board [7 ]['W' ] = self .board [16 ]['B' ] = 3
406
- self .board [11 ]['B' ] = self .board [12 ]['W' ] = 5
407
- self .allow_bear_off = {'W' : False , 'B' : False }
408
-
405
+ point = {'W' : 0 , 'B' : 0 }
406
+ board = [point .copy () for index in range (24 )]
407
+ board [0 ]['B' ] = board [23 ]['W' ] = 2
408
+ board [5 ]['W' ] = board [18 ]['B' ] = 5
409
+ board [7 ]['W' ] = board [16 ]['B' ] = 3
410
+ board [11 ]['B' ] = board [12 ]['W' ] = 5
411
+ self .allow_bear_off = {'W' : False , 'B' : False }
409
412
self .initial = GameState (to_move = 'W' ,
410
- utility = 0 ,
411
- board = self . board ,
412
- moves = self .get_all_moves (self . board , 'W' ))
413
+ utility = 0 ,
414
+ board = board ,
415
+ moves = self .get_all_moves (board , 'W' ))
413
416
414
417
def actions (self , state ):
415
- """Returns a list of legal moves for a state."""
418
+ """Return a list of legal moves for a state."""
416
419
player = state .to_move
417
420
moves = state .moves
418
421
if len (moves ) == 1 and len (moves [0 ]) == 1 :
419
422
return moves
420
423
legal_moves = []
421
424
for move in moves :
422
425
board = copy .deepcopy (state .board )
423
- if self .is_legal_move (move , self .dice_roll , player ):
426
+ if self .is_legal_move (board , move , self .dice_roll , player ):
424
427
legal_moves .append (move )
425
428
return legal_moves
426
429
427
430
def result (self , state , move ):
428
431
board = copy .deepcopy (state .board )
429
432
player = state .to_move
430
- self .move_checker (move [0 ], self .dice_roll [0 ], player )
433
+ self .move_checker (board , move [0 ], self .dice_roll [0 ], player )
431
434
if len (move ) == 2 :
432
- self .move_checker (move [1 ], self .dice_roll [1 ], player )
435
+ self .move_checker (board , move [1 ], self .dice_roll [1 ], player )
433
436
to_move = ('W' if player == 'B' else 'B' )
434
437
return GameState (to_move = to_move ,
435
438
utility = self .compute_utility (board , move , player ),
436
439
board = board ,
437
440
moves = self .get_all_moves (board , to_move ))
438
441
439
-
440
442
def utility (self , state , player ):
441
443
"""Return the value to player; 1 for win, -1 for loss, 0 otherwise."""
442
444
return state .utility if player == 'W' else - state .utility
@@ -452,7 +454,7 @@ def get_all_moves(self, board, player):
452
454
all_points = board
453
455
taken_points = [index for index , point in enumerate (all_points )
454
456
if point [player ] > 0 ]
455
- if self .checkers_at_home (player ) == 1 :
457
+ if self .checkers_at_home (board , player ) == 1 :
456
458
return [(taken_points [0 ], )]
457
459
moves = list (itertools .permutations (taken_points , 2 ))
458
460
moves = moves + [(index , index ) for index , point in enumerate (all_points )
@@ -463,32 +465,28 @@ def display(self, state):
463
465
"""Display state of the game."""
464
466
board = state .board
465
467
player = state .to_move
466
- print ("Current State : " )
468
+ print ("current state : " )
467
469
for index , point in enumerate (board ):
468
- if point ['W' ] != 0 or point ['B' ] != 0 :
469
- print ("Point : " , index , " W : " , point ['W' ], " B : " , point ['B' ])
470
- print ("To play : " , player )
470
+ print ("point : " , index , " W : " , point ['W' ], " B : " , point ['B' ])
471
+ print ("to play : " , player )
471
472
472
473
def compute_utility (self , board , move , player ):
473
474
"""If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0."""
474
- count = 0
475
+ util = { 'W' : 1 , 'B' : '-1' }
475
476
for idx in range (0 , 24 ):
476
- count = count + board [idx ][player ]
477
- if player == 'W' and count == 0 :
478
- return 1
479
- if player == 'B' and count == 0 :
480
- return - 1
481
- return 0
477
+ if board [idx ][player ] > 0 :
478
+ return 0
479
+ return util [player ]
482
480
483
- def checkers_at_home (self , player ):
481
+ def checkers_at_home (self , board , player ):
484
482
"""Return the no. of checkers at home for a player."""
485
483
sum_range = range (0 , 7 ) if player == 'W' else range (17 , 24 )
486
484
count = 0
487
485
for idx in sum_range :
488
- count = count + self . board [idx ][player ]
486
+ count = count + board [idx ][player ]
489
487
return count
490
488
491
- def is_legal_move (self , start , steps , player ):
489
+ def is_legal_move (self , board , start , steps , player ):
492
490
"""Move is a tuple which contains starting points of checkers to be
493
491
moved during a player's turn. An on-board move is legal if both the destinations
494
492
are open. A bear-off move is the one where a checker is moved off-board.
@@ -497,31 +495,31 @@ def is_legal_move(self, start, steps, player):
497
495
dest_range = range (0 , 24 )
498
496
move1_legal = move2_legal = False
499
497
if dest1 in dest_range :
500
- if self .is_point_open (player , self . board [dest1 ]):
501
- self .move_checker (start [0 ], steps [0 ], player )
498
+ if self .is_point_open (player , board [dest1 ]):
499
+ self .move_checker (board , start [0 ], steps [0 ], player )
502
500
move1_legal = True
503
501
else :
504
502
if self .allow_bear_off [player ]:
505
- self .move_checker (start [0 ], steps [0 ], player )
503
+ self .move_checker (board , start [0 ], steps [0 ], player )
506
504
move1_legal = True
507
505
if not move1_legal :
508
506
return False
509
507
if dest2 in dest_range :
510
- if self .is_point_open (player , self . board [dest2 ]):
508
+ if self .is_point_open (player , board [dest2 ]):
511
509
move2_legal = True
512
510
else :
513
511
if self .allow_bear_off [player ]:
514
512
move2_legal = True
515
513
return move1_legal and move2_legal
516
514
517
- def move_checker (self , start , steps , player ):
515
+ def move_checker (self , board , start , steps , player ):
518
516
"""Move a checker from starting point by a given number of steps"""
519
517
dest = start + steps
520
518
dest_range = range (0 , 24 )
521
- self . board [start ][player ] -= 1
519
+ board [start ][player ] -= 1
522
520
if dest in dest_range :
523
- self . board [dest ][player ] += 1
524
- if self .checkers_at_home (player ) == 15 :
521
+ board [dest ][player ] += 1
522
+ if self .checkers_at_home (board , player ) == 15 :
525
523
self .allow_bear_off [player ] = True
526
524
527
525
def is_point_open (self , player , point ):
@@ -530,3 +528,19 @@ def is_point_open(self, player, point):
530
528
move a checker to a point only if it is open."""
531
529
opponent = 'B' if player == 'W' else 'W'
532
530
return point [opponent ] <= 1
531
+
532
+ def play_game (self , * players ):
533
+ """Play backgammon."""
534
+ state = self .initial
535
+ while True :
536
+ for player in players :
537
+ saved_dice_roll = self .dice_roll
538
+ move = player (self , state )
539
+ self .dice_roll = saved_dice_roll
540
+ if move is not None :
541
+ state = self .result (state , move )
542
+ self .dice_roll = tuple (map ((direction [player ]).__mul__ ,
543
+ random .choice (dice_rolls )))
544
+ if self .terminal_test (state ):
545
+ self .display (state )
546
+ return self .utility (state , self .to_move (self .initial ))
0 commit comments