feat: create the basic player using iddfs for go

This commit is contained in:
Nemo D'ACREMONT 2025-05-17 15:36:19 +02:00
parent fdd51b5857
commit 7b418ab5cb
No known key found for this signature in database
GPG Key ID: 85F245EC3BB1E022
14 changed files with 1909 additions and 2 deletions

6
go_ia/.gitignore vendored
View File

@ -1,8 +1,10 @@
__pycache__
/venv
/.venv
/samples-8x8.json.gz
/samples-8x8.json
/*.json.gz
/*.json
*.pt
*.gz
*.txt
/rendu

View File

@ -1,3 +1,6 @@
__pycache__
/venv
/.venv
/go-package.tgz
*.ipynb
/chess

157
go_player/GnuGo.py Normal file
View 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

File diff suppressed because it is too large Load Diff

91
go_player/README.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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")

View 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
View 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 :(!!")

View File

@ -0,0 +1 @@
numpy

68
go_player/starter-go.py Normal file
View 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)