Upload 52 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- Alpha_MCTS.py +120 -0
- Alpha_MCTS_Parallel.py +128 -0
- Alpha_Zero.py +143 -0
- Games/2048/2048.py +41 -0
- Games/2048/2048NN.py +0 -0
- Games/Chess/Chess.py +41 -0
- Games/Chess/ChessNN.py +63 -0
- Games/Chess/Stokfish.py +1 -0
- Games/ConnectFour/ConnectFour.py +76 -0
- Games/ConnectFour/ConnectFourNN.py +61 -0
- Games/ConnectFour/__pycache__/ConnectFour.cpython-310.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFour.cpython-311.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFour.cpython-312.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFour.cpython-313.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFourNN.cpython-310.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFourNN.cpython-311.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFourNN.cpython-312.pyc +0 -0
- Games/ConnectFour/__pycache__/ConnectFourNN.cpython-313.pyc +0 -0
- Games/ConnectFour/models_n_optimizers/model.pt +3 -0
- Games/ConnectFour/models_n_optimizers/optimizer.pt +3 -0
- Games/TicTacToe/TicTacToe.py +59 -0
- Games/TicTacToe/TicTacToeNN.py +61 -0
- Games/TicTacToe/__pycache__/TicTacToe.cpython-310.pyc +0 -0
- Games/TicTacToe/__pycache__/TicTacToe.cpython-311.pyc +0 -0
- Games/TicTacToe/__pycache__/TicTacToe.cpython-313.pyc +0 -0
- Games/TicTacToe/__pycache__/TicTacToeNN.cpython-310.pyc +0 -0
- Games/TicTacToe/__pycache__/TicTacToeNN.cpython-311.pyc +0 -0
- Games/TicTacToe/__pycache__/TicTacToeNN.cpython-313.pyc +0 -0
- Games/TicTacToe/models_n_optimizers/model.pt +3 -0
- Games/TicTacToe/models_n_optimizers/model_non_parallel.pt +3 -0
- Games/TicTacToe/models_n_optimizers/optimizer.pt +3 -0
- Games/TicTacToe/models_n_optimizers/optimizer_non_parallel.pt +3 -0
- Games/game.py +41 -0
- MCTS.py +104 -0
- Play.py +86 -0
- Train.py +50 -0
- __pycache__/Alpha_MCTS.cpython-310.pyc +0 -0
- __pycache__/Alpha_MCTS.cpython-311.pyc +0 -0
- __pycache__/Alpha_MCTS.cpython-313.pyc +0 -0
- __pycache__/Alpha_MCTS_Parallel.cpython-310.pyc +0 -0
- __pycache__/Alpha_MCTS_Parallel.cpython-311.pyc +0 -0
- __pycache__/Alpha_MCTS_Parallel.cpython-313.pyc +0 -0
- __pycache__/Alpha_Zero.cpython-310.pyc +0 -0
- __pycache__/Alpha_Zero.cpython-311.pyc +0 -0
- __pycache__/Alpha_Zero.cpython-313.pyc +0 -0
- __pycache__/Alpha_Zero_Parallel.cpython-310.pyc +0 -0
- __pycache__/Alpha_Zero_Parallel.cpython-311.pyc +0 -0
- __pycache__/Alpha_Zero_Parallel.cpython-313.pyc +0 -0
- __pycache__/Arena.cpython-313.pyc +0 -0
- requirements.txt +64 -0
Alpha_MCTS.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
class Node:
|
| 6 |
+
|
| 7 |
+
def __init__(self, game, args, state, parent = None, action = None, prob = 0, visits = 0):
|
| 8 |
+
self.game = game
|
| 9 |
+
self.args = args
|
| 10 |
+
self.state = state
|
| 11 |
+
self.parent = parent
|
| 12 |
+
self.action = action
|
| 13 |
+
self.prob = prob
|
| 14 |
+
|
| 15 |
+
self.children = []
|
| 16 |
+
self.visits = visits
|
| 17 |
+
self.value = 0
|
| 18 |
+
|
| 19 |
+
def leaf_or_not(self):
|
| 20 |
+
return len(self.children) > 0
|
| 21 |
+
|
| 22 |
+
def search(self):
|
| 23 |
+
best_child = None
|
| 24 |
+
best_ucb = -np.inf
|
| 25 |
+
for child in self.children:
|
| 26 |
+
ucb = self.get_ucb(child)
|
| 27 |
+
if best_ucb < ucb:
|
| 28 |
+
best_ucb = ucb
|
| 29 |
+
best_child = child
|
| 30 |
+
return best_child
|
| 31 |
+
|
| 32 |
+
def get_ucb(self, child):
|
| 33 |
+
if child.visits == 0:
|
| 34 |
+
q_value = 0
|
| 35 |
+
else:
|
| 36 |
+
q_value = 1 - (((child.value / child.visits) + 1) / 2)
|
| 37 |
+
|
| 38 |
+
return q_value + self.args['EXPLORATION_CONSTANT'] * (math.sqrt(self.visits) / (child.visits + 1)) * child.prob
|
| 39 |
+
|
| 40 |
+
def expand(self, policy):
|
| 41 |
+
|
| 42 |
+
for move, prob in enumerate(policy):
|
| 43 |
+
if prob > 0:
|
| 44 |
+
child = self.state.copy()
|
| 45 |
+
child = self.game.make_move(child, move, 1)
|
| 46 |
+
if self.args["ADVERSARIAL"]:
|
| 47 |
+
child = self.game.change_perspective(child, player = -1)
|
| 48 |
+
|
| 49 |
+
child = Node(self.game, self.args, child, self, move, prob)
|
| 50 |
+
self.children.append(child)
|
| 51 |
+
|
| 52 |
+
def backpropagate(self,state_value):
|
| 53 |
+
self.value += state_value
|
| 54 |
+
self.visits += 1
|
| 55 |
+
if self.args["ADVERSARIAL"]:
|
| 56 |
+
state_value = self.game.get_opponent_value(state_value)
|
| 57 |
+
if self.parent is not None:
|
| 58 |
+
self.parent.backpropagate(state_value)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class Alpha_MCTS:
|
| 62 |
+
|
| 63 |
+
def __init__(self, game, args, model):
|
| 64 |
+
self.game = game
|
| 65 |
+
self.args = args
|
| 66 |
+
self.model = model
|
| 67 |
+
|
| 68 |
+
@torch.no_grad()
|
| 69 |
+
def search(self, state):
|
| 70 |
+
root = Node(self.game, self.args, state, visits = 1)
|
| 71 |
+
|
| 72 |
+
if self.args["ROOT_RANDOMNESS"]:
|
| 73 |
+
policy, _ = self.model(
|
| 74 |
+
torch.tensor(self.game.get_encoded_state(state), device = self.model.device
|
| 75 |
+
).unsqueeze(0))
|
| 76 |
+
|
| 77 |
+
policy = torch.softmax(policy, axis = 1).squeeze(0).cpu().numpy()
|
| 78 |
+
|
| 79 |
+
policy = (1 - self.args["DIRICHLET_EPSILON"]) * policy + self.args["DIRICHLET_EPSILON"] * np.random.dirichlet([self.args["DIRICHLET_ALPHA"]] * self.game.possible_state)
|
| 80 |
+
|
| 81 |
+
valid_state = self.game.get_valid_moves(state)
|
| 82 |
+
policy *= valid_state
|
| 83 |
+
policy /= np.sum(policy)
|
| 84 |
+
root.expand(policy)
|
| 85 |
+
|
| 86 |
+
for _ in range(self.args["NO_OF_SEARCHES"]):
|
| 87 |
+
node = root
|
| 88 |
+
no_moves = 0
|
| 89 |
+
while node.leaf_or_not():
|
| 90 |
+
node = node.search()
|
| 91 |
+
no_moves += 1
|
| 92 |
+
|
| 93 |
+
is_terminal, value = self.game.know_terminal_value(node.state, node.action)
|
| 94 |
+
|
| 95 |
+
if self.args["ADVERSARIAL"]:
|
| 96 |
+
value = self.game.get_opponent_value(value)
|
| 97 |
+
|
| 98 |
+
if not is_terminal:
|
| 99 |
+
|
| 100 |
+
policy, value = self.model(
|
| 101 |
+
torch.tensor(self.game.get_encoded_state(node.state), device = self.model.device).unsqueeze(0)
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
valid_state = self.game.get_valid_moves(node.state)
|
| 105 |
+
policy = torch.softmax(policy, axis = 1).squeeze(0).cpu().numpy().astype(np.float64)
|
| 106 |
+
|
| 107 |
+
policy *= valid_state
|
| 108 |
+
policy /= np.sum(policy)
|
| 109 |
+
|
| 110 |
+
value = value.item()
|
| 111 |
+
|
| 112 |
+
node.expand(policy)
|
| 113 |
+
|
| 114 |
+
node.backpropagate(value)
|
| 115 |
+
|
| 116 |
+
move_probability = np.zeros(self.game.possible_state)
|
| 117 |
+
for children in root.children:
|
| 118 |
+
move_probability[children.action] = children.visits
|
| 119 |
+
move_probability /= np.sum(move_probability)
|
| 120 |
+
return move_probability
|
Alpha_MCTS_Parallel.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import torch
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
class Node:
|
| 6 |
+
|
| 7 |
+
def __init__(self, game, args, state, parent = None, action = None, prob = 0, visits = 0):
|
| 8 |
+
self.game = game
|
| 9 |
+
self.args = args
|
| 10 |
+
self.state = state
|
| 11 |
+
self.parent = parent
|
| 12 |
+
self.action = action
|
| 13 |
+
self.prob = prob
|
| 14 |
+
|
| 15 |
+
self.children = []
|
| 16 |
+
self.visits = visits
|
| 17 |
+
self.value = 0
|
| 18 |
+
|
| 19 |
+
def leaf_or_not(self):
|
| 20 |
+
return len(self.children) > 0
|
| 21 |
+
|
| 22 |
+
def search(self):
|
| 23 |
+
best_child = None
|
| 24 |
+
best_ucb = -np.inf
|
| 25 |
+
for child in self.children:
|
| 26 |
+
ucb = self.get_ucb(child)
|
| 27 |
+
if best_ucb < ucb:
|
| 28 |
+
best_ucb = ucb
|
| 29 |
+
best_child = child
|
| 30 |
+
return best_child
|
| 31 |
+
|
| 32 |
+
def get_ucb(self, child):
|
| 33 |
+
if child.visits == 0:
|
| 34 |
+
q_value = 0
|
| 35 |
+
else:
|
| 36 |
+
q_value = 1 - ((child.value / child.visits) + 1) / 2
|
| 37 |
+
|
| 38 |
+
return q_value + self.args['EXPLORATION_CONSTANT'] * (math.sqrt(self.visits) / (child.visits + 1)) * child.prob
|
| 39 |
+
|
| 40 |
+
def expand(self, policy):
|
| 41 |
+
|
| 42 |
+
for move, prob in enumerate(policy):
|
| 43 |
+
if prob > 0:
|
| 44 |
+
child = self.state.copy()
|
| 45 |
+
child = self.game.make_move(child, move, 1)
|
| 46 |
+
if self.args["ADVERSARIAL"]:
|
| 47 |
+
child = self.game.change_perspective(child, player = -1)
|
| 48 |
+
|
| 49 |
+
child = Node(self.game, self.args, child, self, move, prob)
|
| 50 |
+
self.children.append(child)
|
| 51 |
+
|
| 52 |
+
def backpropagate(self,state_value):
|
| 53 |
+
self.value += state_value
|
| 54 |
+
self.visits += 1
|
| 55 |
+
if self.args["ADVERSARIAL"]:
|
| 56 |
+
state_value = self.game.get_opponent_value(state_value)
|
| 57 |
+
if self.parent is not None:
|
| 58 |
+
self.parent.backpropagate(state_value)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
class Alpha_MCTS:
|
| 62 |
+
|
| 63 |
+
def __init__(self, game, args, model):
|
| 64 |
+
self.game = game
|
| 65 |
+
self.args = args
|
| 66 |
+
self.model = model
|
| 67 |
+
|
| 68 |
+
@torch.no_grad()
|
| 69 |
+
def search(self, states, spGames):
|
| 70 |
+
|
| 71 |
+
policy, _ = self.model(
|
| 72 |
+
torch.tensor(self.game.get_encoded_state(states), device = self.model.device
|
| 73 |
+
))
|
| 74 |
+
|
| 75 |
+
policy = torch.softmax(policy, axis = 1).cpu().numpy()
|
| 76 |
+
|
| 77 |
+
if self.args["ROOT_RANDOMNESS"]:
|
| 78 |
+
policy = (1 - self.args["DIRICHLET_EPSILON"]) * policy + self.args["DIRICHLET_EPSILON"] * np.random.dirichlet([self.args["DIRICHLET_ALPHA"]] * self.game.possible_state, size = policy.shape[0])
|
| 79 |
+
|
| 80 |
+
for i, spg in enumerate(spGames):
|
| 81 |
+
spg_policy = policy[i]
|
| 82 |
+
valid_state = self.game.get_valid_moves(states[i])
|
| 83 |
+
spg_policy *= valid_state
|
| 84 |
+
spg_policy /= np.sum(spg_policy)
|
| 85 |
+
|
| 86 |
+
spg.root = Node(self.game, self.args, states[i], visits = 1)
|
| 87 |
+
spg.root.expand(spg_policy)
|
| 88 |
+
|
| 89 |
+
for _ in range(self.args["NO_OF_SEARCHES"]):
|
| 90 |
+
|
| 91 |
+
for spg in spGames:
|
| 92 |
+
spg.node = None
|
| 93 |
+
node = spg.root
|
| 94 |
+
|
| 95 |
+
while node.leaf_or_not():
|
| 96 |
+
node = node.search()
|
| 97 |
+
|
| 98 |
+
is_terminal, value = self.game.know_terminal_value(node.state, node.action)
|
| 99 |
+
|
| 100 |
+
if self.args["ADVERSARIAL"]:
|
| 101 |
+
value = self.game.get_opponent_value(value)
|
| 102 |
+
|
| 103 |
+
if is_terminal:
|
| 104 |
+
node.backpropagate(value)
|
| 105 |
+
else:
|
| 106 |
+
spg.node = node
|
| 107 |
+
|
| 108 |
+
expandabel_spgs = [mapping_index for mapping_index in range(len(spGames)) if spGames[mapping_index].node is not None]
|
| 109 |
+
|
| 110 |
+
if len(expandabel_spgs) > 0:
|
| 111 |
+
states = np.stack([spGames[mapping_index].node.state for mapping_index in expandabel_spgs])
|
| 112 |
+
policy, value = self.model(
|
| 113 |
+
torch.tensor(self.game.get_encoded_state(states), device = self.model.device)
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
policy = torch.softmax(policy, axis = 1).cpu().numpy().astype(np.float64)
|
| 117 |
+
value = value.cpu().numpy()
|
| 118 |
+
|
| 119 |
+
for i, mapping_index in enumerate(expandabel_spgs):
|
| 120 |
+
node = spGames[mapping_index].node
|
| 121 |
+
spg_policy, spg_value = policy[i], value[i]
|
| 122 |
+
valid_state = self.game.get_valid_moves(node.state)
|
| 123 |
+
|
| 124 |
+
spg_policy *= valid_state
|
| 125 |
+
spg_policy /= np.sum(spg_policy)
|
| 126 |
+
|
| 127 |
+
node.expand(spg_policy)
|
| 128 |
+
node.backpropagate(spg_value)
|
Alpha_Zero.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import random
|
| 3 |
+
import copy
|
| 4 |
+
import numpy as np
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
|
| 9 |
+
from tqdm import trange
|
| 10 |
+
from Alpha_MCTS import Alpha_MCTS
|
| 11 |
+
from Arena import Arena
|
| 12 |
+
|
| 13 |
+
class Colors:
|
| 14 |
+
RESET = "\033[0m"
|
| 15 |
+
RED = "\033[91m"
|
| 16 |
+
GREEN = "\033[92m"
|
| 17 |
+
YELLOW = "\033[93m"
|
| 18 |
+
BLUE = "\033[94m"
|
| 19 |
+
MAGENTA = "\033[95m"
|
| 20 |
+
CYAN = "\033[96m"
|
| 21 |
+
WHITE = "\033[97m"
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class Alpha_Zero:
|
| 25 |
+
|
| 26 |
+
def __init__(self, game, args, model, optimizer):
|
| 27 |
+
self.game = game
|
| 28 |
+
self.args = args
|
| 29 |
+
self.model = model
|
| 30 |
+
self.optimizer = optimizer
|
| 31 |
+
self.mcts = Alpha_MCTS(game, args, model)
|
| 32 |
+
|
| 33 |
+
def self_play(self):
|
| 34 |
+
|
| 35 |
+
single_game_memory = []
|
| 36 |
+
player = 1
|
| 37 |
+
state = self.game.initialise_state()
|
| 38 |
+
|
| 39 |
+
while True:
|
| 40 |
+
neutral_state = self.game.change_perspective(state, player) if self.args["ADVERSARIAL"] else state
|
| 41 |
+
prob = self.mcts.search(neutral_state)
|
| 42 |
+
|
| 43 |
+
single_game_memory.append((neutral_state, prob, player))
|
| 44 |
+
|
| 45 |
+
temp_prob = prob ** (1 / self.args["TEMPERATURE"])
|
| 46 |
+
|
| 47 |
+
temp_prob[temp_prob == 0] = - np.inf
|
| 48 |
+
|
| 49 |
+
temp_prob = torch.softmax(torch.tensor(temp_prob), axis = 0).cpu().numpy()
|
| 50 |
+
|
| 51 |
+
move = np.random.choice(self.game.possible_state, p = temp_prob)
|
| 52 |
+
|
| 53 |
+
state = self.game.make_move(state, move, player)
|
| 54 |
+
is_terminal, value = self.game.know_terminal_value(state, move)
|
| 55 |
+
|
| 56 |
+
if is_terminal:
|
| 57 |
+
return_memory = []
|
| 58 |
+
for return_state, return_action_prob, return_player in single_game_memory:
|
| 59 |
+
if self.args["ADVERSARIAL"]:
|
| 60 |
+
return_value = value if return_player == player else self.game.get_opponent_value(value)
|
| 61 |
+
else:
|
| 62 |
+
return_value = value
|
| 63 |
+
|
| 64 |
+
return_memory.append((
|
| 65 |
+
self.game.get_encoded_state(return_state),
|
| 66 |
+
return_action_prob,
|
| 67 |
+
return_value
|
| 68 |
+
))
|
| 69 |
+
return return_memory
|
| 70 |
+
|
| 71 |
+
if self.args["ADVERSARIAL"]:
|
| 72 |
+
player = self.game.get_opponent(player)
|
| 73 |
+
|
| 74 |
+
def train(self, memory):
|
| 75 |
+
|
| 76 |
+
random.shuffle(memory)
|
| 77 |
+
|
| 78 |
+
for batch_start in range(0, len(memory), self.args["BATCH_SIZE"]):
|
| 79 |
+
batch_end = batch_start + self.args["BATCH_SIZE"]
|
| 80 |
+
|
| 81 |
+
training_memory = memory[batch_start : batch_end]
|
| 82 |
+
|
| 83 |
+
state, action_prob, value = zip(*training_memory)
|
| 84 |
+
|
| 85 |
+
state, action_prob, value = np.array(state), np.array(action_prob), np.array(value).reshape(-1, 1)
|
| 86 |
+
|
| 87 |
+
state = torch.tensor(state, device = self.model.device, dtype=torch.float32)
|
| 88 |
+
policy_targets = torch.tensor(action_prob, device = self.model.device, dtype=torch.float32)
|
| 89 |
+
value_targets = torch.tensor(value, device = self.model.device, dtype=torch.float32)
|
| 90 |
+
|
| 91 |
+
out_policy, out_value = self.model(state)
|
| 92 |
+
|
| 93 |
+
policy_loss = F.cross_entropy(out_policy, policy_targets)
|
| 94 |
+
value_loss = F.mse_loss(out_value, value_targets)
|
| 95 |
+
loss = policy_loss + value_loss
|
| 96 |
+
|
| 97 |
+
self.optimizer.zero_grad()
|
| 98 |
+
loss.backward()
|
| 99 |
+
self.optimizer.step()
|
| 100 |
+
|
| 101 |
+
def learn(self):
|
| 102 |
+
try:
|
| 103 |
+
model_path = os.path.join(self.args["MODEL_PATH"], 'model.pt')
|
| 104 |
+
optimizer_path = os.path.join(self.args["MODEL_PATH"], 'optimizer.pt')
|
| 105 |
+
|
| 106 |
+
self.model.load_state_dict(torch.load(model_path))
|
| 107 |
+
self.optimizer.load_state_dict(torch.load(optimizer_path))
|
| 108 |
+
except:
|
| 109 |
+
print(Colors.RED + "UNABLE TO LOAD MODEL")
|
| 110 |
+
print(Colors.GREEN + "SETTING UP NEW MODEL..." + Colors.RESET)
|
| 111 |
+
|
| 112 |
+
else:
|
| 113 |
+
print(Colors.GREEN + "MODEL FOUND\nLOADING MODEL..." + Colors.RESET)
|
| 114 |
+
finally:
|
| 115 |
+
|
| 116 |
+
initial_model = copy.copy(self.model)
|
| 117 |
+
|
| 118 |
+
for iteration in range(self.args["NO_ITERATIONS"]):
|
| 119 |
+
memory = []
|
| 120 |
+
|
| 121 |
+
print(Colors.BLUE + "\nIteration no: " , iteration + 1, Colors.RESET)
|
| 122 |
+
|
| 123 |
+
print(Colors.YELLOW + "Self Play" + Colors.RESET)
|
| 124 |
+
self.model.eval()
|
| 125 |
+
|
| 126 |
+
for _ in trange(self.args["SELF_PLAY_ITERATIONS"]):
|
| 127 |
+
memory += self.self_play()
|
| 128 |
+
|
| 129 |
+
print(Colors.YELLOW + "Training..." + Colors.RESET)
|
| 130 |
+
self.model.train()
|
| 131 |
+
for _ in trange(self.args["EPOCHS"]):
|
| 132 |
+
self.train(memory)
|
| 133 |
+
|
| 134 |
+
print(Colors.YELLOW + "Testing..." + Colors.RESET)
|
| 135 |
+
self.model.eval()
|
| 136 |
+
wins, draws, defeats = Arena(self.game, self.args, self.model, initial_model)
|
| 137 |
+
print(Colors.GREEN + "Testing Completed" + Colors.WHITE + "\nTrained Model Stats:")
|
| 138 |
+
print(Colors.GREEN, "Wins: ", wins, Colors.RESET, "|", Colors.RED, "Loss: ", defeats, Colors.RESET, "|", Colors.WHITE," Draw: ", draws, Colors.RESET)
|
| 139 |
+
|
| 140 |
+
print(Colors.YELLOW + "Saving Model...")
|
| 141 |
+
torch.save(self.model.state_dict(), os.path.join(self.args["MODEL_PATH"], "model_non_parallel.pt"))
|
| 142 |
+
torch.save(self.optimizer.state_dict(), os.path.join(self.args["MODEL_PATH"], "optimizer_non_parallel.pt"))
|
| 143 |
+
print("Saved!" + Colors.RESET)
|
Games/2048/2048.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""NOTE : THIS IS THE FORMAT YOU SHOULD FOLLOW TO MAKE GAMES"""
|
| 2 |
+
|
| 3 |
+
class GAME:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.row = 4
|
| 6 |
+
self.col = 4
|
| 7 |
+
self.possible_state = self.row * self.col # no of possible state game can have (no of possible moves)
|
| 8 |
+
|
| 9 |
+
def initialise_state(self):
|
| 10 |
+
"""NOTE: this fuction creates starting position for your game"""
|
| 11 |
+
|
| 12 |
+
def get_valid_moves(self, state):
|
| 13 |
+
""" NOTE: inputs state and output all possible valid moves"""
|
| 14 |
+
|
| 15 |
+
def know_terminal_value(self, state, action):
|
| 16 |
+
"""NOTE: input state and action and the output will be tuple True
|
| 17 |
+
if is terminal state and False if not and value 0 is
|
| 18 |
+
player lost and 1 if the player won the game"""
|
| 19 |
+
|
| 20 |
+
def make_move(self, state, action, player):
|
| 21 |
+
"""NOTE: input a action and a state and a player, the
|
| 22 |
+
action is taken on given state and output is the final state"""
|
| 23 |
+
|
| 24 |
+
def get_encoded_state(self, state):
|
| 25 |
+
"""input a state the output should be the encoded state that will be
|
| 26 |
+
given to the neural network"""
|
| 27 |
+
|
| 28 |
+
def get_opponent(self, player):
|
| 29 |
+
"""NOTE: taking the input of current player this should change the
|
| 30 |
+
player to the opponent"""
|
| 31 |
+
return -1 * player
|
| 32 |
+
|
| 33 |
+
def get_opponent_value(self, value):
|
| 34 |
+
"""this should switch the reward obtained from the know_terminal_state()
|
| 35 |
+
to the reward of the opponent"""
|
| 36 |
+
return -1 * value
|
| 37 |
+
|
| 38 |
+
def change_perspective(self, state, player = -1):
|
| 39 |
+
"""this function taking input state and player flip the board and return
|
| 40 |
+
the game with the perspective of the opponent"""
|
| 41 |
+
return player * state
|
Games/2048/2048NN.py
ADDED
|
File without changes
|
Games/Chess/Chess.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""NOTE : THIS IS THE FORMAT YOU SHOULD FOLLOW TO MAKE GAMES"""
|
| 2 |
+
|
| 3 |
+
class GAME:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.row = 8
|
| 6 |
+
self.col = 8
|
| 7 |
+
self.possible_state = self.row * self.col
|
| 8 |
+
|
| 9 |
+
def initialise_state(self):
|
| 10 |
+
"""NOTE: this fuction creates starting position for your game"""
|
| 11 |
+
|
| 12 |
+
def get_valid_moves(self, state):
|
| 13 |
+
""" NOTE: inputs state and output all possible valid moves"""
|
| 14 |
+
|
| 15 |
+
def know_terminal_value(self, state, action):
|
| 16 |
+
"""NOTE: input state and action and the output will be tuple True
|
| 17 |
+
if is terminal state and False if not and value 0 is
|
| 18 |
+
player lost and 1 if the player won the game"""
|
| 19 |
+
|
| 20 |
+
def make_move(self, state, action, player):
|
| 21 |
+
"""NOTE: input a action and a state and a player, the
|
| 22 |
+
action is taken on given state and output is the final state"""
|
| 23 |
+
|
| 24 |
+
def get_encoded_state(self, state):
|
| 25 |
+
"""input a state the output should be the encoded state that will be
|
| 26 |
+
given to the neural network"""
|
| 27 |
+
|
| 28 |
+
def get_opponent(self, player):
|
| 29 |
+
"""NOTE: taking the input of current player this should change the
|
| 30 |
+
player to the opponent"""
|
| 31 |
+
return -1 * player
|
| 32 |
+
|
| 33 |
+
def get_opponent_value(self, value):
|
| 34 |
+
"""this should switch the reward obtained from the know_terminal_state()
|
| 35 |
+
to the reward of the opponent"""
|
| 36 |
+
return -1 * value
|
| 37 |
+
|
| 38 |
+
def change_perspective(self, state, player = -1):
|
| 39 |
+
"""this function taking input state and player flip the board and return
|
| 40 |
+
the game with the perspective of the opponent"""
|
| 41 |
+
return player * state
|
Games/Chess/ChessNN.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
class ResNet(nn.Module):
|
| 5 |
+
def __init__(self, game, num_resBlocks, num_hidden, device):
|
| 6 |
+
super().__init__()
|
| 7 |
+
self.device = device
|
| 8 |
+
self.startBlock = nn.Sequential(
|
| 9 |
+
nn.Conv2d(3, num_hidden, kernel_size=3, padding=1),
|
| 10 |
+
nn.BatchNorm2d(num_hidden),
|
| 11 |
+
nn.ReLU(),
|
| 12 |
+
nn.Conv2d(3, num_hidden, kernel_size=3, padding=1),
|
| 13 |
+
nn.BatchNorm2d(num_hidden),
|
| 14 |
+
nn.ReLU()
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
self.backBone = nn.ModuleList(
|
| 18 |
+
[ResBlock(num_hidden) for i in range(num_resBlocks)]
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
self.policyHead = nn.Sequential(
|
| 22 |
+
nn.Conv2d(num_hidden, 128, kernel_size=3, padding=1),
|
| 23 |
+
nn.BatchNorm2d(128),
|
| 24 |
+
nn.ReLU(),
|
| 25 |
+
nn.Flatten(),
|
| 26 |
+
nn.Linear(128 * game.row * game.col, game.possible_state)
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
self.valueHead = nn.Sequential(
|
| 30 |
+
nn.Conv2d(num_hidden, 3, kernel_size=3, padding=1),
|
| 31 |
+
nn.BatchNorm2d(3),
|
| 32 |
+
nn.ReLU(),
|
| 33 |
+
nn.Flatten(),
|
| 34 |
+
nn.Linear(3 * game.row * game.col, 1),
|
| 35 |
+
nn.Tanh()
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
self.to(device)
|
| 39 |
+
|
| 40 |
+
def forward(self, x):
|
| 41 |
+
x = self.startBlock(x)
|
| 42 |
+
for resBlock in self.backBone:
|
| 43 |
+
x = resBlock(x)
|
| 44 |
+
policy = self.policyHead(x)
|
| 45 |
+
value = self.valueHead(x)
|
| 46 |
+
return policy, value
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class ResBlock(nn.Module):
|
| 50 |
+
def __init__(self, num_hidden):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.conv1 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 53 |
+
self.bn1 = nn.BatchNorm2d(num_hidden)
|
| 54 |
+
self.conv2 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 55 |
+
self.bn2 = nn.BatchNorm2d(num_hidden)
|
| 56 |
+
|
| 57 |
+
def forward(self, x):
|
| 58 |
+
residual = x
|
| 59 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 60 |
+
x = self.bn2(self.conv2(x))
|
| 61 |
+
x += residual
|
| 62 |
+
x = F.relu(x)
|
| 63 |
+
return x
|
Games/Chess/Stokfish.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import Stokfish
|
Games/ConnectFour/ConnectFour.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
class ConnectFour:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.row = 6
|
| 6 |
+
self.col = 7
|
| 7 |
+
self.possible_state = self.col
|
| 8 |
+
self.in_a_row = 4
|
| 9 |
+
|
| 10 |
+
def __repr__(self):
|
| 11 |
+
return "ConnectFour"
|
| 12 |
+
|
| 13 |
+
def initialise_state(self):
|
| 14 |
+
return np.zeros((self.row, self.col))
|
| 15 |
+
|
| 16 |
+
def make_move(self, state, action, player):
|
| 17 |
+
row = np.max(np.where(state[:, action] == 0))
|
| 18 |
+
state[row, action] = player
|
| 19 |
+
return state
|
| 20 |
+
|
| 21 |
+
def get_valid_moves(self, state):
|
| 22 |
+
return (state[0] == 0).astype(np.uint8)
|
| 23 |
+
|
| 24 |
+
def check_win(self, state, action):
|
| 25 |
+
if action == None:
|
| 26 |
+
return False
|
| 27 |
+
|
| 28 |
+
row = np.min(np.where(state[:, action] != 0))
|
| 29 |
+
column = action
|
| 30 |
+
player = state[row][column]
|
| 31 |
+
|
| 32 |
+
def count(offset_row, offset_column):
|
| 33 |
+
for i in range(1, self.in_a_row):
|
| 34 |
+
r = row + offset_row * i
|
| 35 |
+
c = action + offset_column * i
|
| 36 |
+
if (
|
| 37 |
+
r < 0
|
| 38 |
+
or r >= self.row
|
| 39 |
+
or c < 0
|
| 40 |
+
or c >= self.col
|
| 41 |
+
or state[r][c] != player
|
| 42 |
+
):
|
| 43 |
+
return i - 1
|
| 44 |
+
return self.in_a_row - 1
|
| 45 |
+
|
| 46 |
+
return (
|
| 47 |
+
count(1, 0) >= self.in_a_row - 1 # vertical
|
| 48 |
+
or (count(0, 1) + count(0, -1)) >= self.in_a_row - 1 # horizontal
|
| 49 |
+
or (count(1, 1) + count(-1, -1)) >= self.in_a_row - 1 # top left diagonal
|
| 50 |
+
or (count(1, -1) + count(-1, 1)) >= self.in_a_row - 1 # top right diagonal
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
def know_terminal_value(self, state, action):
|
| 54 |
+
if self.check_win(state, action):
|
| 55 |
+
return True, 1
|
| 56 |
+
if np.sum(self.get_valid_moves(state)) == 0:
|
| 57 |
+
return True, 0
|
| 58 |
+
return False, 0
|
| 59 |
+
|
| 60 |
+
def get_opponent(self, player):
|
| 61 |
+
return -player
|
| 62 |
+
|
| 63 |
+
def get_opponent_value(self, value):
|
| 64 |
+
return -value
|
| 65 |
+
|
| 66 |
+
def change_perspective(self, state, player):
|
| 67 |
+
return state * player
|
| 68 |
+
|
| 69 |
+
def get_encoded_state(self, state):
|
| 70 |
+
encoded_state = np.stack(
|
| 71 |
+
(state == -1, state == 0, state == 1)
|
| 72 |
+
).astype(np.float32)
|
| 73 |
+
if len(state.shape) == 3:
|
| 74 |
+
encoded_state = np.swapaxes(encoded_state, 0, 1)
|
| 75 |
+
|
| 76 |
+
return encoded_state
|
Games/ConnectFour/ConnectFourNN.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class ResNet(nn.Module):
|
| 6 |
+
def __init__(self, game, num_resBlocks, num_hidden, device):
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.device = device
|
| 9 |
+
self.startBlock = nn.Sequential(
|
| 10 |
+
nn.Conv2d(3, num_hidden, kernel_size=3, padding=1),
|
| 11 |
+
nn.BatchNorm2d(num_hidden),
|
| 12 |
+
nn.ReLU()
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
self.backBone = nn.ModuleList(
|
| 16 |
+
[ResBlock(num_hidden) for i in range(num_resBlocks)]
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
self.policyHead = nn.Sequential(
|
| 20 |
+
nn.Conv2d(num_hidden, 32, kernel_size=3, padding=1),
|
| 21 |
+
nn.BatchNorm2d(32),
|
| 22 |
+
nn.ReLU(),
|
| 23 |
+
nn.Flatten(),
|
| 24 |
+
nn.Linear(32 * game.row * game.col, game.possible_state)
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
self.valueHead = nn.Sequential(
|
| 28 |
+
nn.Conv2d(num_hidden, 3, kernel_size=3, padding=1),
|
| 29 |
+
nn.BatchNorm2d(3),
|
| 30 |
+
nn.ReLU(),
|
| 31 |
+
nn.Flatten(),
|
| 32 |
+
nn.Linear(3 * game.row * game.col, 1),
|
| 33 |
+
nn.Tanh()
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
self.to(device)
|
| 37 |
+
|
| 38 |
+
def forward(self, x):
|
| 39 |
+
x = self.startBlock(x)
|
| 40 |
+
for resBlock in self.backBone:
|
| 41 |
+
x = resBlock(x)
|
| 42 |
+
policy = self.policyHead(x)
|
| 43 |
+
value = self.valueHead(x)
|
| 44 |
+
return policy, value
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ResBlock(nn.Module):
|
| 48 |
+
def __init__(self, num_hidden):
|
| 49 |
+
super().__init__()
|
| 50 |
+
self.conv1 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 51 |
+
self.bn1 = nn.BatchNorm2d(num_hidden)
|
| 52 |
+
self.conv2 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 53 |
+
self.bn2 = nn.BatchNorm2d(num_hidden)
|
| 54 |
+
|
| 55 |
+
def forward(self, x):
|
| 56 |
+
residual = x
|
| 57 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 58 |
+
x = self.bn2(self.conv2(x))
|
| 59 |
+
x += residual
|
| 60 |
+
x = F.relu(x)
|
| 61 |
+
return x
|
Games/ConnectFour/__pycache__/ConnectFour.cpython-310.pyc
ADDED
|
Binary file (2.98 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFour.cpython-311.pyc
ADDED
|
Binary file (4.8 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFour.cpython-312.pyc
ADDED
|
Binary file (4.48 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFour.cpython-313.pyc
ADDED
|
Binary file (4.59 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFourNN.cpython-310.pyc
ADDED
|
Binary file (2.08 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFourNN.cpython-311.pyc
ADDED
|
Binary file (4.38 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFourNN.cpython-312.pyc
ADDED
|
Binary file (3.88 kB). View file
|
|
|
Games/ConnectFour/__pycache__/ConnectFourNN.cpython-313.pyc
ADDED
|
Binary file (4 kB). View file
|
|
|
Games/ConnectFour/models_n_optimizers/model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5ecbcdf83dc322c859ab3188f11e2f7b25ee504515a8bcf2b84798be7b1b3eb7
|
| 3 |
+
size 10920462
|
Games/ConnectFour/models_n_optimizers/optimizer.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c7165fd808da4ff2c3535392d8cf651ee905b0d0a15aadbef7e2e8300a7387fd
|
| 3 |
+
size 21792762
|
Games/TicTacToe/TicTacToe.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
class TicTacToe:
|
| 4 |
+
|
| 5 |
+
def __init__(self):
|
| 6 |
+
self.col = 3
|
| 7 |
+
self.row = 3
|
| 8 |
+
self.possible_state = self.col * self.row
|
| 9 |
+
|
| 10 |
+
def initialise_state(self):
|
| 11 |
+
return np.zeros((self.row, self.col))
|
| 12 |
+
|
| 13 |
+
def get_valid_moves(self, state):
|
| 14 |
+
return (state.reshape(-1) == 0).astype(np.uint8)
|
| 15 |
+
|
| 16 |
+
def know_terminal_value(self, state, action):
|
| 17 |
+
|
| 18 |
+
if action is None:
|
| 19 |
+
return False, 0
|
| 20 |
+
|
| 21 |
+
row=action//3
|
| 22 |
+
col=action%3
|
| 23 |
+
player = state[row,col]
|
| 24 |
+
|
| 25 |
+
if (sum(state[row,:])==player*3
|
| 26 |
+
or sum(state[:,col])==player*3
|
| 27 |
+
or np.trace(state)==player*3
|
| 28 |
+
or np.trace(np.fliplr(state))==player*3):
|
| 29 |
+
return True, 1
|
| 30 |
+
|
| 31 |
+
if 0 not in state.reshape(-1):
|
| 32 |
+
return True, 0
|
| 33 |
+
else:
|
| 34 |
+
return False,0
|
| 35 |
+
|
| 36 |
+
def make_move(self, state, action, player):
|
| 37 |
+
row=action//3
|
| 38 |
+
col=action%3
|
| 39 |
+
|
| 40 |
+
state[row,col]=player
|
| 41 |
+
|
| 42 |
+
return state
|
| 43 |
+
|
| 44 |
+
def get_encoded_state(self, state):
|
| 45 |
+
encoded_state = np.stack(
|
| 46 |
+
(state == -1, state == 0, state == 1)
|
| 47 |
+
).astype(np.float32)
|
| 48 |
+
if len(state.shape) == 3:
|
| 49 |
+
encoded_state = np.swapaxes(encoded_state, 0, 1)
|
| 50 |
+
|
| 51 |
+
return encoded_state
|
| 52 |
+
def get_opponent(self, player):
|
| 53 |
+
return -1 * player
|
| 54 |
+
|
| 55 |
+
def get_opponent_value(self, value):
|
| 56 |
+
return -1 * value
|
| 57 |
+
|
| 58 |
+
def change_perspective(self, state, player = -1):
|
| 59 |
+
return (player * state).astype(np.float32)
|
Games/TicTacToe/TicTacToeNN.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch.nn as nn
|
| 2 |
+
import torch.nn.functional as F
|
| 3 |
+
|
| 4 |
+
class ResNet(nn.Module):
|
| 5 |
+
|
| 6 |
+
def __init__(self, game, num_resBlocks, num_hidden, device):
|
| 7 |
+
super().__init__()
|
| 8 |
+
self.device = device
|
| 9 |
+
self.startBlock = nn.Sequential(
|
| 10 |
+
nn.Conv2d(3, num_hidden, kernel_size=3, padding=1),
|
| 11 |
+
nn.BatchNorm2d(num_hidden),
|
| 12 |
+
nn.ReLU()
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
self.backBone = nn.ModuleList(
|
| 16 |
+
[ResBlock(num_hidden) for i in range(num_resBlocks)]
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
self.policyHead = nn.Sequential(
|
| 20 |
+
nn.Conv2d(num_hidden, 32, kernel_size=3, padding=1),
|
| 21 |
+
nn.BatchNorm2d(32),
|
| 22 |
+
nn.ReLU(),
|
| 23 |
+
nn.Flatten(),
|
| 24 |
+
nn.Linear(32 * game.row * game.col, game.possible_state)
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
self.valueHead = nn.Sequential(
|
| 28 |
+
nn.Conv2d(num_hidden, 3, kernel_size=3, padding=1),
|
| 29 |
+
nn.BatchNorm2d(3),
|
| 30 |
+
nn.ReLU(),
|
| 31 |
+
nn.Flatten(),
|
| 32 |
+
nn.Linear(3 * game.row * game.col, 1),
|
| 33 |
+
nn.Tanh()
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
self.to(device)
|
| 37 |
+
|
| 38 |
+
def forward(self, x):
|
| 39 |
+
x = self.startBlock(x)
|
| 40 |
+
for resBlock in self.backBone:
|
| 41 |
+
x = resBlock(x)
|
| 42 |
+
policy = self.policyHead(x)
|
| 43 |
+
value = self.valueHead(x)
|
| 44 |
+
return policy, value
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class ResBlock(nn.Module):
|
| 48 |
+
def __init__(self, num_hidden):
|
| 49 |
+
super().__init__()
|
| 50 |
+
self.conv1 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 51 |
+
self.bn1 = nn.BatchNorm2d(num_hidden)
|
| 52 |
+
self.conv2 = nn.Conv2d(num_hidden, num_hidden, kernel_size=3, padding=1)
|
| 53 |
+
self.bn2 = nn.BatchNorm2d(num_hidden)
|
| 54 |
+
|
| 55 |
+
def forward(self, x):
|
| 56 |
+
residual = x
|
| 57 |
+
x = F.relu(self.bn1(self.conv1(x)))
|
| 58 |
+
x = self.bn2(self.conv2(x))
|
| 59 |
+
x += residual
|
| 60 |
+
x = F.relu(x)
|
| 61 |
+
return x
|
Games/TicTacToe/__pycache__/TicTacToe.cpython-310.pyc
ADDED
|
Binary file (2.27 kB). View file
|
|
|
Games/TicTacToe/__pycache__/TicTacToe.cpython-311.pyc
ADDED
|
Binary file (3.56 kB). View file
|
|
|
Games/TicTacToe/__pycache__/TicTacToe.cpython-313.pyc
ADDED
|
Binary file (3.36 kB). View file
|
|
|
Games/TicTacToe/__pycache__/TicTacToeNN.cpython-310.pyc
ADDED
|
Binary file (2.06 kB). View file
|
|
|
Games/TicTacToe/__pycache__/TicTacToeNN.cpython-311.pyc
ADDED
|
Binary file (4.38 kB). View file
|
|
|
Games/TicTacToe/__pycache__/TicTacToeNN.cpython-313.pyc
ADDED
|
Binary file (4 kB). View file
|
|
|
Games/TicTacToe/models_n_optimizers/model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5877c49bfb164d48c9ccf7d165d9e647c14ed6956cb047e6f40b6e7237e9af2d
|
| 3 |
+
size 10900420
|
Games/TicTacToe/models_n_optimizers/model_non_parallel.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f4cefe7c002f47fe17ee7b6304bdd6f32b44191bc00090604923300803062740
|
| 3 |
+
size 10903056
|
Games/TicTacToe/models_n_optimizers/optimizer.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:38db7c19396de4e717b28891427dcf3ff4efc1451c3a0cc7baff70b3cc5824c5
|
| 3 |
+
size 21738002
|
Games/TicTacToe/models_n_optimizers/optimizer_non_parallel.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:0f346133359c24c36ae633ab24f2e4c47773d5e40506c5f2fb64a06f8c54c7fa
|
| 3 |
+
size 21741505
|
Games/game.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""NOTE : THIS IS THE FORMAT YOU SHOULD FOLLOW TO MAKE GAMES"""
|
| 2 |
+
|
| 3 |
+
class GAME:
|
| 4 |
+
def __init__(self):
|
| 5 |
+
self.row = None
|
| 6 |
+
self.col = None
|
| 7 |
+
self.possible_state = self.row * self.col # no of possible state game can have (no of possible moves)
|
| 8 |
+
|
| 9 |
+
def initialise_state(self):
|
| 10 |
+
"""NOTE: this fuction creates starting position for your game"""
|
| 11 |
+
|
| 12 |
+
def get_valid_moves(self, state):
|
| 13 |
+
""" NOTE: inputs state and output all possible valid moves"""
|
| 14 |
+
|
| 15 |
+
def know_terminal_value(self, state, action):
|
| 16 |
+
"""NOTE: input state and action and the output will be tuple True
|
| 17 |
+
if is terminal state and False if not and value 0 is
|
| 18 |
+
player lost and 1 if the player won the game"""
|
| 19 |
+
|
| 20 |
+
def make_move(self, state, action, player):
|
| 21 |
+
"""NOTE: input a action and a state and a player, the
|
| 22 |
+
action is taken on given state and output is the final state"""
|
| 23 |
+
|
| 24 |
+
def get_encoded_state(self, state):
|
| 25 |
+
"""input a state the output should be the encoded state that will be
|
| 26 |
+
given to the neural network"""
|
| 27 |
+
|
| 28 |
+
def get_opponent(self, player):
|
| 29 |
+
"""NOTE: taking the input of current player this should change the
|
| 30 |
+
player to the opponent"""
|
| 31 |
+
return -1 * player
|
| 32 |
+
|
| 33 |
+
def get_opponent_value(self, value):
|
| 34 |
+
"""this should switch the reward obtained from the know_terminal_state()
|
| 35 |
+
to the reward of the opponent"""
|
| 36 |
+
return -1 * value
|
| 37 |
+
|
| 38 |
+
def change_perspective(self, state, player = -1):
|
| 39 |
+
"""this function taking input state and player flip the board and return
|
| 40 |
+
the game with the perspective of the opponent"""
|
| 41 |
+
return player * state
|
MCTS.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import numpy as np
|
| 3 |
+
|
| 4 |
+
class Node:
|
| 5 |
+
|
| 6 |
+
def __init__(self, game, args, state, parent = None, action = None):
|
| 7 |
+
self.game = game
|
| 8 |
+
self.args = args
|
| 9 |
+
self.state = state
|
| 10 |
+
self.parent = parent
|
| 11 |
+
self.action = action
|
| 12 |
+
|
| 13 |
+
self.children = list()
|
| 14 |
+
self.expandable_moves = self.game.get_moves(self.state)
|
| 15 |
+
self.visits = 0
|
| 16 |
+
self.total_value = 0
|
| 17 |
+
|
| 18 |
+
def leaf_or_not(self):
|
| 19 |
+
return (len(self.children) > 0 and len(self.expandable_moves) == 0)
|
| 20 |
+
|
| 21 |
+
def search(self):
|
| 22 |
+
best_child = None
|
| 23 |
+
best_ucb = -np.inf
|
| 24 |
+
for child in self.children:
|
| 25 |
+
ucb = self.get_ucb(child)
|
| 26 |
+
if best_ucb < ucb:
|
| 27 |
+
best_ucb = ucb
|
| 28 |
+
best_child = child
|
| 29 |
+
|
| 30 |
+
return best_child
|
| 31 |
+
|
| 32 |
+
def get_ucb(self, child):
|
| 33 |
+
q_value = 1 - ((child.total_value / child.visits) + 1) / 2
|
| 34 |
+
return q_value + self.args["EXPLORATION_CONSTANT"] * math.sqrt(math.log(self.visits) / child.visits)
|
| 35 |
+
|
| 36 |
+
def expand(self):
|
| 37 |
+
rand_move = np.random.choice(self.expandable_moves)
|
| 38 |
+
self.expandable_moves.remove(rand_move)
|
| 39 |
+
|
| 40 |
+
child = self.game.make_move(self.state.copy(), rand_move, 1)
|
| 41 |
+
child = self.game.change_perspective(child)
|
| 42 |
+
child = Node(self.game, self.args, child, self, rand_move)
|
| 43 |
+
|
| 44 |
+
self.children.append(child)
|
| 45 |
+
|
| 46 |
+
return child
|
| 47 |
+
|
| 48 |
+
def simulate(self):
|
| 49 |
+
is_terminal, value = self.game.know_terminal_value(self.state, self.action)
|
| 50 |
+
value = self.game.get_opponent_value(value)
|
| 51 |
+
|
| 52 |
+
if is_terminal:
|
| 53 |
+
return value
|
| 54 |
+
|
| 55 |
+
state = self.state.copy()
|
| 56 |
+
player = 1
|
| 57 |
+
while True:
|
| 58 |
+
possible_moves = self.game.get_moves(state)
|
| 59 |
+
rand_move = np.random.choice(possible_moves)
|
| 60 |
+
state = self.game.make_move(state, rand_move, player)
|
| 61 |
+
is_terminal, value = self.game.know_terminal_value(state, rand_move)
|
| 62 |
+
if is_terminal:
|
| 63 |
+
if player == -1:
|
| 64 |
+
value = self.game.get_opponent_value(value)
|
| 65 |
+
return value
|
| 66 |
+
player = self.game.get_opponent(player)
|
| 67 |
+
|
| 68 |
+
def backpropagate(self,value):
|
| 69 |
+
self.total_value += value
|
| 70 |
+
self.visits += 1
|
| 71 |
+
|
| 72 |
+
value = self.game.get_opponent_value(value)
|
| 73 |
+
if self.parent is not None:
|
| 74 |
+
self.parent.backpropagate(value)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class MCTS:
|
| 78 |
+
def __init__(self, game, args):
|
| 79 |
+
self.game = game
|
| 80 |
+
self.args = args
|
| 81 |
+
|
| 82 |
+
def search(self, node):
|
| 83 |
+
root = Node(self.game, self.args, node)
|
| 84 |
+
|
| 85 |
+
for _ in range(self.args["NO_OF_SEARCHES"]):
|
| 86 |
+
node = root
|
| 87 |
+
while node.leaf_or_not():
|
| 88 |
+
node = node.search()
|
| 89 |
+
|
| 90 |
+
is_terminal, value = self.game.know_terminal_value(node.state, node.action)
|
| 91 |
+
value = self.game.get_opponent_value(value)
|
| 92 |
+
|
| 93 |
+
if not is_terminal:
|
| 94 |
+
node = node.expand()
|
| 95 |
+
value = node.simulate()
|
| 96 |
+
|
| 97 |
+
node.backpropagate(value)
|
| 98 |
+
|
| 99 |
+
move_probability = np.zeros(self.game.possible_state)
|
| 100 |
+
for children in root.children:
|
| 101 |
+
move_probability[children.action] = children.visits
|
| 102 |
+
move_probability /= np.sum(move_probability)
|
| 103 |
+
|
| 104 |
+
return move_probability
|
Play.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import os
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from Games.ConnectFour.ConnectFour import ConnectFour
|
| 8 |
+
from Games.ConnectFour.ConnectFourNN import ResNet
|
| 9 |
+
from Alpha_MCTS import Alpha_MCTS
|
| 10 |
+
|
| 11 |
+
class Colors:
|
| 12 |
+
RESET = "\033[0m"
|
| 13 |
+
RED = "\033[91m"
|
| 14 |
+
GREEN = "\033[92m"
|
| 15 |
+
YELLOW = "\033[93m"
|
| 16 |
+
BLUE = "\033[94m"
|
| 17 |
+
MAGENTA = "\033[95m"
|
| 18 |
+
CYAN = "\033[96m"
|
| 19 |
+
WHITE = "\033[97m"
|
| 20 |
+
|
| 21 |
+
GAME = "ConnectFour"
|
| 22 |
+
args = {
|
| 23 |
+
"MODEL_PATH" : os.path.join(os.getcwd(), "Games", GAME, "models_n_optimizers"),
|
| 24 |
+
|
| 25 |
+
"ADVERSARIAL" : True,
|
| 26 |
+
"ROOT_RANDOMNESS": False,
|
| 27 |
+
|
| 28 |
+
"TEMPERATURE" : 1,
|
| 29 |
+
|
| 30 |
+
"NO_OF_SEARCHES" : 1200,
|
| 31 |
+
"EXPLORATION_CONSTANT" : 1,
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
game = ConnectFour()
|
| 36 |
+
device = torch.device("cuda" if torch.cuda.is_available else "cpu")
|
| 37 |
+
|
| 38 |
+
model = ResNet(game, 9, 128, device)
|
| 39 |
+
model.eval()
|
| 40 |
+
|
| 41 |
+
path = os.path.join(args["MODEL_PATH"], "model.pt")
|
| 42 |
+
|
| 43 |
+
try:
|
| 44 |
+
model.load_state_dict(torch.load(path))
|
| 45 |
+
print(Colors.GREEN, "Model Found\n Model Successfully Loaded", Colors.RESET)
|
| 46 |
+
|
| 47 |
+
except:
|
| 48 |
+
print(Colors.RED, "Model Not Found!!!", Colors.RESET)
|
| 49 |
+
|
| 50 |
+
finally:
|
| 51 |
+
mcts = Alpha_MCTS(game, args, model)
|
| 52 |
+
|
| 53 |
+
state = game.initialise_state()
|
| 54 |
+
player = -1
|
| 55 |
+
|
| 56 |
+
while True:
|
| 57 |
+
print(state)
|
| 58 |
+
|
| 59 |
+
if player == 1:
|
| 60 |
+
valid_moves = game.get_valid_moves(state)
|
| 61 |
+
print("valid_moves", [i for i in range(game.possible_state) if valid_moves[i] == 1])
|
| 62 |
+
action = int(input(f"{player}:"))
|
| 63 |
+
|
| 64 |
+
if valid_moves[action] == 0:
|
| 65 |
+
print("action not valid")
|
| 66 |
+
continue
|
| 67 |
+
|
| 68 |
+
else:
|
| 69 |
+
neutral_state = game.change_perspective(state, player)
|
| 70 |
+
mcts_probs = mcts.search(neutral_state)
|
| 71 |
+
print(Colors.GREEN, "MCTS Move Probabilities:", Colors.RESET,mcts_probs )
|
| 72 |
+
action = np.argmax(mcts_probs)
|
| 73 |
+
|
| 74 |
+
state = game.make_move(state, action, player)
|
| 75 |
+
|
| 76 |
+
is_terminal, value = game.know_terminal_value(state, action)
|
| 77 |
+
|
| 78 |
+
if is_terminal:
|
| 79 |
+
print(state)
|
| 80 |
+
if value == 1:
|
| 81 |
+
print(player, "won")
|
| 82 |
+
else:
|
| 83 |
+
print("draw")
|
| 84 |
+
break
|
| 85 |
+
|
| 86 |
+
player = game.get_opponent(player)
|
Train.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
|
| 4 |
+
from Games.TicTacToe.TicTacToe import TicTacToe
|
| 5 |
+
from Games.TicTacToe.TicTacToeNN import ResNet
|
| 6 |
+
from Alpha_Zero_Parallel import Alpha_Zero
|
| 7 |
+
|
| 8 |
+
GAME = "TicTacToe"
|
| 9 |
+
|
| 10 |
+
args = {
|
| 11 |
+
"MODEL_PATH" : os.path.join(os.getcwd(), "Games", GAME, "models_n_optimizers"),
|
| 12 |
+
"SAVE_GAME_PATH" : os.path.join(os.getcwd(), "Games", GAME, "games"),
|
| 13 |
+
|
| 14 |
+
"EXPLORATION_CONSTANT" : 2,
|
| 15 |
+
|
| 16 |
+
"TEMPERATURE" : 2,
|
| 17 |
+
|
| 18 |
+
"DIRICHLET_EPSILON" : 0.25,
|
| 19 |
+
"DIRICHLET_ALPHA" : 0.3,
|
| 20 |
+
"ROOT_RANDOMNESS": True,
|
| 21 |
+
|
| 22 |
+
"ADVERSARIAL" : True,
|
| 23 |
+
|
| 24 |
+
"NO_OF_SEARCHES" : 800,
|
| 25 |
+
"NO_ITERATIONS" : 1,
|
| 26 |
+
"SELF_PLAY_ITERATIONS" : 3000,
|
| 27 |
+
"PARALLEL_PROCESS" : 500,
|
| 28 |
+
"EPOCHS" : 1,
|
| 29 |
+
"BATCH_SIZE" : 128,
|
| 30 |
+
"MODEL_CHECK_GAMES" : 200,
|
| 31 |
+
"WIN_RATIO_FOR_SAVING": 0.5,
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
game = TicTacToe()
|
| 36 |
+
torch.backends.cudnn.enabled = False
|
| 37 |
+
|
| 38 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
| 39 |
+
|
| 40 |
+
print(device, "in use")
|
| 41 |
+
|
| 42 |
+
model = ResNet(game, 9, 128, device)
|
| 43 |
+
|
| 44 |
+
optimizer = torch.optim.Adam(model.parameters(), lr=0.001, weight_decay = 0.0001)
|
| 45 |
+
|
| 46 |
+
state = game.initialise_state()
|
| 47 |
+
|
| 48 |
+
alpha_zero = Alpha_Zero(game, args, model, optimizer)
|
| 49 |
+
|
| 50 |
+
alpha_zero.learn()
|
__pycache__/Alpha_MCTS.cpython-310.pyc
ADDED
|
Binary file (3.47 kB). View file
|
|
|
__pycache__/Alpha_MCTS.cpython-311.pyc
ADDED
|
Binary file (7.26 kB). View file
|
|
|
__pycache__/Alpha_MCTS.cpython-313.pyc
ADDED
|
Binary file (7.27 kB). View file
|
|
|
__pycache__/Alpha_MCTS_Parallel.cpython-310.pyc
ADDED
|
Binary file (3.9 kB). View file
|
|
|
__pycache__/Alpha_MCTS_Parallel.cpython-311.pyc
ADDED
|
Binary file (8.2 kB). View file
|
|
|
__pycache__/Alpha_MCTS_Parallel.cpython-313.pyc
ADDED
|
Binary file (7.7 kB). View file
|
|
|
__pycache__/Alpha_Zero.cpython-310.pyc
ADDED
|
Binary file (4.13 kB). View file
|
|
|
__pycache__/Alpha_Zero.cpython-311.pyc
ADDED
|
Binary file (13.7 kB). View file
|
|
|
__pycache__/Alpha_Zero.cpython-313.pyc
ADDED
|
Binary file (11.4 kB). View file
|
|
|
__pycache__/Alpha_Zero_Parallel.cpython-310.pyc
ADDED
|
Binary file (5.64 kB). View file
|
|
|
__pycache__/Alpha_Zero_Parallel.cpython-311.pyc
ADDED
|
Binary file (14 kB). View file
|
|
|
__pycache__/Alpha_Zero_Parallel.cpython-313.pyc
ADDED
|
Binary file (13.7 kB). View file
|
|
|
__pycache__/Arena.cpython-313.pyc
ADDED
|
Binary file (3.8 kB). View file
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
altair==6.0.0
|
| 2 |
+
attrs==25.4.0
|
| 3 |
+
blinker==1.9.0
|
| 4 |
+
cachetools==7.0.5
|
| 5 |
+
certifi==2026.2.25
|
| 6 |
+
charset-normalizer==3.4.5
|
| 7 |
+
click==8.3.1
|
| 8 |
+
cuda-bindings==12.9.4
|
| 9 |
+
cuda-pathfinder==1.4.2
|
| 10 |
+
filelock==3.25.2
|
| 11 |
+
fsspec==2026.2.0
|
| 12 |
+
gitdb==4.0.12
|
| 13 |
+
GitPython==3.1.46
|
| 14 |
+
idna==3.11
|
| 15 |
+
Jinja2==3.1.6
|
| 16 |
+
jsonschema==4.26.0
|
| 17 |
+
jsonschema-specifications==2025.9.1
|
| 18 |
+
MarkupSafe==3.0.3
|
| 19 |
+
mpmath==1.3.0
|
| 20 |
+
narwhals==2.18.0
|
| 21 |
+
networkx==3.6.1
|
| 22 |
+
numpy==2.4.3
|
| 23 |
+
nvidia-cublas-cu12==12.8.4.1
|
| 24 |
+
nvidia-cuda-cupti-cu12==12.8.90
|
| 25 |
+
nvidia-cuda-nvrtc-cu12==12.8.93
|
| 26 |
+
nvidia-cuda-runtime-cu12==12.8.90
|
| 27 |
+
nvidia-cudnn-cu12==9.10.2.21
|
| 28 |
+
nvidia-cufft-cu12==11.3.3.83
|
| 29 |
+
nvidia-cufile-cu12==1.13.1.3
|
| 30 |
+
nvidia-curand-cu12==10.3.9.90
|
| 31 |
+
nvidia-cusolver-cu12==11.7.3.90
|
| 32 |
+
nvidia-cusparse-cu12==12.5.8.93
|
| 33 |
+
nvidia-cusparselt-cu12==0.7.1
|
| 34 |
+
nvidia-nccl-cu12==2.27.5
|
| 35 |
+
nvidia-nvjitlink-cu12==12.8.93
|
| 36 |
+
nvidia-nvshmem-cu12==3.4.5
|
| 37 |
+
nvidia-nvtx-cu12==12.8.90
|
| 38 |
+
packaging==26.0
|
| 39 |
+
pandas==2.3.3
|
| 40 |
+
pillow==12.1.1
|
| 41 |
+
protobuf==6.33.5
|
| 42 |
+
pyarrow==23.0.1
|
| 43 |
+
pydeck==0.9.1
|
| 44 |
+
python-dateutil==2.9.0.post0
|
| 45 |
+
pytz==2026.1.post1
|
| 46 |
+
referencing==0.37.0
|
| 47 |
+
requests==2.32.5
|
| 48 |
+
rpds-py==0.30.0
|
| 49 |
+
setuptools==82.0.1
|
| 50 |
+
six==1.17.0
|
| 51 |
+
smmap==5.0.3
|
| 52 |
+
streamlit==1.55.0
|
| 53 |
+
sympy==1.14.0
|
| 54 |
+
tenacity==9.1.4
|
| 55 |
+
toml==0.10.2
|
| 56 |
+
torch==2.10.0
|
| 57 |
+
torchvision==0.25.0
|
| 58 |
+
tornado==6.5.5
|
| 59 |
+
tqdm==4.67.3
|
| 60 |
+
triton==3.6.0
|
| 61 |
+
typing_extensions==4.15.0
|
| 62 |
+
tzdata==2025.3
|
| 63 |
+
urllib3==2.6.3
|
| 64 |
+
watchdog==6.0.0
|