go-ai/go_player/myPlayer.py
Nemo D'ACREMONT b657036355 random
2025-05-18 13:40:54 +02:00

252 lines
7.7 KiB
Python

# -*- coding: utf-8 -*-
"""This is the file you have to modify for the tournament. Your default AI player must be called by this module, in the
myPlayer class.
Right now, this class contains the copy of the randomPlayer. But you have to change this!
"""
from sys import stderr
import time
import math
import Goban
from random import choice
from moveSearch import IDDFS, alphabeta
from playerInterface import *
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from torch.utils.data import Dataset
def setup_device():
# Allows to use the GPU if available
if torch.cuda.is_available():
device = torch.device("cuda")
elif torch.backends.mps.is_available():
device = torch.device("mps")
else:
device = torch.device("cpu")
return device
def goban2Go(board: Goban.Board):
goBoard = torch.zeros((3, 8, 8), dtype=torch.float32)
black_plays = (board.next_player() == Goban.Board._BLACK)
flat = board.get_board()
for i in range(8):
for j in range(8):
if flat[i * 8 + j] == Goban.Board._BLACK:
goBoard[0, i, j] = 1
elif flat[i * 8 + j] == Goban.Board._WHITE:
goBoard[1, i, j] = 1
goBoard[2,:,:] = 1 if black_plays else 0
return goBoard
class GoModel(nn.Module):
def __init__(self):
super(GoModel, self).__init__()
self.net = torch.nn.Sequential(
nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(16),
torch.nn.ReLU(),
nn.Conv2d(16, 32, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(32),
torch.nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.Dropout(0.4),
torch.nn.ReLU(),
nn.Conv2d(64, 128, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(128),
torch.nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(128),
torch.nn.ReLU(),
nn.Conv2d(128, 128, kernel_size=3, padding=1, bias=False),
nn.BatchNorm2d(128),
torch.nn.ReLU(),
nn.Flatten(),
nn.Linear(128 * 8 * 8, 128),
nn.BatchNorm1d(128),
torch.nn.ReLU(),
nn.Dropout(0.4),
nn.Linear(128, 1),
nn.Sigmoid()
)
def forward(self, x):
if self.training:
return self.net(x)
else:
y = self.net(x)
batch_size = x.size(0)
x_rotated = torch.stack([torch.rot90(x, k=k, dims=[2, 3]) for k in range(4)], dim=1) # x_rotated: [batch_size, 4, 3, 8, 8]
x_rotated = x_rotated.view(-1, 3, 8, 8) # [batch_size*4, 3, 8, 8]
with torch.no_grad():
y_rotated = self.net(x_rotated) # [batch_size*4, 1]
# Reshape to get them by rotation
y_rotated = y_rotated.view(batch_size, 4, -1) # [batch_size, 4, 1]
y_mean = y_rotated.mean(dim=1) # [batch_size, 1]
return y_mean
class GoDataset(Dataset):
def __init__(self, data, device, test=False):
def label(d, j):
if j == 0:
return d["black_wins"] / d["rollouts"]
else:
return 1 - label(d, 0)
def board(d, j, k):
if j == 0:
out = stones_to_board(d["black_stones"], d["white_stones"], d["depth"] % 2 == 0)
else:
out = stones_to_board(d["white_stones"], d["black_stones"], d["depth"] % 2 == 1)
if k == 0:
return out
else:
return out.flipud()
if test:
dims = [1, 2]
self.boards = torch.from_numpy(np.array([
board(d, 0, 0) for d in data
])).float().to(device)
self.labels = torch.from_numpy(np.array(
[label(d, 0) for d in data],
)).float().to(device)
else:
dims = [1, 2]
self.boards = torch.from_numpy(np.array([
torch.rot90(board(d, j, k), i, dims)
for d in data
for k in range(2)
for i in range(4)
for j in range(2)
])).float().to(device)
self.labels = torch.from_numpy(np.array(
[label(d, j) for d in data for _ in range(4) for _k in range(2) for j in range(2)],
)).float().to(device)
def __len__(self):
return len(self.boards)
def __getitem__(self, i):
return self.boards[i], self.labels[i]
class myPlayer(PlayerInterface):
"""
Example of a random player for the go. The only tricky part is to be able to handle
the internal representation of moves given by legal_moves() and used by push() and
to translate them to the GO-move strings "A1", ..., "J8", "PASS". Easy!
"""
def __init__(self):
self._board = Goban.Board()
self._mycolor = None
self.device = setup_device()
print(self.device)
self.model = GoModel().to(self.device)
checkpoint = torch.load("scrum.pt", weights_only=True, map_location=self.device)
self.model.load_state_dict(checkpoint["model_state_dict"])
def getPlayerName(self):
return "xXx_7h3_5cRuM_M45T3r_xXx"
@staticmethod
def simple_heuristic(board, color):
# Simple stone difference heuristic
score = board.compute_score()
return (
score[0] - score[1] if color == Goban.Board._BLACK else score[1] - score[0]
)
def nnheuristic(self, board: Goban.Board, color):
if board.is_game_over():
if board.winner() == board._EMPTY:
return 0.5
return math.inf if board.winner() == color else -math.inf
go_board = torch.from_numpy(np.array([goban2Go(board)])).float().to(self.device)
self.model.eval()
with torch.no_grad():
prediction = self.model(go_board).item()
if color == Goban.Board._BLACK:
return prediction
return 1 - prediction
def getPlayerMove(self):
if self._board.is_game_over():
print("Referee told me to play but the game is over!")
return "PASS"
duration = 1.
if self._board._nbBLACK + self._board._nbWHITE < 10:
max_depth = 1
elif self._board._nbBLACK + self._board._nbWHITE < 20:
max_depth = 2
elif self._board._nbBLACK + self._board._nbWHITE < 40:
max_depth = 3
else:
duration = 64 - (self._board._nbBLACK + self._board._nbWHITE)
max_depth = 24
# move = alphabeta(self._board, self.nnheuristic, self._mycolor, 1)
move, score = IDDFS(
self._board, self.nnheuristic, self._mycolor, duration=duration, maxdepth=max_depth
)
self._board.push(move)
print(move, score, file=stderr)
# New here: allows to consider internal representations of moves
# move is an internal representation. To communicate with the interface I need to change if to a string
return Goban.Board.flat_to_name(move)
def playOpponentMove(self, move):
print("Opponent played ", move) # New here
# the board needs an internal represetation to push the move. Not a string
self._board.push(Goban.Board.name_to_flat(move))
def newGame(self, color):
self._mycolor = color
self._opponent = Goban.Board.flip(color)
def endGame(self, winner):
if self._mycolor == winner:
print("I won!!!")
else:
print("I lost :(!!")