From 39c85524bd418b77b7001e8c31b5adc5e7eb2cb9 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Sat, 14 Apr 2018 00:40:24 +0530 Subject: [PATCH 1/5] Add stochastic game class --- games.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/games.py b/games.py index 23e785bab..8f753fbc2 100644 --- a/games.py +++ b/games.py @@ -256,6 +256,32 @@ def play_game(self, *players): self.display(state) return self.utility(state, self.to_move(self.initial)) +class StochasticGame(Game): + """A stochastic game includes uncertain events which influence + the moves of players at each state. To create a stochastic game, subclass + this class and implement chances and outcome along with the other + unimplemented game class methods.""" + + def chances(self, state): + """Return a list of all possible uncertain events at a state.""" + raise NotImplementedError + + def outcome(self, chance): + """Return the state which is the outcome of a chance trial.""" + raise NotImplementedError + + def play_game(self, *players): + """Play an n-person, move-alternating stochastic game.""" + state = self.initial + while True: + for player in players: + chance = random.choice(self.chances(state)) + self.outcome(chance) + move = player(self, state) + state = self.result(state, move) + if self.terminal_test(state): + self.display(state) + return self.utility(state, self.to_move(self.initial)) class Fig52Game(Game): """The game represented in [Figure 5.2]. Serves as a simple test case.""" From 9e5c88647fea7c7bc57f294e2f8b8878f550ed4e Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Wed, 18 Apr 2018 00:57:39 +0530 Subject: [PATCH 2/5] Update backgammon class --- games.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/games.py b/games.py index 8f753fbc2..f146b2de1 100644 --- a/games.py +++ b/games.py @@ -41,8 +41,6 @@ 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 @@ -266,7 +264,7 @@ def chances(self, state): """Return a list of all possible uncertain events at a state.""" raise NotImplementedError - def outcome(self, chance): + def outcome(self, state, chance): """Return the state which is the outcome of a chance trial.""" raise NotImplementedError @@ -419,7 +417,7 @@ def actions(self, state): if y == 1 or (x, y - 1) in state.board] -class Backgammon(Game): +class Backgammon(StochasticGame): """A two player game where the goal of each player is to move all the checkers off the board. The moves for each state are determined by rolling a pair of dice.""" @@ -435,6 +433,7 @@ def __init__(self): board[7]['W'] = board[16]['B'] = 3 board[11]['B'] = board[12]['W'] = 5 self.allow_bear_off = {'W' : False, 'B' : False} + self.direction = {'W' : -1, 'B' : 1} self.initial = GameState(to_move='W', utility=0, board=board, @@ -555,6 +554,15 @@ def is_point_open(self, player, point): opponent = 'B' if player == 'W' else 'W' return point[opponent] <= 1 + def chances(self, state): + """Return a list of all possible uncertain events at a state.""" + dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) + return dice_rolls + + def outcome(self, chance): + """Return the state which is the outcome of a chance trial.""" + self.dice_roll = tuple(map((self.direction[state.to_move]).__mul__, chance)) +''' def play_game(self, *players): """Play backgammon.""" state = self.initial @@ -570,3 +578,4 @@ def play_game(self, *players): if self.terminal_test(state): self.display(state) return self.utility(state, self.to_move(self.initial)) +''' From d1f5d30ac0209b9948768ff6c8fddd953cf83488 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Thu, 26 Apr 2018 00:40:16 +0530 Subject: [PATCH 3/5] Update Expectiminimax --- games.py | 75 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/games.py b/games.py index f146b2de1..2743661aa 100644 --- a/games.py +++ b/games.py @@ -8,6 +8,7 @@ infinity = float('inf') GameState = namedtuple('GameState', 'to_move, utility, board, moves') +StochasticGameState = namedtuple('StochasticGameState', 'to_move, utility, board, moves, chance') # ______________________________________________________________________________ # Minimax Search @@ -47,34 +48,33 @@ def expectiminimax(state, game): includes chance nodes along with min and max nodes. [Figure 5.11]""" player = game.to_move(state) - def max_value(state, dice_roll): + def max_value(state): v = -infinity for a in game.actions(state): v = max(v, chance_node(state, a)) - game.dice_roll = dice_roll return v - def min_value(state, dice_roll): + def min_value(state): v = infinity for a in game.actions(state): v = min(v, chance_node(state, a)) - game.dice_roll = dice_roll return v def chance_node(state, action): res_state = game.result(state, action) + game.display(res_state) if game.terminal_test(res_state): return game.utility(res_state, player) sum_chances = 0 - num_chances = 21 - for val in dice_rolls: - game.dice_roll = tuple(map((direction[res_state.to_move]).__mul__, val)) + num_chances = len(game.chances(res_state)) + for chance in game.chances(res_state): + res_state = game.outcome(res_state, chance) util = 0 if res_state.to_move == player: - util = max_value(res_state, game.dice_roll) + util = max_value(res_state) else: - util = min_value(res_state, game.dice_roll) - sum_chances += util * (1/36 if val[0] == val[1] else 1/18) + util = min_value(res_state) + sum_chances += util * game.probability(chance) return sum_chances / num_chances # Body of expectiminimax: @@ -268,13 +268,18 @@ def outcome(self, state, chance): """Return the state which is the outcome of a chance trial.""" raise NotImplementedError + def probability(self, chance): + """Return the probability of occurence of a chance.""" + raise NotImplementedError + def play_game(self, *players): """Play an n-person, move-alternating stochastic game.""" state = self.initial while True: for player in players: + print("x") chance = random.choice(self.chances(state)) - self.outcome(chance) + state = self.outcome(state, chance) move = player(self, state) state = self.result(state, move) if self.terminal_test(state): @@ -424,7 +429,6 @@ class Backgammon(StochasticGame): def __init__(self): """Initial state of the game""" - 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} board = [point.copy() for index in range(24)] @@ -434,10 +438,10 @@ def __init__(self): board[11]['B'] = board[12]['W'] = 5 self.allow_bear_off = {'W' : False, 'B' : False} self.direction = {'W' : -1, 'B' : 1} - self.initial = GameState(to_move='W', + self.initial = StochasticGameState(to_move='W', utility=0, board=board, - moves=self.get_all_moves(board, 'W')) + moves=self.get_all_moves(board, 'W'), chance=None) def actions(self, state): """Return a list of legal moves for a state.""" @@ -448,21 +452,21 @@ def actions(self, state): legal_moves = [] for move in moves: board = copy.deepcopy(state.board) - if self.is_legal_move(board, move, self.dice_roll, player): + if self.is_legal_move(board, move, state.chance, 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(board, move[0], self.dice_roll[0], player) + self.move_checker(board, move[0], state.chance[0], player) if len(move) == 2: - self.move_checker(board, move[1], self.dice_roll[1], player) + self.move_checker(board, move[1], state.chance[1], player) to_move = ('W' if player == 'B' else 'B') - return GameState(to_move=to_move, + return StochasticGameState(to_move=to_move, utility=self.compute_utility(board, move, player), board=board, - moves=self.get_all_moves(board, to_move)) + moves=self.get_all_moves(board, to_move), chance=None) def utility(self, state, player): """Return the value to player; 1 for win, -1 for loss, 0 otherwise.""" @@ -559,23 +563,18 @@ def chances(self, state): dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) return dice_rolls - def outcome(self, chance): + def outcome(self, state, chance): """Return the state which is the outcome of a chance trial.""" - self.dice_roll = tuple(map((self.direction[state.to_move]).__mul__, chance)) -''' - 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)) -''' + dice = tuple(map((self.direction[state.to_move]).__mul__, chance)) + return StochasticGameState(to_move=state.to_move, + utility=state.utility, + board=state.board, + moves=state.moves, chance=dice) + + def probability(self, chance): + """Return the probability of occurence of a dice roll.""" + return (1/36 if chance[0] == chance[1] else 1/18) + +if __name__ == "__main__": + bgm = Backgammon() + move = bgm.play_game(expectiminimax_player, expectiminimax_player) From 6d1489b3f52035b4cbb67da8b868780a1a2bae59 Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Fri, 27 Apr 2018 01:18:28 +0530 Subject: [PATCH 4/5] Fix lint issues --- games.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/games.py b/games.py index 2743661aa..65a4f1a1b 100644 --- a/games.py +++ b/games.py @@ -62,7 +62,6 @@ def min_value(state): def chance_node(state, action): res_state = game.result(state, action) - game.display(res_state) if game.terminal_test(res_state): return game.utility(res_state, player) sum_chances = 0 @@ -277,7 +276,6 @@ def play_game(self, *players): state = self.initial while True: for player in players: - print("x") chance = random.choice(self.chances(state)) state = self.outcome(state, chance) move = player(self, state) @@ -429,7 +427,6 @@ class Backgammon(StochasticGame): def __init__(self): """Initial state of the game""" - # TODO : Add bar to Board class where a blot is placed when it is hit. point = {'W' : 0, 'B' : 0} board = [point.copy() for index in range(24)] board[0]['B'] = board[23]['W'] = 2 @@ -439,9 +436,9 @@ def __init__(self): self.allow_bear_off = {'W' : False, 'B' : False} self.direction = {'W' : -1, 'B' : 1} self.initial = StochasticGameState(to_move='W', - utility=0, - board=board, - moves=self.get_all_moves(board, 'W'), chance=None) + utility=0, + board=board, + moves=self.get_all_moves(board, 'W'), chance=None) def actions(self, state): """Return a list of legal moves for a state.""" @@ -464,9 +461,9 @@ def result(self, state, move): self.move_checker(board, move[1], state.chance[1], player) to_move = ('W' if player == 'B' else 'B') return StochasticGameState(to_move=to_move, - utility=self.compute_utility(board, move, player), - board=board, - moves=self.get_all_moves(board, to_move), chance=None) + utility=self.compute_utility(board, move, player), + board=board, + moves=self.get_all_moves(board, to_move), chance=None) def utility(self, state, player): """Return the value to player; 1 for win, -1 for loss, 0 otherwise.""" @@ -559,22 +556,18 @@ def is_point_open(self, player, point): return point[opponent] <= 1 def chances(self, state): - """Return a list of all possible uncertain events at a state.""" + """Return a list of all possible dice rolls at a state.""" dice_rolls = list(itertools.combinations_with_replacement([1, 2, 3, 4, 5, 6], 2)) return dice_rolls def outcome(self, state, chance): - """Return the state which is the outcome of a chance trial.""" + """Return the state which is the outcome of a dice roll.""" dice = tuple(map((self.direction[state.to_move]).__mul__, chance)) return StochasticGameState(to_move=state.to_move, - utility=state.utility, - board=state.board, - moves=state.moves, chance=dice) + utility=state.utility, + board=state.board, + moves=state.moves, chance=dice) def probability(self, chance): """Return the probability of occurence of a dice roll.""" - return (1/36 if chance[0] == chance[1] else 1/18) - -if __name__ == "__main__": - bgm = Backgammon() - move = bgm.play_game(expectiminimax_player, expectiminimax_player) + return 1/36 if chance[0] == chance[1] else 1/18 From 1577512dc3c0631ed7a8f8284cf81a1c6d680c8f Mon Sep 17 00:00:00 2001 From: AdityaDaflapurkar Date: Tue, 1 May 2018 01:38:06 +0530 Subject: [PATCH 5/5] Correct compute_utility function --- games.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/games.py b/games.py index 65a4f1a1b..c8a8a7d8e 100644 --- a/games.py +++ b/games.py @@ -498,7 +498,7 @@ def display(self, state): def compute_utility(self, board, move, player): """If 'W' wins with this move, return 1; if 'B' wins return -1; else return 0.""" - util = {'W' : 1, 'B' : '-1'} + util = {'W' : 1, 'B' : -1} for idx in range(0, 24): if board[idx][player] > 0: return 0