Skip to content

Commit 6095265

Browse files
authored
Merge pull request #4 from aimacode/master
Update
2 parents 6b32d31 + 5889656 commit 6095265

File tree

9 files changed

+938
-142
lines changed

9 files changed

+938
-142
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and
7474
| 3.7 | Graph-Search | `graph_search` | [`search.py`][search] | Done | |
7575
| 3.11 | Breadth-First-Search | `breadth_first_graph_search` | [`search.py`][search] | Done | Included |
7676
| 3.14 | Uniform-Cost-Search | `uniform_cost_search` | [`search.py`][search] | Done | Included |
77-
| 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | |
78-
| 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | |
77+
| 3.17 | Depth-Limited-Search | `depth_limited_search` | [`search.py`][search] | Done | Included |
78+
| 3.18 | Iterative-Deepening-Search | `iterative_deepening_search` | [`search.py`][search] | Done | Included |
7979
| 3.22 | Best-First-Search | `best_first_graph_search` | [`search.py`][search] | Done | Included |
8080
| 3.24 | A\*-Search | `astar_search` | [`search.py`][search] | Done | Included |
8181
| 3.26 | Recursive-Best-First-Search | `recursive_best_first_search` | [`search.py`][search] | Done | |
@@ -111,7 +111,7 @@ Here is a table of algorithms, the figure, name of the algorithm in the book and
111111
| 10.1 | Air-Cargo-problem | `air_cargo` | [`planning.py`][planning] | Done | Included |
112112
| 10.2 | Spare-Tire-Problem | `spare_tire` | [`planning.py`][planning] | Done | Included |
113113
| 10.3 | Three-Block-Tower | `three_block_tower` | [`planning.py`][planning] | Done | Included |
114-
| 10.7 | Cake-Problem | `have_cake_and_eat_cake_too` | [`planning.py`][planning] | Done | |
114+
| 10.7 | Cake-Problem | `have_cake_and_eat_cake_too` | [`planning.py`][planning] | Done | Included |
115115
| 10.9 | Graphplan | `GraphPlan` | [`planning.py`][planning] | | |
116116
| 10.13 | Partial-Order-Planner | | | | |
117117
| 11.1 | Job-Shop-Problem-With-Resources | `job_shop_problem` | [`planning.py`][planning] | Done | |
@@ -186,4 +186,4 @@ Many thanks for contributions over the years. I got bug reports, corrected code,
186186
[rl]:../master/rl.py
187187
[search]:../master/search.py
188188
[utils]:../master/utils.py
189-
[text]:../master/text.py
189+
[text]:../master/text.py

games.py

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ def min_value(state):
4141

4242
# ______________________________________________________________________________
4343

44+
dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2))
45+
direction = {'W' : -1, 'B' : 1}
46+
4447
def expectiminimax(state, game):
4548
"""Return the best move for a player after dice are thrown. The game tree
4649
includes chance nodes along with min and max nodes. [Figure 5.11]"""
@@ -66,21 +69,19 @@ def chance_node(state, action):
6669
return game.utility(res_state, player)
6770
sum_chances = 0
6871
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)
7980
return sum_chances / num_chances
8081

8182
# Body of expectiminimax:
8283
return argmax(game.actions(state),
83-
key=lambda a: chance_node(state, a))
84+
key=lambda a: chance_node(state, a), default=None)
8485

8586

8687
def alphabeta_search(state, game):
@@ -181,18 +182,21 @@ def query_player(game, state):
181182
game.display(state)
182183
print("available moves: {}".format(game.actions(state)))
183184
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')
189194
return move
190195

191196

192197
def random_player(game, state):
193198
"""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
196200

197201
def alphabeta_player(game, state):
198202
return alphabeta_search(state, game)
@@ -396,47 +400,45 @@ class Backgammon(Game):
396400

397401
def __init__(self):
398402
"""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)))
400404
# 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}
409412
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'))
413416

414417
def actions(self, state):
415-
"""Returns a list of legal moves for a state."""
418+
"""Return a list of legal moves for a state."""
416419
player = state.to_move
417420
moves = state.moves
418421
if len(moves) == 1 and len(moves[0]) == 1:
419422
return moves
420423
legal_moves = []
421424
for move in moves:
422425
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):
424427
legal_moves.append(move)
425428
return legal_moves
426429

