Games implemented with easyAI¶
Here are reported some games implementations provided in the
examples
folder of easyAI.
Tic-Tac-Toe¶
from easyAI import TwoPlayersGame
from easyAI.Player import Human_Player
class TicTacToe( TwoPlayersGame ):
""" The board positions are numbered as follows:
7 8 9
4 5 6
1 2 3
"""
def __init__(self, players):
self.players = players
self.board = [0 for i in range(9)]
self.nplayer = 1 # player 1 starts.
def possible_moves(self):
return [i+1 for i,e in enumerate(self.board) if e==0]
def make_move(self, move):
self.board[int(move)-1] = self.nplayer
def unmake_move(self, move): # optional method (speeds up the AI)
self.board[int(move)-1] = 0
def lose(self):
""" Has the opponent "three in line ?" """
return any( [all([(self.board[c-1]== self.nopponent)
for c in line])
for line in [[1,2,3],[4,5,6],[7,8,9], # horiz.
[1,4,7],[2,5,8],[3,6,9], # vertical
[1,5,9],[3,5,7]]]) # diagonal
def is_over(self):
return (self.possible_moves() == []) or self.lose()
def show(self):
print ('\n'+'\n'.join([
' '.join([['.','O','X'][self.board[3*j+i]]
for i in range(3)])
for j in range(3)]) )
def scoring(self):
return -100 if self.lose() else 0
if __name__ == "__main__":
from easyAI import AI_Player, Negamax
ai_algo = Negamax(6)
TicTacToe( [Human_Player(),AI_Player(ai_algo)]).play()
Game of Nim¶
from easyAI import TwoPlayersGame
class Nim(TwoPlayersGame):
"""
The game starts with 4 piles of 5 pieces. In turn the players
remove as much pieces as they want, but from one pile only. The
player that removes the last piece loses.
Arguments:
- players: the players...
- piles: the piles the game starts with. With piles=[2,3,4,4] the
game will start with 1 pile of 2 pieces, 1 pile of 3 pieces, and 2
piles of 4 pieces. If set to None, the default will be [5,5,5,5]
"""
def __init__(self, players, piles = None):
""" Default for `piles` is 5 piles of 5 pieces. """
self.players = players
self.piles = piles if (piles != None) else [5, 5, 5, 5]
self.nplayer = 1 # player 1 starts.
def possible_moves(self):
return ["%d,%d" % (i + 1, j) for i in range(len(self.piles))
for j in range(1, self.piles[i] + 1)]
def make_move(self, move):
move = list(map(int, move.split(',')))
self.piles[move[0] - 1] -= move[1]
def unmake_move(self, move): # optional, speeds up the AI
move = list(map(int, move.split(',')))
self.piles[move[0] - 1] += move[1]
def show(self): print(" ".join(map(str, self.piles)))
def win(self): return max(self.piles) == 0
def is_over(self): return self.win()
def scoring(self): return 100 if self.win() else 0
def ttentry(self): return tuple(self.piles) #optional, speeds up AI
if __name__ == "__main__":
# IN WHAT FOLLOWS WE SOLVE THE GAME AND START A MATCH AGAINST THE AI
from easyAI import AI_Player, Human_Player, Negamax, id_solve
from easyAI.AI import TT
# we first solve the game
w, d, m, tt = id_solve(Nim, range(5, 20), win_score = 80)
print
w, d, len(tt.d)
# the previous line prints -1, 16 which shows that if the
# computer plays second with an AI depth of 16 (or 15) it will
# always win in 16 (total) moves or less.
# Now let's play (and lose !) against the AI
ai = Negamax(16, tt = TT())
game = Nim([Human_Player(), AI_Player(tt)])
game.play() # You will always lose this game !
print("player %d wins" % game.nplayer)
# Note that with the transposition table tt generated by id_solve
# we can setup a perfect AI which doesn't have to think:
# >>> game = Nim( [ Human_Player(), AI_Player( tt )])
# >>> game.play() # You will always lose this game too!
Game of Knights¶
import numpy as np
from easyAI import TwoPlayersGame
# directions in which a knight can move
DIRECTIONS = list(map(np.array, [[1, 2], [-1, 2], [1, -2], [-1, -2],
[2, 1], [2, -1], [-2, 1], [-2, -1]]))
# functions to convert "D8" into (3,7) and back...
pos2string = lambda ab: "ABCDEFGH"[ab[0]] + str(ab[1] + 1)
string2pos = lambda s: np.array(["ABCDEFGH".index(s[0]), int(s[1])-1])
class Knights(TwoPlayersGame):
"""
Each player has a chess knight (that moves in "L") on a chessboard.
Each turn the player moves the knight to any tile that hasn't been
occupied by a knight before. The first player that cannot move loses.
"""
def __init__(self, players, board_size = (8, 8)):
self.players = players
self.board_size = board_size
self.board = np.zeros(board_size, dtype = int)
self.board[0, 0] = 1
self.board[board_size[0] - 1, board_size[1] - 1] = 2
players[0].pos = np.array([0, 0])
players[1].pos = np.array([board_size[0] - 1, board_size[1]-1])
self.nplayer = 1 # player 1 starts.
def possible_moves(self):
endings = [self.player.pos + d for d in DIRECTIONS]
return [pos2string(e) for e in endings # all positions
if (e[0] >= 0) and (e[1] >= 0) and
(e[0] < self.board_size[0]) and
(e[1] < self.board_size[1]) and # inside the board
self.board[e[0], e[1]] == 0] # and not blocked
def make_move(self, pos):
pi, pj = self.player.pos
self.board[pi, pj] = 3 # 3 means blocked
self.player.pos = string2pos(pos)
pi, pj = self.player.pos
self.board[pi, pj] = self.nplayer # place player on board
def ttentry(self):
e = [tuple(row) for row in self.board]
e.append(pos2string(self.players[0].pos))
e.append(pos2string(self.players[1].pos))
return tuple(e)
def ttrestore(self, entry):
for x, row in enumerate(entry[:self.board_size[0]]):
for y, n in enumerate(row):
self.board[x, y] = n
self.players[0].pos = string2pos(entry[-2])
self.players[1].pos = string2pos(entry[-1])
def show(self):
print('\n' + '\n'.join([' 1 2 3 4 5 6 7 8'] +
['ABCDEFGH'[k] +
' ' + ' '.join([['.', '1', '2', 'X'][self.board[k, i]]
for i in range(self.board_size[0])])
for k in range(self.board_size[1])] + ['']))
def lose(self):
return self.possible_moves() == []
def scoring(self):
return -100 if (self.possible_moves() == []) else 0
def is_over(self):
return self.lose()
if __name__ == "__main__":
from easyAI import AI_Player, Negamax
ai_algo = Negamax(11)
game = Knights([AI_Player(ai_algo), AI_Player(ai_algo)], (5, 5))
game.play()
print("player %d loses" % (game.nplayer))
Awele¶
try:
import numpy as np
except ImportError:
print("Sorry, this example requires Numpy installed !")
raise
from easyAI import TwoPlayersGame
class Awele(TwoPlayersGame):
"""
Rules are as defined as in http://en.wikipedia.org/wiki/Oware
with the additional rule that the game ends when then are 6 seeds
left in the game.
"""
def __init__(self, players):
for i, player in enumerate(players):
player.score = 0
player.isstarved = False
player.camp = i
self.players = players
# Initial configuration of the board.
# holes are indexed by a,b,c,d...
self.board = [4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4]
self.nplayer = 1 # player 1 starts.
def make_move(self, move):
if move == "None":
self.player.isstarved = True
s = 6 * self.opponent.camp
self.player.score += sum(self.board[s:s + 6])
return
move = 'abcdefghijkl'.index(move)
pos = move
for i in range(self.board[move]): #DEAL
pos = (pos + 1) % 12
if pos == move:
pos = (pos + 1) % 12
self.board[pos] += 1
self.board[move] = 0
while ((pos / 6) == self.opponent.camp
and (self.board[pos] in [2, 3])): # TAKE
self.player.score += self.board[pos]
self.board[pos] = 0
pos = (pos - 1) % 12
def possible_moves(self):
"""
A player must play any hole that contains enough seeds to
'feed' the opponent. This no hole has this many seeds, any
non-empty hole can be played.
"""
if self.nplayer == 1:
if max(self.board[:6]) == 0: return ['None']
moves = [i for i in range(6) if (self.board[i] >= 6 - i)]
if moves == []:
moves = [i for i in range(6) if self.board[i] != 0]
else:
if max(self.board[6:]) == 0: return ['None']
moves = [i for i in range(6,12) if (self.board[i] >= 12-i)]
if moves == []:
moves = [i for i in range(6, 12) if self.board[i] != 0]
return ['abcdefghijkl'[u] for u in moves]
def show(self):
""" Prints the board, with the hole's respective letters """
print("Score: %d / %d" % tuple(p.score for p in self.players))
print(' '.join('lkjihg'))
print(' '.join(["%02d" % i for i in self.board[-1:-7:-1]]))
print(' '.join(["%02d" % i for i in self.board[:6]]))
print(' '.join('abcdef'))
def lose(self):
return self.opponent.score > 24
def is_over(self):
return ( self.lose() or
sum(self.board) < 7 or
self.opponent.isstarved )
if __name__ == "__main__":
# In what follows we setup the AI and launch a AI-vs-AI match.
from easyAI import Human_Player, AI_Player, Negamax
# this shows that the scoring can be defined in the AI algo,
# which enables 2 AIs with different scorings to play a match.
scoring = lambda game: game.player.score - game.opponent.score
ai = Negamax(6, scoring)
game = Awele([AI_Player(ai), AI_Player(ai)])
game.play()
if game.player.score > game.opponent.score:
print("Player %d wins." % game.nplayer)
elif game.player.score < game.opponent.score:
print("Player %d wins." % game.nopponent)
else:
print("Looks like we have a draw.")
Connect 4¶
try:
import numpy as np
except ImportError:
print("Sorry, this example requires Numpy installed !")
raise
from easyAI import TwoPlayersGame
class ConnectFour(TwoPlayersGame):
"""
The game of Connect Four, as described here:
http://en.wikipedia.org/wiki/Connect_Four
"""
def __init__(self, players, board = None):
self.players = players
self.board = board if (board != None) else (
np.array([[0 for i in range(7)] for j in range(6)]))
self.nplayer = 1 # player 1 starts.
def possible_moves(self):
return [i for i in range(7) if (self.board[:, i].min() == 0)]
def make_move(self, column):
line = np.argmin(self.board[:, column] != 0)
self.board[line, column] = self.nplayer
def show(self):
print('\n' + '\n'.join(
['0 1 2 3 4 5 6', 13 * '-'] +
[' '.join([['.', 'O', 'X'][self.board[5 - j][i]]
for i in range(7)]) for j in range(6)]))
def lose(self):
return find_four(self.board, self.nopponent)
def is_over(self):
return (self.board.min() > 0) or self.lose()
def scoring(self):
return -100 if self.lose() else 0
def find_four(board, nplayer):
"""
Returns True iff the player has connected 4 (or more)
This is much faster if written in C or Cython
"""
for pos, direction in POS_DIR:
streak = 0
while (0 <= pos[0] <= 5) and (0 <= pos[1] <= 6):
if board[pos[0], pos[1]] == nplayer:
streak += 1
if streak == 4:
return True
else:
streak = 0
pos = pos + direction
return False
POS_DIR = np.array([[[i, 0], [0, 1]] for i in range(6)] +
[[[0, i], [1, 0]] for i in range(7)] +
[[[i, 0], [1, 1]] for i in range(1, 3)] +
[[[0, i], [1, 1]] for i in range(4)] +
[[[i, 6], [1, -1]] for i in range(1, 3)] +
[[[0, i], [1, -1]] for i in range(3, 7)])
if __name__ == '__main__':
# LET'S PLAY !
from easyAI import Human_Player, AI_Player, Negamax, SSS, DUAL
ai_algo_neg = Negamax(5)
ai_algo_sss = SSS(5)
game = ConnectFour([AI_Player(ai_algo_neg), AI_Player(ai_algo_sss)])
game.play()
if game.lose():
print("Player %d wins." % (game.nopponent))
else:
print("Looks like we have a draw.")