Reference Manual¶
Games¶
-
class
easyAI.
TwoPlayersGame
[source]¶ Base class for... wait for it... two-players games !
To define a new game, make a subclass of TwoPlayersGame, and define the following 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
, 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 methods can be used but should not be overwritten:self.player
: the current player (e.g.Human_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 ?
For more, see the examples in the dedicated folder.
-
get_move
()[source]¶ Method for getting a move from the current player. If the player is an AI_Player, then this method will invoke the AI algorithm to choose the move. If the player is a Human_Player, then the interaction with the human is via the text terminal.
-
play
(nmoves=1000, verbose=True)[source]¶ Method for starting the play of a game to completion. If one of the players is a Human_Player, then the interaction with the human is via the text terminal.
Parameters: nmoves:
The limit of how many moves (plies) to play unless the game ends on it’s own first.
verbose:
Setting verbose=True displays additional text messages.
Players¶
AI algorithms¶
-
class
easyAI.AI.
Negamax
(depth, scoring=None, win_score=inf, tt=None)[source]¶ This implements Negamax on steroids. The following example shows how to setup the AI and play a Connect Four game:
>>> from easyAI.games import ConnectFour >>> from easyAI import Negamax, Human_Player, AI_Player >>> scoring = lambda game: -100 if game.lose() else 0 >>> ai_algo = Negamax(8, scoring) # AI will think 8 turns in advance >>> game = ConnectFour([Human_Player(), AI_Player(ai_algo)]) >>> game.play()
Parameters: depth:
How many moves in advance should the AI think ? (2 moves = 1 complete turn)
scoring:
- A function f(game)-> score. If no scoring is provided
and the game object has a
scoring
method it ill be used.
win_score:
- Score above which the score means a win. This will be
used to speed up computations if provided, but the AI will not differentiate quick defeats from long-fought ones (see next section).
tt:
A transposition table (a table storing game states and moves) scoring: can be none if the game that the AI will be given has a
scoring
method.Notes
The score of a given game is given by
>>> scoring(current_game) - 0.01*sign*current_depth
for instance if a lose is -100 points, then losing after 4 moves will score -99.96 points but losing after 8 moves will be -99.92 points. Thus, the AI will chose the move that leads to defeat in 8 turns, which makes it more difficult for the (human) opponent. This will not always work if a
win_score
argument is provided.
-
class
easyAI.AI.
NonRecursiveNegamax
(depth, scoring=None, win_score=inf, tt=None)[source]¶ This implements Negamax without recursion. The following example shows how to setup the AI and play a Connect Four game:
>>> from easyAI.games import ConnectFour >>> from easyAI import NonRecursiveNegamax, Human_Player, AI_Player >>> scoring = lambda game: -100 if game.lose() else 0 >>> ai_algo = NonRecursiveNegamax(8, scoring) # AI will think 8 turns in advance >>> game = ConnectFour([Human_Player(), AI_Player(ai_algo)]) >>> game.play()
This algorithm also REQUIRES that the game class support the
ttentry
andttrestore
methods.This algorithm ignores any optional
unmake_move
method in the game class.This version of Negamax does not support transposition tables.
Parameters: depth:
How many moves in advance should the AI think ? (2 moves = 1 complete turn)
scoring:
- A function f(game)-> score. If no scoring is provided
and the game object has a
scoring
method it will be used.
win_score:
Score above which the score means a win.
tt:
A transposition table (a table storing game states and moves). Currently, this parameter is ignored.
-
class
easyAI.AI.
DUAL
(depth, scoring=None, win_score=100000, tt=None)[source]¶ This implements DUAL algorithm. The following example shows how to setup the AI and play a Connect Four game:
>>> from easyAI import Human_Player, AI_Player, DUAL >>> AI = DUAL(7) >>> game = ConnectFour([AI_Player(AI),Human_Player()]) >>> game.play()
Parameters: depth:
How many moves in advance should the AI think ? (2 moves = 1 complete turn)
scoring:
- A function f(game)-> score. If no scoring is provided
and the game object has a
scoring
method it ill be used.
win_score:
Score LARGER than the largest score of game, but smaller than inf. It’s required to run algorithm.
tt:
A transposition table (a table storing game states and moves) scoring: can be none if the game that the AI will be given has a
scoring
method.Notes
The score of a given game is given by
>>> scoring(current_game) - 0.01*sign*current_depth
for instance if a lose is -100 points, then losing after 4 moves will score -99.96 points but losing after 8 moves will be -99.92 points. Thus, the AI will chose the move that leads to defeat in 8 turns, which makes it more difficult for the (human) opponent. This will not always work if a
win_score
argument is provided.
-
class
easyAI.AI.
SSS
(depth, scoring=None, win_score=100000, tt=None)[source]¶ This implements SSS* algorithm. The following example shows how to setup the AI and play a Connect Four game:
>>> from easyAI import Human_Player, AI_Player, SSS >>> AI = SSS(7) >>> game = ConnectFour([AI_Player(AI),Human_Player()]) >>> game.play()
Parameters: depth:
How many moves in advance should the AI think ? (2 moves = 1 complete turn)
scoring:
- A function f(game)-> score. If no scoring is provided
and the game object has a
scoring
method it ill be used.
win_score:
Score LARGER than the largest score of game, but smaller than inf. It’s required to run algorithm.
tt:
A transposition table (a table storing game states and moves) scoring: can be none if the game that the AI will be given has a
scoring
method.Notes
The score of a given game is given by
>>> scoring(current_game) - 0.01*sign*current_depth
for instance if a lose is -100 points, then losing after 4 moves will score -99.96 points but losing after 8 moves will be -99.92 points. Thus, the AI will chose the move that leads to defeat in 8 turns, which makes it more difficult for the (human) opponent. This will not always work if a
win_score
argument is provided.
Transposition tables¶
-
class
easyAI.AI.
TT
(own_dict=None)[source]¶ A tranposition table made out of a Python dictionnary.
It creates a “cache” of already resolved moves that can, under some circumstances, let the algorithm run faster.
This table can be stored to file, allowing games to be stopped and restarted at a later time. Or, if the game is fully solved, the cache can return the correct moves nearly instantly because the AI alogorithm no longer has to compute correct moves.
Transposition tables can only be used on games which have a method game.ttentry() -> string or tuple
To save the table as a pickle file, use the tofile and fromfile methods. A pickle file is binary and usually faster. A pickle file can also be appended to with new cached data. See python’s pickle documentation for secuirty issues.
To save the table as a universal JSON file, use the to_json_file and from_json_file methods. For these methods, you must explicity pass use_tuples=True if game.ttentry() returns tuples rather than strings.
Usage:
>>> table = TT() >>> ai = Negamax(8, scoring, tt = table) >>> ai(some_game) # computes a move, fills the table >>> table.tofile('saved_tt.data') # maybe save for later ?
>>> # later (or in a different program)... >>> table = TT().fromfile('saved_tt.data') >>> ai = Negamax(8, scoring, tt = table)
Transposition tables can also be used as an AI (
AI_player(tt)
) but they must be exhaustive in this case: if they are asked for a position that isn’t stored in the table, it will lead to an error.-
from_json_file
(filename, use_tuples=False)[source]¶ Loads a transposition table previously saved with
TT.to_json_file
-
lookup
(game)[source]¶ Requests the entry in the table. Returns None if the entry has not been previously stored in the table.
-
Solving Games¶
-
easyAI.AI.solving.
id_solve
(game, ai_depths, win_score, scoring=None, tt=None, verbose=True)[source]¶ Solves a game using iterative deepening, i.e. determines if by playing perfectly the first player can force a win, or whether it will always lose against a perfect opponent.
This algorithm 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, in which case it stops. The use of transposition table leads to speed gain as the results of shallower searches are used to help exploring the deeper ones.
Parameters: ai_depths:
List of AI depths to try (e.g. [5,6,7,8,9,10])
win_score:
Score above which a score means a win.
scoring:
Scoring function (see doc of class Negamax)
tt:
An optional transposition table to speed up computations.
verbose:
If set to
True
, will print a summary of the best move after each depth tried.Returns: (result, depth, move):
As below
result:
Either 1 (certain victory of the first player) or -1 (certain defeat) or 0 (either draw, or the search was not deep enough)
depth:
The minimal number of moves before victory (or defeat)
move:
Best move to play for the first player.
Also returns with
tt
set.Will be None if
use_tt
was set to false, else will be a transposition table containing all the relevant situations to play a perfect game and can be used withAI_player(tt)
-
easyAI.AI.solving.
df_solve
(game, win_score, maxdepth=50, tt=None, depth=0)[source]¶ Solves a game using a depth-first search: the game is explored until endgames are reached.
The endgames are evaluated to see if there are victories or defeats. 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. Situations are evaluated until the initial condition receives a label (victory or defeat). Draws are also possible.
This algorithm can be faster but less informative than
id_solve
, as it does not provide ‘optimal’ strategies (like shortest path to the victory). It returns simply 1, 0, or -1 to indicate certain victory, draw, or defeat of the first player.Parameters: game:
An Game instance, initialized and ready to be played.
win_score:
Score above which a score means a win.
maxdepth:
Maximal recursion depth allowed.
tt:
An optional transposition table to speed up computations.
depth:
Index of the current depth (don’t touch that).
Returns: result
Either 1 (certain victory of the first player) or -1 (certain defeat) or 0 (either draw, or the search was not deep enough)