-
Notifications
You must be signed in to change notification settings - Fork 0
/
othello_simulation.py
108 lines (86 loc) · 4.1 KB
/
othello_simulation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# othello_simulation.py
import multiprocessing
import time
import numpy as np
from tqdm import tqdm
from agents import Agent, Player, MinmaxAgent, RandomAgent, MCTSAgent
from othello import Othello
from heuristics import HEURISTICS
from numba import njit, int16
class OthelloSimulation:
"""
A class to handle the simulation of Othello games without a graphical user interface (GUI).
Attributes:
game (Othello): An instance of the Othello game with the provided players.
"""
def __init__(self, player1: Player, player2: Player):
"""
Initializes the OthelloSimulation with two players.
Args:
player1 (Player): The first player.
player2 (Player): The second player.
"""
self.game = Othello(player1, player2)
def run_simulation(self, num_simulations: int, parallel: bool = True):
"""
Runs the simulation for the specified number of games without GUI.
Args:
num_simulations (int): The number of games to simulate.
parallel (bool): Whether to run simulations in parallel.
Raises:
ValueError: If either of the players is not an instance of the Agent class.
"""
if not isinstance(self.game.player1, Agent) or not isinstance(self.game.player2, Agent):
raise Exception("Cannot simulate games with non Agent instances.")
nb_cores = max(0, multiprocessing.cpu_count() - 1)
game_results = []
print("=============== Othello Simulation ===============")
start = time.perf_counter()
if parallel:
print(f"Starting simulation on {nb_cores} cores.\n")
with multiprocessing.Pool(processes=nb_cores) as pool:
game_results = list(tqdm(pool.imap_unordered(self.simulate_game,
((self.game.player1.copy(), self.game.player2.copy()) for _ in range(num_simulations))), total=num_simulations))
else:
for i in range(num_simulations):
mid = time.perf_counter()
result = self.simulate_game((self.game.player1.copy(), self.game.player2.copy()))
tot = time.perf_counter() - mid
game_results.append(result)
print(f"Simulation {i+1} took {tot:<6.2f} sec")
end_tot = time.perf_counter() - start
counts = {1: 0, 2: 0, 0: 0}
for result in game_results:
counts[result] += 1
print("\n===================== Results ====================")
print(f"Player 1 | Wins: {counts[self.game.player1.id]:<3}, Draws: {counts[0]:<3}")
print(f"Player 2 | Wins: {counts[self.game.player2.id]:<3}, Draws: {counts[0]:<3}")
print(f"Simulation took {end_tot:<7.2f} sec (avg:{end_tot/num_simulations:.2f})")
@staticmethod
def simulate_game(players: tuple) -> int:
"""
Simulates a single game without GUI and returns the winner.
Args:
players (tuple(Player)): A tuple containing two player instances.
Returns:
int: The ID of the winner, or 0 if the game is a draw.
"""
player1, player2 = players
game = Othello(player1, player2)
while True:
current_player_valid_moves = game.current_player_moves
if current_player_valid_moves.size == 0:
game.switch_player()
opponent_valid_moves = game.current_player_moves
if opponent_valid_moves.size == 0:
break
continue
move = game.current_player.get_move(game.board, None)
if move in current_player_valid_moves:
game.make_move(move)
winner = game.get_winner()
return winner
if __name__ == "__main__":
simulation = OthelloSimulation(player1=MinmaxAgent(depth=7, heuristic=HEURISTICS.STABILITY),
player2=MCTSAgent(nb_iterations=100000))
simulation.run_simulation(50, parallel=True)