feat: create the basic player using iddfs for go
This commit is contained in:
parent
fdd51b5857
commit
7b418ab5cb
6
go_ia/.gitignore
vendored
6
go_ia/.gitignore
vendored
@ -1,8 +1,10 @@
|
||||
__pycache__
|
||||
/venv
|
||||
/.venv
|
||||
/samples-8x8.json.gz
|
||||
/samples-8x8.json
|
||||
/*.json.gz
|
||||
/*.json
|
||||
*.pt
|
||||
*.gz
|
||||
*.txt
|
||||
/rendu
|
||||
|
||||
|
3
go_player/.gitignore
vendored
3
go_player/.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
__pycache__
|
||||
/venv
|
||||
/.venv
|
||||
/go-package.tgz
|
||||
*.ipynb
|
||||
/chess
|
||||
|
157
go_player/GnuGo.py
Normal file
157
go_player/GnuGo.py
Normal file
@ -0,0 +1,157 @@
|
||||
import subprocess, sys
|
||||
|
||||
""" Connection with the Go Text Protocol of GNU Go.
|
||||
You have to have gnugo installed, and in your exec path."""
|
||||
import random
|
||||
|
||||
|
||||
class GnuGo:
|
||||
|
||||
def query(self, s):
|
||||
self._stdin.write(s + "\n")
|
||||
ret = []
|
||||
while True:
|
||||
l = self._stdout.readline().rstrip()
|
||||
if l == "":
|
||||
break
|
||||
ret.append(l)
|
||||
if len(ret) == 1 and ret[0].startswith("="):
|
||||
return ("OK", ret[0][1:])
|
||||
elif len(ret) == 0:
|
||||
return ("NOK", None)
|
||||
else:
|
||||
return ("NOK", ret[0])
|
||||
|
||||
def __str__(self):
|
||||
self._stdin.write("showboard\n")
|
||||
ret = []
|
||||
while True:
|
||||
l = self._stdout.readline().rstrip()
|
||||
if l == "":
|
||||
break
|
||||
ret.append(l)
|
||||
return "\n".join(ret[1:])
|
||||
|
||||
def finalScore(self):
|
||||
self._stdin.write("final_score\n")
|
||||
ret = []
|
||||
while True:
|
||||
l = self._stdout.readline().rstrip()
|
||||
if l == "":
|
||||
break
|
||||
ret.append(l)
|
||||
return ret[0][2:]
|
||||
|
||||
class Moves:
|
||||
|
||||
def __init__(self, gnugo):
|
||||
self._nextplayer = "black"
|
||||
self._gnugo = gnugo
|
||||
|
||||
def flip(self):
|
||||
if self._nextplayer == "black":
|
||||
self._nextplayer = "white"
|
||||
else:
|
||||
self._nextplayer = "black"
|
||||
|
||||
def player(self):
|
||||
return self._nextplayer
|
||||
|
||||
def getbest(self):
|
||||
status, toret = self._gnugo.query("reg_genmove " + self._nextplayer)
|
||||
if status == "OK":
|
||||
return toret.strip()
|
||||
return "ERR"
|
||||
|
||||
def get_randomized_best(self):
|
||||
status, toret = self._gnugo.query("experimental_score " + self._nextplayer)
|
||||
if status != "OK":
|
||||
return "ERR"
|
||||
status, toret = self._gnugo.query("top_moves " + self._nextplayer)
|
||||
if status != "OK":
|
||||
return "ERR"
|
||||
moves = []
|
||||
scoremoves = []
|
||||
cumulatedscore = []
|
||||
cumul = 0
|
||||
toread = toret.strip().split()
|
||||
if len(toread) == 0:
|
||||
return "PASS"
|
||||
while len(toread) > 0:
|
||||
m = toread.pop(0)
|
||||
s = float(toread.pop(0))
|
||||
moves.append(m)
|
||||
scoremoves.append(s)
|
||||
cumul += s
|
||||
cumulatedscore.append(cumul)
|
||||
r = random.uniform(0, cumul)
|
||||
i = 0
|
||||
while i < len(moves) and r > cumulatedscore[i]:
|
||||
i += 1
|
||||
if i >= len(moves):
|
||||
i = len(moves) - 1
|
||||
return moves[i]
|
||||
|
||||
def get_history(self):
|
||||
status, toret = self._gnugo.query("move_history")
|
||||
if status != "OK":
|
||||
return "ERR"
|
||||
toread = toret.strip().split()
|
||||
return toread
|
||||
|
||||
def playthis(self, move):
|
||||
status, toret = self._gnugo.query(
|
||||
"play " + self._nextplayer + " " + str(move)
|
||||
)
|
||||
# print(status, toret)
|
||||
self.flip()
|
||||
return status
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
status, toret = self._gnugo.query("genmove " + self._nextplayer)
|
||||
self.flip()
|
||||
if status == "OK":
|
||||
return toret.strip()
|
||||
return "ERR"
|
||||
|
||||
def __init__(self, size, seed=12345678):
|
||||
self._proc = subprocess.Popen(
|
||||
[
|
||||
"gnugo",
|
||||
"--capture-all-dead",
|
||||
"--chinese-rules",
|
||||
"--monte-carlo",
|
||||
"--boardsize",
|
||||
str(size),
|
||||
"--mode",
|
||||
"gtp",
|
||||
"--never-resign",
|
||||
"--seed",
|
||||
str(seed),
|
||||
],
|
||||
bufsize=1,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
universal_newlines=True,
|
||||
)
|
||||
self._stdin = self._proc.stdin
|
||||
self._stdout = self._proc.stdout
|
||||
self._size = size
|
||||
self._nextplayer = "black"
|
||||
(ok, _) = self.query("level 0")
|
||||
assert ok == "OK"
|
||||
(ok, _) = self.query("boardsize " + str(size))
|
||||
assert ok == "OK"
|
||||
(ok, _) = self.query("clear_board")
|
||||
assert ok == "OK"
|
||||
(ok, name) = self.query("name")
|
||||
assert ok == "OK"
|
||||
(ok, version) = self.query("version")
|
||||
assert ok == "OK"
|
||||
# print("Connection to", name.strip(), "(" + version.strip() + ")","Ok")
|
||||
(ok, legal) = self.query("all_legal black")
|
||||
assert ok == "OK"
|
||||
# print("Legal moves: ", legal)
|
1038
go_player/Goban.py
Normal file
1038
go_player/Goban.py
Normal file
File diff suppressed because it is too large
Load Diff
91
go_player/README.txt
Normal file
91
go_player/README.txt
Normal file
@ -0,0 +1,91 @@
|
||||
Goban.py
|
||||
---------
|
||||
|
||||
Fichier contenant les règles du jeu de GO avec les fonctions et méthodes pour parcourir (relativement) efficacement
|
||||
l'arbre de jeu, à l'aide de legal_moves() et push()/pop() comme vu en cours.
|
||||
|
||||
Ce fichier sera utilisé comme arbitre dans le tournoi. Vous avez maintenant les fonctions de score implantés dedans.
|
||||
Sauf problème, ce sera la methode result() qui donnera la vainqueur quand is_game_over() sera Vrai.
|
||||
|
||||
Vous avez un décompte plus précis de la victoire dans final_go_score()
|
||||
|
||||
Pour vous aider à parcourir le plateau de jeu, si b est un Board(), vous pouvez avoir accès à la couleur de la pierre
|
||||
posée en (x,y) en utilisant b[Board.flatten((x,y))]
|
||||
|
||||
|
||||
GnuGo.py
|
||||
--------
|
||||
|
||||
Fichier contenant un ensemble de fonctions pour communiquer avec gnugo. Attention, il faut installer correctement (et
|
||||
à part gnugo sur votre machine). Je l'ai testé sur Linux uniquement mais cela doit fonctionner avec tous les autres
|
||||
systèmes (même s'ils sont moins bons :)).
|
||||
|
||||
|
||||
starter-go.py
|
||||
-------------
|
||||
|
||||
Exemples de deux développements aléatoires (utilisant legal_moves et push/pop). Le premier utilise legal_moves et le
|
||||
second weak_legal_moves, qui ne garanti plus que le coup aléatoire soit vraiment légal (à cause des Ko).
|
||||
|
||||
La première chose à faire est probablement de
|
||||
|
||||
|
||||
localGame.py
|
||||
------------
|
||||
|
||||
Permet de lancer un match de myPlayer contre lui même, en vérifiant les coups avec une instanciation de Goban.py comme
|
||||
arbitre. Vous ne devez pas modifier ce fichier pour qu'il fonctionne, sans quoi je risque d'avoir des problèmes pour
|
||||
faire entrer votre IA dans le tournoi.
|
||||
|
||||
|
||||
playerInterface.py
|
||||
------------------
|
||||
|
||||
Classe abstraite, décrite dans le sujet, permettant à votre joueur d'implanter correctement les fonctions pour être
|
||||
utilisé dans localGame et donc, dans le tournoi. Attention, il faut bien faire attention aux coups internes dans Goban
|
||||
(appelés "flat") et qui sont utilisés dans legal_moves/weak_legal_moves et push/pop des coups externes qui sont
|
||||
utilisés dans l'interface (les named moves). En interne, un coup est un indice dans un tableau 1 dimension
|
||||
-1, 0.._BOARDSIZE^2 et en externe (dans cette interface) les coups sont des chaines de caractères dans "A1", ..., "J9",
|
||||
"PASS". Il ne faut pas se mélanger les pinceaux.
|
||||
|
||||
|
||||
myPlayer.py
|
||||
-----------
|
||||
|
||||
Fichier que vous devrez modifier pour y mettre votre IA pour le tournoi. En l'état actuel, il contient la copie du
|
||||
joueur randomPlayer.py
|
||||
|
||||
|
||||
randomPlayer.py
|
||||
---------------
|
||||
|
||||
Un joueur aléatoire que vous pourrez conserver tel quel
|
||||
|
||||
|
||||
gnugoPlayer.py
|
||||
--------------
|
||||
|
||||
Un joueur basé sur gnugo. Vous permet de vous mesurer à lui simplement.
|
||||
|
||||
|
||||
namedGame.py
|
||||
------------
|
||||
|
||||
Permet de lancer deux joueurs différents l'un contre l'autre.
|
||||
Il attent en argument les deux modules des deux joueurs à importer.
|
||||
|
||||
|
||||
EXEMPLES DE LIGNES DE COMMANDES:
|
||||
================================
|
||||
|
||||
python3 localGame.py
|
||||
--> Va lancer un match myPlayer.py contre myPlayer.py
|
||||
|
||||
python3 namedGame.py myPlayer randomPlayer
|
||||
--> Va lancer un match entre votre joueur (NOIRS) et le randomPlayer
|
||||
(BLANC)
|
||||
|
||||
python3 namedGame gnugoPlayer myPlayer
|
||||
--> gnugo (level 0) contre votre joueur (très dur à battre)
|
||||
|
||||
|
60
go_player/gnugoPlayer.py
Normal file
60
go_player/gnugoPlayer.py
Normal file
@ -0,0 +1,60 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import time
|
||||
import Goban
|
||||
from playerInterface import *
|
||||
import GnuGo
|
||||
|
||||
|
||||
class myPlayer(PlayerInterface):
|
||||
"""Antoher player example that simply act as a wrapper to my GnuGo.py interface. Allows to play against gnugo."""
|
||||
|
||||
def __init__(self):
|
||||
self._board = Goban.Board()
|
||||
self._gnugo = GnuGo.GnuGo(Goban.Board._BOARDSIZE)
|
||||
self._moves = self._gnugo.Moves(self._gnugo)
|
||||
self._mycolor = None
|
||||
|
||||
def getPlayerName(self):
|
||||
return "Gnugo Player"
|
||||
|
||||
def getPlayerMove(self):
|
||||
if self._board.is_game_over():
|
||||
print("Referee told me to play but the game is over!")
|
||||
return "PASS"
|
||||
# gets the legal moves from Goban (just to write them)
|
||||
board_moves = [Goban.Board.flat_to_name(m) for m in self._board.legal_moves()]
|
||||
print(
|
||||
"Board Legal Moves for player "
|
||||
+ Goban.Board.player_name(self._board._nextPlayer)
|
||||
)
|
||||
(ok, legal) = self._gnugo.query(
|
||||
"all_legal " + Goban.Board.player_name(self._board._nextPlayer)
|
||||
)
|
||||
print("GNUGO Legal Moves are ", legal[1:])
|
||||
|
||||
move = self._moves.getbest()
|
||||
print(
|
||||
"I am playing ", move
|
||||
) # New here: allows to consider internal representations of
|
||||
self._board.push(Goban.Board.name_to_flat(move))
|
||||
self._moves.playthis(move)
|
||||
# moves
|
||||
print("My current board :")
|
||||
self._board.prettyPrint()
|
||||
return move
|
||||
|
||||
def playOpponentMove(self, move):
|
||||
print("Opponent played ", move)
|
||||
self._board.push(Goban.Board.name_to_flat(move))
|
||||
self._moves.playthis(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 :(!!")
|
95
go_player/localGame.py
Normal file
95
go_player/localGame.py
Normal file
@ -0,0 +1,95 @@
|
||||
""""""
|
||||
|
||||
import Goban
|
||||
import myPlayer
|
||||
import randomPlayer
|
||||
import time
|
||||
from io import StringIO
|
||||
import sys
|
||||
|
||||
b = Goban.Board()
|
||||
|
||||
players = []
|
||||
player1 = myPlayer.myPlayer()
|
||||
player1.newGame(Goban.Board._BLACK)
|
||||
players.append(player1)
|
||||
|
||||
player2 = randomPlayer.myPlayer()
|
||||
player2.newGame(Goban.Board._WHITE)
|
||||
players.append(player2)
|
||||
|
||||
totalTime = [0, 0] # total real time for each player
|
||||
nextplayer = 0
|
||||
nextplayercolor = Goban.Board._BLACK
|
||||
nbmoves = 1
|
||||
|
||||
outputs = ["", ""]
|
||||
sysstdout = sys.stdout
|
||||
stringio = StringIO()
|
||||
wrongmovefrom = 0
|
||||
|
||||
while not b.is_game_over():
|
||||
print("Referee Board:")
|
||||
b.prettyPrint()
|
||||
print("Before move", nbmoves)
|
||||
legals = (
|
||||
b.legal_moves()
|
||||
) # legal moves are given as internal (flat) coordinates, not A1, A2, ...
|
||||
print(
|
||||
"Legal Moves: ", [b.move_to_str(m) for m in legals]
|
||||
) # I have to use this wrapper if I want to print them
|
||||
nbmoves += 1
|
||||
otherplayer = (nextplayer + 1) % 2
|
||||
othercolor = Goban.Board.flip(nextplayercolor)
|
||||
|
||||
currentTime = time.time()
|
||||
sys.stdout = stringio
|
||||
move = players[
|
||||
nextplayer
|
||||
].getPlayerMove() # The move must be given by "A1", ... "J8" string coordinates (not as an internal move)
|
||||
sys.stdout = sysstdout
|
||||
playeroutput = stringio.getvalue()
|
||||
stringio.truncate(0)
|
||||
stringio.seek(0)
|
||||
print(("[Player " + str(nextplayer) + "] ").join(playeroutput.splitlines(True)))
|
||||
outputs[nextplayer] += playeroutput
|
||||
totalTime[nextplayer] += time.time() - currentTime
|
||||
print(
|
||||
"Player ",
|
||||
nextplayercolor,
|
||||
players[nextplayer].getPlayerName(),
|
||||
"plays: " + move,
|
||||
) # changed
|
||||
|
||||
if not Goban.Board.name_to_flat(move) in legals:
|
||||
print(otherplayer, nextplayer, nextplayercolor)
|
||||
print("Problem: illegal move")
|
||||
wrongmovefrom = nextplayercolor
|
||||
break
|
||||
b.push(
|
||||
Goban.Board.name_to_flat(move)
|
||||
) # Here I have to internally flatten the move to be able to check it.
|
||||
players[otherplayer].playOpponentMove(move)
|
||||
|
||||
nextplayer = otherplayer
|
||||
nextplayercolor = othercolor
|
||||
|
||||
print("The game is over")
|
||||
b.prettyPrint()
|
||||
result = b.result()
|
||||
print("Time:", totalTime)
|
||||
print("GO Score:", b.final_go_score())
|
||||
print("Winner: ", end="")
|
||||
if wrongmovefrom > 0:
|
||||
if wrongmovefrom == b._WHITE:
|
||||
print("BLACK")
|
||||
elif wrongmovefrom == b._BLACK:
|
||||
print("WHITE")
|
||||
else:
|
||||
print("ERROR")
|
||||
elif result == "1-0":
|
||||
print("WHITE")
|
||||
elif result == "0-1":
|
||||
print("BLACK")
|
||||
else:
|
||||
print("DEUCE")
|
106
go_player/moveSearch.py
Normal file
106
go_player/moveSearch.py
Normal file
@ -0,0 +1,106 @@
|
||||
from sys import stderr
|
||||
import time
|
||||
import math
|
||||
from typing import Any, Callable
|
||||
import Goban
|
||||
|
||||
|
||||
def _next_color(color):
|
||||
return Goban.Board._BLACK if color == Goban.Board._WHITE else Goban.Board._WHITE
|
||||
|
||||
# Returns heuristic, move
|
||||
def _alphabeta(
|
||||
board: Goban.Board,
|
||||
heuristic: Callable[[Goban.Board, Any], float],
|
||||
color,
|
||||
move,
|
||||
alpha=-math.inf,
|
||||
beta=math.inf,
|
||||
depth: int = 3,
|
||||
) -> tuple[float, Any]:
|
||||
|
||||
wantMax = board.next_player != color
|
||||
if depth == 0 or board.is_game_over():
|
||||
return heuristic(board, color), move
|
||||
|
||||
if wantMax:
|
||||
acc = -math.inf, None
|
||||
for move in board.generate_legal_moves():
|
||||
board.push(move)
|
||||
value = (
|
||||
_alphabeta(
|
||||
board,
|
||||
alpha=alpha,
|
||||
beta=beta,
|
||||
move=move,
|
||||
heuristic=heuristic,
|
||||
color=_next_color(color),
|
||||
depth=depth - 1,
|
||||
)[0],
|
||||
move,
|
||||
)
|
||||
acc = max(
|
||||
acc,
|
||||
value,
|
||||
key=lambda t: t[0],
|
||||
)
|
||||
board.pop()
|
||||
|
||||
if acc[0] >= beta:
|
||||
break # beta cutoff
|
||||
alpha = max(alpha, value[0])
|
||||
|
||||
else:
|
||||
acc = math.inf, None
|
||||
for move in board.generate_legal_moves():
|
||||
board.push(move)
|
||||
value = (
|
||||
_alphabeta(
|
||||
board,
|
||||
alpha=alpha,
|
||||
beta=beta,
|
||||
move=move,
|
||||
heuristic=heuristic,
|
||||
color=_next_color(color),
|
||||
depth=depth - 1,
|
||||
)[0],
|
||||
move,
|
||||
)
|
||||
acc = min(
|
||||
acc,
|
||||
value,
|
||||
key=lambda t: t[0],
|
||||
)
|
||||
board.pop()
|
||||
|
||||
if acc[0] <= alpha:
|
||||
break # alpha cutoff
|
||||
beta = min(beta, value[0])
|
||||
|
||||
return acc
|
||||
|
||||
|
||||
def alphabeta(
|
||||
board: Goban.Board,
|
||||
heuristic: Callable[[Goban.Board, Any], float],
|
||||
color,
|
||||
depth: int = 3,
|
||||
):
|
||||
_, move = _alphabeta(board, move=-1, heuristic=heuristic, color=color, depth=depth)
|
||||
return move
|
||||
|
||||
|
||||
def IDDFS(board: Goban.Board, heuristic, color, duration: float, maxdepth=42):
|
||||
st = time.time()
|
||||
depth = 1
|
||||
move = -1
|
||||
|
||||
while time.time() - st < duration and depth < maxdepth:
|
||||
print("depth:", depth, time.time() - st, file=stderr)
|
||||
move = _alphabeta(
|
||||
board, heuristic, color, move=-1, alpha=-10, beta=10, depth=depth
|
||||
)[1]
|
||||
depth += 1
|
||||
|
||||
print(time.time() - st, duration, depth, file=stderr)
|
||||
return move
|
77
go_player/myPlayer.py
Normal file
77
go_player/myPlayer.py
Normal file
@ -0,0 +1,77 @@
|
||||
# -*- 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!
|
||||
"""
|
||||
|
||||
import time
|
||||
import Goban
|
||||
from random import choice
|
||||
from moveSearch import IDDFS, alphabeta
|
||||
from playerInterface import *
|
||||
|
||||
|
||||
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.moveCount = 0
|
||||
|
||||
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 getPlayerMove(self):
|
||||
if self._board.is_game_over():
|
||||
print("Referee told me to play but the game is over!")
|
||||
return "PASS"
|
||||
|
||||
if self.moveCount < 10:
|
||||
max_depth = 1
|
||||
elif self.moveCount < 20:
|
||||
max_depth = 2
|
||||
elif self.moveCount < 40:
|
||||
max_depth = 3
|
||||
else:
|
||||
max_depth = 24
|
||||
|
||||
move = IDDFS(
|
||||
self._board, self.simple_heuristic, self._mycolor, duration=1., maxdepth=max_depth
|
||||
) # IDDFS(self._board, self.simple_heuristic, self._mycolor, 1.)
|
||||
self._board.push(move)
|
||||
|
||||
# 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
|
||||
self.moveCount += 1 if Goban.Board.flat_to_name(move) != "PASS" else 0
|
||||
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.moveCount += 1 if move != "PASS" else 0
|
||||
self._board.push(Goban.Board.name_to_flat(move))
|
||||
|
||||
def newGame(self, color):
|
||||
self._mycolor = color
|
||||
self._opponent = Goban.Board.flip(color)
|
||||
self.moveCount = 0
|
||||
|
||||
def endGame(self, winner):
|
||||
if self._mycolor == winner:
|
||||
print("I won!!!")
|
||||
else:
|
||||
print("I lost :(!!")
|
110
go_player/namedGame.py
Normal file
110
go_player/namedGame.py
Normal file
@ -0,0 +1,110 @@
|
||||
"""Sorry no comments :)."""
|
||||
|
||||
import Goban
|
||||
import importlib
|
||||
import time
|
||||
from io import StringIO
|
||||
import sys
|
||||
|
||||
|
||||
def fileorpackage(name):
|
||||
if name.endswith(".py"):
|
||||
return name[:-3]
|
||||
return name
|
||||
|
||||
|
||||
if len(sys.argv) > 2:
|
||||
classNames = [fileorpackage(sys.argv[1]), fileorpackage(sys.argv[2])]
|
||||
elif len(sys.argv) > 1:
|
||||
classNames = [fileorpackage(sys.argv[1]), "myPlayer"]
|
||||
else:
|
||||
classNames = ["myPlayer", "myPlayer"]
|
||||
|
||||
b = Goban.Board()
|
||||
|
||||
players = []
|
||||
player1class = importlib.import_module(classNames[0])
|
||||
player1 = player1class.myPlayer()
|
||||
player1.newGame(Goban.Board._BLACK)
|
||||
players.append(player1)
|
||||
|
||||
player2class = importlib.import_module(classNames[1])
|
||||
player2 = player2class.myPlayer()
|
||||
player2.newGame(Goban.Board._WHITE)
|
||||
players.append(player2)
|
||||
|
||||
totalTime = [0, 0] # total real time for each player
|
||||
nextplayer = 0
|
||||
nextplayercolor = Goban.Board._BLACK
|
||||
nbmoves = 1
|
||||
|
||||
outputs = ["", ""]
|
||||
sysstdout = sys.stdout
|
||||
stringio = StringIO()
|
||||
wrongmovefrom = 0
|
||||
|
||||
while not b.is_game_over():
|
||||
print("Referee Board:")
|
||||
b.prettyPrint()
|
||||
print("Before move", nbmoves)
|
||||
legals = (
|
||||
b.legal_moves()
|
||||
) # legal moves are given as internal (flat) coordinates, not A1, A2, ...
|
||||
print(
|
||||
"Legal Moves: ", [b.move_to_str(m) for m in legals]
|
||||
) # I have to use this wrapper if I want to print them
|
||||
nbmoves += 1
|
||||
otherplayer = (nextplayer + 1) % 2
|
||||
othercolor = Goban.Board.flip(nextplayercolor)
|
||||
|
||||
currentTime = time.time()
|
||||
sys.stdout = stringio
|
||||
move = players[
|
||||
nextplayer
|
||||
].getPlayerMove() # The move must be given by "A1", ... "J8" string coordinates (not as an internal move)
|
||||
sys.stdout = sysstdout
|
||||
playeroutput = stringio.getvalue()
|
||||
stringio.truncate(0)
|
||||
stringio.seek(0)
|
||||
print(("[Player " + str(nextplayer) + "] ").join(playeroutput.splitlines(True)))
|
||||
outputs[nextplayer] += playeroutput
|
||||
totalTime[nextplayer] += time.time() - currentTime
|
||||
print(
|
||||
"Player ",
|
||||
nextplayercolor,
|
||||
players[nextplayer].getPlayerName(),
|
||||
"plays: " + move,
|
||||
) # changed
|
||||
|
||||
if not Goban.Board.name_to_flat(move) in legals:
|
||||
print(otherplayer, nextplayer, nextplayercolor)
|
||||
print("Problem: illegal move")
|
||||
wrongmovefrom = nextplayercolor
|
||||
break
|
||||
b.push(
|
||||
Goban.Board.name_to_flat(move)
|
||||
) # Here I have to internally flatten the move to be able to check it.
|
||||
players[otherplayer].playOpponentMove(move)
|
||||
|
||||
nextplayer = otherplayer
|
||||
nextplayercolor = othercolor
|
||||
|
||||
print("The game is over")
|
||||
b.prettyPrint()
|
||||
result = b.result()
|
||||
print("Time:", totalTime)
|
||||
print("GO Score:", b.final_go_score())
|
||||
print("Winner: ", end="")
|
||||
if wrongmovefrom > 0:
|
||||
if wrongmovefrom == b._WHITE:
|
||||
print("BLACK")
|
||||
elif wrongmovefrom == b._BLACK:
|
||||
print("WHITE")
|
||||
else:
|
||||
print("ERROR")
|
||||
elif result == "1-0":
|
||||
print("WHITE")
|
||||
elif result == "0-1":
|
||||
print("BLACK")
|
||||
else:
|
||||
print("DEUCE")
|
50
go_player/playerInterface.py
Normal file
50
go_player/playerInterface.py
Normal file
@ -0,0 +1,50 @@
|
||||
class PlayerInterface:
|
||||
"""Abstract class that must be implemented by you AI. Typically, a file "myPlayer.py" will implement it for your
|
||||
AI to enter the tournament.
|
||||
|
||||
You may want to check to player implementations of this interface:
|
||||
- the random player
|
||||
- the gnugo player
|
||||
"""
|
||||
|
||||
def getPlayerName(self):
|
||||
"""Must return the name of your AI player."""
|
||||
return "Not Defined"
|
||||
|
||||
def getPlayerMove(self):
|
||||
"""This is where you will put your AI. This function must return the move as a standard
|
||||
move in GO, ie, "A1", "A2", ..., "D5", ..., "J8", "J9" or "PASS"
|
||||
|
||||
WARNING: In the Board class, legal_moves() and weak_legal_moves() are giving internal
|
||||
coordinates only (to speed up the push/pop methods and the game tree traversal). However,
|
||||
to communicate with this interface, you can't use these moves anymore here.
|
||||
|
||||
You have to use the helper function flat_to_name to translate the internal representation of moves
|
||||
in the Goban.py file into a named move.
|
||||
|
||||
The result of this function must be one element of [Board.flat_to_name(m) for m in b.legal_moves()]
|
||||
(it has to be legal, so at the end, weak_legal_moves() may not be sufficient here.)
|
||||
"""
|
||||
return "PASS"
|
||||
|
||||
def playOpponentMove(self, move):
|
||||
"""Inform you that the oponent has played this move. You must play it with no
|
||||
search (just update your local variables to take it into account)
|
||||
|
||||
The move is given as a GO move string, like "A1", ... "J9", "PASS"
|
||||
|
||||
WARNING: because the method Goban.push(m) needs a move represented as a flat move (integers),
|
||||
you can not directly call this method with the given move here. You will typically call
|
||||
b.push(Board.name_to_flat(move)) to translate the move into its flat (internal) representation.
|
||||
"""
|
||||
pass
|
||||
|
||||
def newGame(self, color):
|
||||
"""Starts a new game, and give you your color. As defined in Goban.py : color=1
|
||||
for BLACK, and color=2 for WHITE"""
|
||||
pass
|
||||
|
||||
def endGame(self, color):
|
||||
"""You can get a feedback on the winner
|
||||
This function gives you the color of the winner"""
|
||||
pass
|
49
go_player/randomPlayer.py
Normal file
49
go_player/randomPlayer.py
Normal file
@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""This is the famous random player whici (almost) always looses."""
|
||||
|
||||
import time
|
||||
import Goban
|
||||
from random import choice
|
||||
from playerInterface import *
|
||||
|
||||
|
||||
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
|
||||
|
||||
def getPlayerName(self):
|
||||
return "Random Player"
|
||||
|
||||
def getPlayerMove(self):
|
||||
if self._board.is_game_over():
|
||||
print("Referee told me to play but the game is over!")
|
||||
return "PASS"
|
||||
moves = self._board.legal_moves() # Dont use weak_legal_moves() here!
|
||||
move = choice(moves)
|
||||
self._board.push(move)
|
||||
|
||||
# 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, "i.e. ", 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 :(!!")
|
1
go_player/requirements.txt
Normal file
1
go_player/requirements.txt
Normal file
@ -0,0 +1 @@
|
||||
numpy
|
68
go_player/starter-go.py
Normal file
68
go_player/starter-go.py
Normal file
@ -0,0 +1,68 @@
|
||||
import time
|
||||
import Goban
|
||||
from random import choice
|
||||
|
||||
|
||||
def randomMove(b):
|
||||
"""Renvoie un mouvement au hasard sur la liste des mouvements possibles. Pour avoir un choix au hasard, il faut
|
||||
construire explicitement tous les mouvements. Or, generate_legal_moves() peut nous donner un itérateur (quand on
|
||||
l'utilise avec pychess)."""
|
||||
return choice(list(b.generate_legal_moves()))
|
||||
|
||||
|
||||
def deroulementRandom(b):
|
||||
"""Déroulement d'une partie de go au hasard des coups possibles. Cela va donner presque exclusivement
|
||||
des parties très longues et sans gagnant. Cela illustre cependant comment on peut jouer avec la librairie
|
||||
très simplement."""
|
||||
print("----------")
|
||||
b.prettyPrint()
|
||||
if b.is_game_over():
|
||||
print("Resultat : ", b.result())
|
||||
return
|
||||
b.push(randomMove(b))
|
||||
deroulementRandom(b)
|
||||
b.pop()
|
||||
|
||||
|
||||
board = Goban.Board()
|
||||
deroulementRandom(board)
|
||||
|
||||
""" Exemple de déroulement random avec weak_legal_moves()"""
|
||||
|
||||
|
||||
def weakRandomMove(b):
|
||||
"""Renvoie un mouvement au hasard sur la liste des mouvements possibles mais attention, dans ce cas
|
||||
weak_legal_moves() peut renvoyer des coups qui entrainent des super ko. Si on prend un coup au hasard
|
||||
il y a donc un risque qu'il ne soit pas légal. Du coup, il faudra surveiller si push() nous renvoie
|
||||
bien True et sinon, défaire immédiatement le coup par un pop() et essayer un autre coup.
|
||||
"""
|
||||
return choice(b.weak_legal_moves())
|
||||
|
||||
|
||||
def weakDeroulementRandom(b):
|
||||
"""Déroulement d'une partie de go au hasard des coups possibles. Cela va donner presque exclusivement
|
||||
des parties très longues. Cela illustre cependant comment on peut jouer avec la librairie
|
||||
très simplement en utilisant les coups weak_legal_moves().
|
||||
|
||||
Ce petit exemple montre comment utiliser weak_legal_moves() plutot que legal_moves(). Vous y gagnerez en efficacité.
|
||||
"""
|
||||
|
||||
print("----------")
|
||||
b.prettyPrint()
|
||||
if b.is_game_over():
|
||||
print("Resultat : ", b.result())
|
||||
return
|
||||
|
||||
while True:
|
||||
# push peut nous renvoyer faux si le coup demandé n'est pas valide à cause d'un superKo. Dans ce cas il faut
|
||||
# faire un pop() avant de retenter un nouveau coup
|
||||
valid = b.push(weakRandomMove(b))
|
||||
if valid:
|
||||
break
|
||||
b.pop()
|
||||
weakDeroulementRandom(b)
|
||||
b.pop()
|
||||
|
||||
|
||||
board = Goban.Board()
|
||||
deroulementRandom(board)
|
Loading…
x
Reference in New Issue
Block a user