Get Started With easyAI¶
The best way to get started is to have a look at A quick example. What follows is a summary of all there is to know about easyAI (you can also find these informations in the documentation of the code).
Defining a game¶
To define a new game, make a subclass of the class easyAI.TwoPlayersGame
, and define these methods:
__init__(self, players, ...)
: initialization of the gamepossible_moves(self)
: returns of all moves allowedmake_move(self, move)
: transforms the game according to the moveis_over(self)
: check whether the game has ended
The following methods are optional:
show(self)
: prints/displays the gamescoring
: gives a score to the current game (for the AI)unmake_move(self, move)
: how to unmake a move (speeds up the AI)ttentry(self)
: returns a string/tuple describing the game.ttrestore(self, entry)
: use string/tuple from ttentry to restore a game.
The __init__
method must do the following actions:
- Store
players
(which must be a list of two Players) into self.players - Tell which player plays first with
self.nplayer = 1 # or 2
When defining possible_moves
, scoring
, etc. you must keep in mind that you are in the scope of the current player. More precisely, a subclass of TwoPlayersGame has the following attributes that indicate whose turn it is. These attributes can be used but should not be overwritten:
self.player
: the current Player (e.g. aHuman_Player()
).self.opponent
: the current Player’s opponent (Player).self.nplayer
: the number (1 or 2) of the current player.self.nopponent
: the number (1 or 2) of the opponent.self.nmove
: How many moves have been played so far ?
To start a game you will write something like this
game = MyGame(players = [player_1, player_2], *other_arguments)
history = game.play() # start the match !
When the game ends it stores the history into the variable history
. The history is a list [(g1,m1),(g2,m2)...] where gi is a copy of the game after i moves and mi is the move made by the player whose turn it was. So for instance:
history = game.play()
game8, move8 = history[8]
game9, move9 = history[9]
game8.make_move( move8 ) # Now game8 and game9 are alike.
Human and AI players¶
The players can be either a Human_Player()
(which will be asked interactively which moves it wants to play) or a AI_Player(algo)
, so you will have for instance
game = MyGame( [ Human_Player(), AI_Player(algo) ])
If you are a human player you will be asked to enter a move when it is your turn. You can also enter show moves
to have a list of all moves allowed, or quit
to quit.
The variable algo is any function f(game)->move
. It can be an algorithm that determines the best move by thinking N turns in advance:
from easyAI import AI_Player, Negamax
ai_player = AI_Player( Negamax(9) )
Or a transposition table (see below) filled in a previous game:
ai_player = AI_Player( transpo_table )
The Negamax algorithm will always look for the shortest path to victory, or the longest path to defeat. It is possible to go faster by not optimizing this (the disadvantage being that the AI can then make suicidal moves if it has found that it will eventually lose against a perfect opponent). To do so, you must provide the argument win_score
to Negamax which indicates above which score a score is considered a win. Keep in mind that the AI adds maluses to the score, so if your scoring function looks like this
scoring = lambda game: 100 if game.win() else 0
you should write Negamax(9, win_score=90)
.
Interactive Play¶
If you are needing to be more interactive with the game play, such as when integrating with other frameworks, you can use the get_move
and play_move
methods instead. get_move
get’s an AI player’s decision. play_move
executes a move (for either player). To illustrate
game.play()
is functionally the same as
while not game.is_over():
game.show()
if game.nplayer==1: # we are assuming player 1 is a Human_Player
poss = game.possible_moves()
for index, move in enumerate(poss):
print("{} : {}".format(index, move))
index = int(input("enter move: "))
move = poss[index]
else: # we are assuming player 2 is an AI_Player
move = game.get_move()
print("AI plays {}".format(move))
game.play_move(move)
Solving a game¶
You can try to solve a game (i.e. determine who will win if both players play perfectly and extract a winning strategy). There are two available algorithms to do so:
id_solve solves a game using iterative deepening: it explores the game by using several times the Negamax algorithm, always starting at the initial state of the game, but taking increasing depth (in the list ai_depths) until the score of the initial condition indicates that the first player will certainly win or loose, at which case it stops:
from easyAI import id_solve
r,d,m = id_solve( MyGame, ai_depths=range(2,20), win_score=100)
Note that the first argument can be either a game instance or a game class. We obtain r=1
, meaning that if both players play perfectly, the first player to play can always win (-1 would have meant always lose), d=10
, which means that the wins will be in ten moves (i.e. 5 moves per player) or less, and m='3'
, which indicates that the first player’s first move should be '3'
.
df_solve solves a game using a depth-first search (therefore it cannot be used for games that can have an infinite number of moves). The game is explored until endgames are reached and these endgames are evaluated to see if their are victories or defeats (or draws). Then, a situation in which every move leads to a defeat is labelled as a (certain) defeat, and a situation in which one move leads to a (certain) defeat of the opponent is labelled as a (certain) victory. This way we come back up to the root (initial condition) which receives a label, which is returned.
from easyAI import df_solve
game = MyGame(players = [... , ...]) # the players are not important
tt = TT() # optional, will speed up the algo
r = df_solve(game, winscore= 90, tt = tt)
After this r
is either -1 (certain defeat of the first player against a perfect opponent), 0 (it is possible to force a draw, but not to win), or 1 (certain victory if the first player plays perfectly).