Integrating easyAI with other frameworks

The primary means of executing easyAI is with the play method of TwoPlayersGame. That method handles getting human input and executing AI functions from start to finish.

But, when using easyAI with other frameworks, one must often break down the steps execution. For that, use the get_move method to get an AI players decision, and the play_move to properly execute a turn.

Here are some games implementations using other frameworks provided in the examples folder of easyAI.

Tic-Tac-Toe Using Flask

from easyAI import TwoPlayersGame, Human_Player, AI_Player, Negamax
from flask import Flask, render_template_string, request, make_response


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

    WIN_LINES = [
        [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 lose(self, who=None):
        """ Has the opponent "three in line ?" """
        if who is None:
            who = self.nopponent
        wins = [all(
            [(self.board[c - 1] == who) for c in line]
        ) for line in self.WIN_LINES]
        return any(wins)

    def is_over(self):
        return (self.possible_moves() == []) or self.lose() or \
            self.lose(who=self.nplayer)

    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 spot_string(self, i, j):
        return ["_", "O", "X"][self.board[3 * j + i]]

    def scoring(self):
        opp_won = self.lose()
        i_won = self.lose(who=self.nplayer)
        if opp_won and not i_won:
            return -100
        if i_won and not opp_won:
            return 100
        return 0

    def winner(self):
        if self.lose(who=2):
            return "AI Wins"
        return "Tie"


TEXT = '''
<!doctype html>
<html>
  <head><title>Tic Tac Toe</title></head>
  <body>
    <h1>Tic Tac Toe</h1>
    <h2>{{msg}}</h2>
    <form action="" method="POST">
      <table>
        {% for j in range(2, -1, -1) %}
        <tr>
          {% for i in range(0, 3) %}
          <td>
            <button type="submit" name="choice" value="{{j*3+i+1}}"
             {{"disabled" if ttt.spot_string(i, j)!="_"}}>
              {{ttt.spot_string(i, j)}}
            </button>
          </td>
          {% endfor %}
        </tr>
        {% endfor %}
      </table>
      <button type="submit" name="reset">Start Over</button>
    </form>
  </body>
</html>
'''

app = Flask(__name__)
ai_algo = Negamax(6)


@app.route("/", methods=['GET', 'POST'])
def play_game():
    ttt = TicTacToe([Human_Player(), AI_Player(ai_algo)])
    game_cookie = request.cookies.get('game_board')
    if game_cookie:
        ttt.board = [int(x) for x in game_cookie.split(",")]
    if "choice" in request.form:
        ttt.play_move(request.form["choice"])
        if not ttt.is_over():
            ai_move = ttt.get_move()
            ttt.play_move(ai_move)
    if "reset" in request.form:
        ttt.board = [0 for i in range(9)]
    if ttt.is_over():
        msg = ttt.winner()
    else:
        msg = "play move"
    resp = make_response(render_template_string(TEXT, ttt=ttt, msg=msg))
    c = ",".join(map(str, ttt.board))
    resp.set_cookie("game_board", c)
    return resp


if __name__ == "__main__":
    app.run()

Game of Knights using Kivy

import numpy as np
from easyAI import TwoPlayersGame, Human_Player, AI_Player, Negamax
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout


# 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]
]))

BOARD_SIZE = (5, 5)

# 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])

SQUARE_COLORS = [
    (0.8, 0.8, 0.8, 1),  # empty
    (0.5, 0.5, 1.0, 1),  # player 1
    (1.0, 1.0, 0.8, 1),  # player 2
    (0.8, 0.0, 0.0, 1)   # occupied
]
SQUARE_TEXT = [
    " ",
    "K1",
    "K2",
    "X"
]

AI = Negamax(11)

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
            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 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()


class KnightsKivyApp(App):

    def build(self):
        layout = BoxLayout(padding=10, orientation="vertical")

        self.msg_button = Button(text="K1, it is your turn.")
        layout.add_widget(self.msg_button)

        self.squares = [[] for _ in range(BOARD_SIZE[1])]
        for i in range(BOARD_SIZE[1]):
            h_layout = BoxLayout(padding=1)
            for j in range(BOARD_SIZE[0]):
                new_button = Button(on_press=self.do_move)
                new_button.location = (i, j)
                self.squares[i].append(new_button)
                h_layout.add_widget(new_button)
            layout.add_widget(h_layout)

        self.reset_button = Button(
            text="[start over]", on_press=self.reset_board
        )
        layout.add_widget(self.reset_button)

        self.refresh_board()

        return layout

    def do_move(self, btn):
        move = pos2string(btn.location)
        if move in self.game.possible_moves():
            self.game.play_move(move)
            self.refresh_board()
            if not self.game.is_over():
                self.msg_button.text = "AI is thinking. Please wait."
                move = self.game.get_move()
                self.game.play_move(move)
                self.msg_button.text = "K1, it is your turn."
        else:
            self.msg_button.text = "Invalid move. Try again."
        self.refresh_board()

    def refresh_board(self):
        for i in range(BOARD_SIZE[1]):
            for j in range(BOARD_SIZE[0]):
                self.squares[i][j].text = SQUARE_TEXT[self.game.board[i, j]]
                self.squares[i][j].background_color = \
                    SQUARE_COLORS[self.game.board[i, j]]
        if self.game.is_over():
            self.msg_button.text = "Game over. {} wins.".format(
                SQUARE_TEXT[self.game.nopponent]
            )

    def reset_board(self, btn):
        self.game = Knights([Human_Player(), AI_Player(AI)], BOARD_SIZE)
        self.refresh_board()


if __name__ == "__main__":
    board_size = (5, 5)
    game = Knights([Human_Player(), AI_Player(AI)], BOARD_SIZE)

    app = KnightsKivyApp()
    app.game = game
    app.run()