| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -41,6 +41,9 @@ def min_value(state): | |
|
|
||
| # ______________________________________________________________________________ | ||
|
|
||
| dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) | ||
| direction = {'W' : -1, 'B' : 1} | ||
|
|
||
| def expectiminimax(state, game): | ||
| """Return the best move for a player after dice are thrown. The game tree | ||
| includes chance nodes along with min and max nodes. [Figure 5.11]""" | ||
| Expand All | @@ -66,21 +69,19 @@ def chance_node(state, action): | |
| return game.utility(res_state, player) | ||
| sum_chances = 0 | ||
| num_chances = 21 | ||
| dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) | ||
| if res_state.to_move == 'W': | ||
| for val in dice_rolls: | ||
| game.dice_roll = (-val[0], -val[1]) | ||
| sum_chances += max_value(res_state, | ||
| (-val[0], -val[1])) * (1/36 if val[0] == val[1] else 1/18) | ||
| elif res_state.to_move == 'B': | ||
| for val in dice_rolls: | ||
| game.dice_roll = val | ||
| sum_chances += min_value(res_state, val) * (1/36 if val[0] == val[1] else 1/18) | ||
| for val in dice_rolls: | ||
| game.dice_roll = tuple(map((direction[res_state.to_move]).__mul__, val)) | ||
| util = 0 | ||
| if res_state.to_move == player: | ||
| util = max_value(res_state, game.dice_roll) | ||
| else: | ||
| util = min_value(res_state, game.dice_roll) | ||
| sum_chances += util * (1/36 if val[0] == val[1] else 1/18) | ||
| return sum_chances / num_chances | ||
|
|
||
| # Body of expectiminimax: | ||
| return argmax(game.actions(state), | ||
| key=lambda a: chance_node(state, a)) | ||
| key=lambda a: chance_node(state, a), default=None) | ||
|
|
||
|
|
||
| def alphabeta_search(state, game): | ||
| Expand Down Expand Up | @@ -181,18 +182,21 @@ def query_player(game, state): | |
| game.display(state) | ||
| print("available moves: {}".format(game.actions(state))) | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityAlphabeta_search cannot have print statements in it, and cannot query the user. Rather, there can be a player who is a user; that player does the input('Your move? ') and does the prints.
Sorry, something went wrong.
All reactions
Copy link
Copy Markdown
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThe print statements are currently inside query_player.
Sorry, something went wrong.
All reactions
|
||
| print("") | ||
| move_string = input('Your move? ') | ||
| try: | ||
| move = eval(move_string) | ||
| except NameError: | ||
| move = move_string | ||
| move = None | ||
| if game.actions(state): | ||
| move_string = input('Your move? ') | ||
| try: | ||
| move = eval(move_string) | ||
| except NameError: | ||
| move = move_string | ||
| else: | ||
| print('no legal moves: passing turn to next player') | ||
| return move | ||
|
|
||
|
|
||
| def random_player(game, state): | ||
| """A player that chooses a legal move at random.""" | ||
| return random.choice(game.actions(state)) | ||
|
|
||
| return random.choice(game.actions(state)) if game.actions(state) else None | ||
|
|
||
| def alphabeta_player(game, state): | ||
| return alphabeta_search(state, game) | ||
| Expand Down Expand Up | @@ -396,47 +400,45 @@ class Backgammon(Game): | |
|
|
||
| def __init__(self): | ||
| """Initial state of the game""" | ||
| self.dice_roll = (-random.randint(1, 6), -random.randint(1, 6)) | ||
| self.dice_roll = tuple(map((direction['W']).__mul__, random.choice(dice_rolls))) | ||
| # TODO : Add bar to Board class where a blot is placed when it is hit. | ||
| point = {'W':0, 'B':0} | ||
| self.board = [point.copy() for index in range(24)] | ||
| self.board[0]['B'] = self.board[23]['W'] = 2 | ||
| self.board[5]['W'] = self.board[18]['B'] = 5 | ||
| self.board[7]['W'] = self.board[16]['B'] = 3 | ||
| self.board[11]['B'] = self.board[12]['W'] = 5 | ||
| self.allow_bear_off = {'W': False, 'B': False} | ||
|
|
||
| point = {'W' : 0, 'B' : 0} | ||
| board = [point.copy() for index in range(24)] | ||
| board[0]['B'] = board[23]['W'] = 2 | ||
| board[5]['W'] = board[18]['B'] = 5 | ||
| board[7]['W'] = board[16]['B'] = 3 | ||
| board[11]['B'] = board[12]['W'] = 5 | ||
| self.allow_bear_off = {'W' : False, 'B' : False} | ||
| self.initial = GameState(to_move='W', | ||
| utility=0, | ||
| board=self.board, | ||
| moves=self.get_all_moves(self.board, 'W')) | ||
| utility=0, | ||
| board=board, | ||
| moves=self.get_all_moves(board, 'W')) | ||
|
|
||
| def actions(self, state): | ||
| """Returns a list of legal moves for a state.""" | ||
| """Return a list of legal moves for a state.""" | ||
| player = state.to_move | ||
| moves = state.moves | ||
| if len(moves) == 1 and len(moves[0]) == 1: | ||
| return moves | ||
| legal_moves = [] | ||
| for move in moves: | ||
| board = copy.deepcopy(state.board) | ||
| if self.is_legal_move(move, self.dice_roll, player): | ||
| if self.is_legal_move(board, move, self.dice_roll, player): | ||
| legal_moves.append(move) | ||
| return legal_moves | ||
|
|
||
| def result(self, state, move): | ||
| board = copy.deepcopy(state.board) | ||
| player = state.to_move | ||
| self.move_checker(move[0], self.dice_roll[0], player) | ||
| self.move_checker(board, move[0], self.dice_roll[0], player) | ||
| if len(move) == 2: | ||
| self.move_checker(move[1], self.dice_roll[1], player) | ||
| self.move_checker(board, move[1], self.dice_roll[1], player) | ||
| to_move = ('W' if player == 'B' else 'B') | ||
| return GameState(to_move=to_move, | ||
| utility=self.compute_utility(board, move, player), | ||
| board=board, | ||
| moves=self.get_all_moves(board, to_move)) | ||
|
|
||
|
|
||
| def utility(self, state, player): | ||
| """Return the value to player; 1 for win, -1 for loss, 0 otherwise.""" | ||
| return state.utility if player == 'W' else -state.utility | ||
| Expand All | @@ -452,7 +454,7 @@ def get_all_moves(self, board, player): | |
| all_points = board | ||
| taken_points = [index for index, point in enumerate(all_points) | ||
| if point[player] > 0] | ||
| if self.checkers_at_home(player) == 1: | ||
| if self.checkers_at_home(board, player) == 1: | ||
| return [(taken_points[0], )] | ||
| moves = list(itertools.permutations(taken_points, 2)) | ||
| moves = moves + [(index, index) for index, point in enumerate(all_points) | ||
| Expand All | @@ -463,32 +465,28 @@ def display(self, state): | |
| """Display state of the game.""" | ||
| board = state.board | ||
| player = state.to_move | ||
| print("Current State : ") | ||
| print("current state : ") | ||
| for index, point in enumerate(board): | ||
| if point['W'] != 0 or point['B'] != 0: | ||
| print("Point : ", index, " W : ", point['W'], " B : ", point['B']) | ||
| print("To play : ", player) | ||
| print("point : ", index, " W : ", point['W'], " B : ", point['B']) | ||
| print("to play : ", player) | ||
|
|
||
| def compute_utility(self, board, move, player): | ||
| """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" | ||
| count = 0 | ||
| util = {'W' : 1, 'B' : '-1'} | ||
| for idx in range(0, 24): | ||
| count = count + board[idx][player] | ||
| if player == 'W' and count == 0: | ||
| return 1 | ||
| if player == 'B' and count == 0: | ||
| return -1 | ||
| return 0 | ||
| if board[idx][player] > 0: | ||
| return 0 | ||
| return util[player] | ||
|
|
||
| def checkers_at_home(self, player): | ||
| def checkers_at_home(self, board, player): | ||
| """Return the no. of checkers at home for a player.""" | ||
| sum_range = range(0, 7) if player == 'W' else range(17, 24) | ||
| count = 0 | ||
| for idx in sum_range: | ||
| count = count + self.board[idx][player] | ||
| count = count + board[idx][player] | ||
| return count | ||
|
|
||
| def is_legal_move(self, start, steps, player): | ||
| def is_legal_move(self, board, start, steps, player): | ||
| """Move is a tuple which contains starting points of checkers to be | ||
| moved during a player's turn. An on-board move is legal if both the destinations | ||
| are open. A bear-off move is the one where a checker is moved off-board. | ||
| Expand All | @@ -497,31 +495,31 @@ def is_legal_move(self, start, steps, player): | |
| dest_range = range(0, 24) | ||
| move1_legal = move2_legal = False | ||
| if dest1 in dest_range: | ||
| if self.is_point_open(player, self.board[dest1]): | ||
| self.move_checker(start[0], steps[0], player) | ||
| if self.is_point_open(player, board[dest1]): | ||
| self.move_checker(board, start[0], steps[0], player) | ||
| move1_legal = True | ||
| else: | ||
| if self.allow_bear_off[player]: | ||
| self.move_checker(start[0], steps[0], player) | ||
| self.move_checker(board, start[0], steps[0], player) | ||
| move1_legal = True | ||
| if not move1_legal: | ||
| return False | ||
| if dest2 in dest_range: | ||
| if self.is_point_open(player, self.board[dest2]): | ||
| if self.is_point_open(player, board[dest2]): | ||
| move2_legal = True | ||
| else: | ||
| if self.allow_bear_off[player]: | ||
| move2_legal = True | ||
| return move1_legal and move2_legal | ||
|
|
||
| def move_checker(self, start, steps, player): | ||
| def move_checker(self, board, start, steps, player): | ||
| """Move a checker from starting point by a given number of steps""" | ||
| dest = start + steps | ||
| dest_range = range(0, 24) | ||
| self.board[start][player] -= 1 | ||
| board[start][player] -= 1 | ||
| if dest in dest_range: | ||
| self.board[dest][player] += 1 | ||
| if self.checkers_at_home(player) == 15: | ||
| board[dest][player] += 1 | ||
| if self.checkers_at_home(board, player) == 15: | ||
| self.allow_bear_off[player] = True | ||
|
|
||
| def is_point_open(self, player, point): | ||
| Expand All | @@ -530,3 +528,19 @@ def is_point_open(self, player, point): | |
| move a checker to a point only if it is open.""" | ||
| opponent = 'B' if player == 'W' else 'W' | ||
| return point[opponent] <= 1 | ||
|
|
||
| def play_game(self, *players): | ||
| """Play backgammon.""" | ||
| state = self.initial | ||
| while True: | ||
| for player in players: | ||
| saved_dice_roll = self.dice_roll | ||
| move = player(self, state) | ||
| self.dice_roll = saved_dice_roll | ||
| if move is not None: | ||
| state = self.result(state, move) | ||
| self.dice_roll = tuple(map((direction[player]).__mul__, | ||
| random.choice(dice_rolls))) | ||
| if self.terminal_test(state): | ||
| self.display(state) | ||
| return self.utility(state, self.to_move(self.initial)) | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityI know this is not your fault, somehow the code got like this before, but expectiminimax cannot be limited to games with two dice. It should be a line-for-line implementation of the pseudocode:
EXPECTIMINIMAX(s) = UTILITY(s) if TERMINAL-TEST(s) max_a EXPECTIMINIMAX(RESULT(s, a)) if PLAYER(s)= MAX min_a EXPECTIMINIMAX(RESULT(s, a)) if PLAYER(s)= MIN ∑_r P(r)EXPECTIMINIMAX(RESULT(s, r)) if PLAYER(s)= CHANCESorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low QualityThanks @norvig for pointing this out. I can make expectiminimax generic for all stochastic games if I create another abstract class StochasticGame which extends Game. The class will have the following methods:
The expectiminimax can then use a StochasticGame object instead of game object. I am not sure if there should be a different GameState for a stochastic game. Please comment.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.