427430
def result(self, state, move):
428431
board = copy.deepcopy(state.board)
429432
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)
431434
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)
433436
to_move = ('W' if player == 'B' else 'B')
434437
return GameState(to_move=to_move,
435438
utility=self.compute_utility(board, move, player),
436439
board=board,
437440
moves=self.get_all_moves(board, to_move))
438441

439-
440442
def utility(self, state, player):
441443
"""Return the value to player; 1 for win, -1 for loss, 0 otherwise."""
442444
return state.utility if player == 'W' else -state.utility
@@ -452,7 +454,7 @@ def get_all_moves(self, board, player):
452454
all_points = board
453455
taken_points = [index for index, point in enumerate(all_points)
454456
if point[player] > 0]
455-
if self.checkers_at_home(player) == 1:
457+
if self.checkers_at_home(board, player) == 1:
456458
return [(taken_points[0], )]
457459
moves = list(itertools.permutations(taken_points, 2))
458460
moves = moves + [(index, index) for index, point in enumerate(all_points)
@@ -463,32 +465,28 @@ def display(self, state):
463465
"""Display state of the game."""
464466
board = state.board
465467
player = state.to_move
466-
print("Current State : ")
468+
print("current state : ")
467469
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)
471472

472473
def compute_utility(self, board, move, player):
473474
"""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'}
475476
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]
482480

483-
def checkers_at_home(self, player):
481+
def checkers_at_home(self, board, player):
484482
"""Return the no. of checkers at home for a player."""
485483
sum_range = range(0, 7) if player == 'W' else range(17, 24)
486484
count = 0
487485
for idx in sum_range:
488-
count = count + self.board[idx][player]
486+
count = count + board[idx][player]
489487
return count
490488

491-
def is_legal_move(self, start, steps, player):
489+
def is_legal_move(self, board, start, steps, player):
492490
"""Move is a tuple which contains starting points of checkers to be
493491
moved during a player's turn. An on-board move is legal if both the destinations
494492
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):
497495
dest_range = range(0, 24)
498496
move1_legal = move2_legal = False
499497
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)
502500
move1_legal = True
503501
else:
504502
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)
506504
move1_legal = True
507505
if not move1_legal:
508506
return False
509507
if dest2 in dest_range:
510-
if self.is_point_open(player, self.board[dest2]):
508+
if self.is_point_open(player, board[dest2]):
511509
move2_legal = True
512510
else:
513511
if self.allow_bear_off[player]:
514512
move2_legal = True
515513
return move1_legal and move2_legal
516514

517-
def move_checker(self, start, steps, player):
515+
def move_checker(self, board, start, steps, player):
518516
"""Move a checker from starting point by a given number of steps"""
519517
dest = start + steps
520518
dest_range = range(0, 24)
521-
self.board[start][player] -= 1
519+
board[start][player] -= 1
522520
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:
525523
self.allow_bear_off[player] = True
526524

527525
def is_point_open(self, player, point):
@@ -530,3 +528,19 @@ def is_point_open(self, player, point):
530528
move a checker to a point only if it is open."""
531529
opponent = 'B' if player == 'W' else 'W'
532530
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))
File renamed without changes.

knowledge.ipynb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,10 +1233,8 @@
12331233
]
12341234
},
12351235
{
1236-
"cell_type": "code",
1237-
"execution_count": null,
1236+
"cell_type": "markdown",
12381237
"metadata": {},
1239-
"outputs": [],
12401238
"source": [
12411239
"Lets look at the pseudocode for this algorithm"
12421240
]

learning_apps.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,15 +93,15 @@
9393
"Training images size: (60000, 784)\n",
9494
"Training labels size: (60000,)\n",
9595
"Testing images size: (10000, 784)\n",
96-
"Training labels size: (10000,)\n"
96+
"Testing labels size: (10000,)\n"
9797
]
9898
}
9999
],
100100
"source": [
101101
"print(\"Training images size:\", train_img.shape)\n",
102102
"print(\"Training labels size:\", train_lbl.shape)\n",
103103
"print(\"Testing images size:\", test_img.shape)\n",
104-
"print(\"Training labels size:\", test_lbl.shape)"
104+
"print(\"Testing labels size:\", test_lbl.shape)"
105105
]
106106
},
107107
{

0 commit comments

Comments
 (0)