Connect4 Tutorial
IArena is a library that implements several games, in order to allow developing AI players that can be tested on them. In this tutorial, we will see each element involved in the player creation. We will illustrate it by showing how to create and test a player for the game Connect 4.
Installation
In order to work with IArena, you need to install it. You can do it by running the following command:
pip install --upgrade git+https://github.com/jparisu/IArena.git
or by adding the following line in a Jupyter notebook:
%pip install --upgrade git+https://github.com/jparisu/IArena.git
For more installation options, please refer to Installation.
Game
First of all, let’s focus on the game we want to play. For this example, we will use the game Connect4. However, this is extrapolated to any other game implemented in IArena.
Position
First of all, in a game we have a Position class.
This object, that inherits from IPosition class, holds the current state of the game.
In the case of Connect4, the position object is Connect4Position:
This position object holds 2 main variables, accessible by the following methods:
next_playe(): The next player to play, by a0or1value.get_matrix(): The board state, by a matrix defined asList[List[int]]where: -Connect4Position.EMPTY_CELL = -1is an empty cell. -0is a player 0 token. -1is a player 1 token.
In the following snippet, we can see how to create an empty board and how to get the matrix and player from it:
from IArena.games.Connect4 import Connect4Position # Import the Connect4Position object
matrix = [[Connect4Position.EMPTY_CELL for _ in range(7)] for _ in range(6)] # Create an empty 6x7 board
position = Connect4Position(None, matrix=matrix) # Create a position object with the empty board
print(f'Board state: {position}')
following_player = position.next_player() # Get the next player to play
print(f'Next player: {following_player}')
position_matrix = position.get_matrix() # Get the matrix of the position
print(f'Position matrix: {position_matrix}')
Note
We will discuss the None parameter later.
Movement
In a game, we have a set of possible Movements.
In the case of Connect4, the movements are defined by the columns where the player can drop a token.
The movements are defined by the Connect4Movement object, an inherited class from IMovement.
This object has the attribute n that defines the column where the token will be dropped.
For example, to drop a token in column 0, we can do:
from IArena.games.Connect4 import Connect4Movement # Import the Connect4Movement class
movement = Connect4Movement(n=0) # Drop a token in column 0
print(f'Movement: {movement}')
Game
Finally, we have the GameRule class.
This object, that inherits from IGameRules, holds the game rules and the game state.
In the case of Connect4, the game object is Connect4Game.
from IArena.games.Connect4 import Connect4Rules # Import the Connect4Rules class
game = Connect4Rules() # Create a game object
The game object has the following methods (as every other IGameRules):
n_players() -> int: Returns the number of players.first_position() -> Connect4Position: Returns the first position of the game.next_position(movement: Connect4Movement, position: Connect4Position) -> Connect4Position: Returns the next position given a movement and a position.possible_movements(position: Connect4Position) -> List[Connect4Movement]: Returns the possible movements given a position.finished(position: Connect4Position) -> bool: Returns whether the game is finished or not.score(position: Connect4Position) -> ScoreBoard: Returns the ScoreBoard of the game.
Example
Let’s see an example on how to create a play of Connect4:
from IArena.games.Connect4 import Connect4Movement, Connect4Position, Connect4Rules
# CREATE GAME RULES
game = Connect4Rules() # Default game rules
# GET FIRST POSITION
position = game.first_position() # Default first position with 6x7 empty board
print(f'Initial position: {position}')
# GET POSSIBLE MOVEMENTS
possible_movements = game.possible_movements(position)
print(f'Possible movements: {" ; ".join([str(m) for m in possible_movements])}')
# PLAY A MOVEMENT
movement = possible_movements[0]
position = game.next_position(movement, position)
print(f'Next position: {position}')
# CHECK IF GAME IS FINISHED
finished = game.finished(position)
print(f'Game finished: {finished}')
# LET'S FORCE A WIN
for i in range(3): # Drop 3 1's tokens in column 1, and 3 0's tokens in column 0
movement = possible_movements[1]
position = game.next_position(movement, position)
movement = possible_movements[0]
position = game.next_position(movement, position)
# CHECK IF GAME IS FINISHED
finished = game.finished(position)
print(f'Game {position} finished: {finished}')
# GET SCORE
score = game.score(position)
print(f'Score:\n{score.pretty_print()}')
# GET THE SCORE OF MY PLAYER
my_score = score[0]
# my_score = score.get_score(0) # This line is equivalent to the previous one
print(f'My score: {my_score}')
Player
Now that we know how to play the game, let’s create a Player. A player is an object of a class that inherits from IPlayer.
Play
Every IPlayer must implement the method play(position: IPosition) -> IMovement,
where the player receives a position and must return a movement.
That is the main logic to implement in a player.
It is useful to use the rules methods in order to get the possible movements.
For this, every position has a method get_rules() that returns the rules object that generated the position.
starting_game
It is assured by the library that, for a given match, the Player will always play with the same player.
This means that, calling position.next_player() will always return the same value for the same player.
In order to create an object that is able to play multiple matches, the interface has a method starting_game(rules: IGameRules, player_index: int),
that is called by the library when the game starts.
This method is useful to set the player index in the player object, in order to know which player it is playing with.
AI Player Example
Let’s see how to create a player for Connect4 that always plays in the first column available:
from IArena.interfaces.IPlayer import IPlayer
from IArena.games.Connect4 import Connect4Movement, Connect4Position, Connect4Rules
class MyAIPlayer(IPlayer): # Create a class that inherits from IPlayer
def play(self, position: Connect4Position) -> Connect4Movement: # Implement the play method
rules = position.get_rules() # Get the rules object from the position
possible_movements = rules.possible_movements(position) # Get the possible movements
return possible_movements[0] # Return the first movement available
# TEST MY PLAYER
my_player = MyAIPlayer()
rules = Connect4Rules() # Default game rules
position = game.first_position() # Default first position with 6x7 empty board
move = my_player.play(position)
print(f'Movement selected: {move}')
position = rules.next_position(move, position)
print(f'Next position: {position}')
Arena
An Arena is a kind of object that holds the game loop.
It is created by a game’s rules, and enough players to play to such game.
The Arena loops by asking the players by the next move given a position, and the players must return a movement.
This ends when the game is finished, returning a Score.
There are different types of arenas, depending on the class to use:
GenericGame: A generic arena that can be used with any game and player.BroadcastGame: An arena that broadcasts the game state to the players in each step.ClockGame: An arena that plays the game with a time limit for eachplaycall for the players.
Built-in Players
The library has some built-in players that can be used to test the games.
PlayablePlayer: A player that asks the user for the movement in each step.RandomPlayer: A player that plays randomly.ConsistentRandomPlayer: A random player with a seed that makes it play consistently.LastPlayer: A player that always plays the last movement available.etc.
Random vs Our player
Let’s create an arena to test our player playing against a random player.
In order to see the game step by step, we will use a BroadcastGame arena.
from IArena.arena.GenericGame import BroadcastGame
from IArena.players.dummy_players import ConsistentRandomPlayer
# CREATE PLAYERS
my_player = MyAIPlayer()
random_player = ConsistentRandomPlayer(seed=42)
# CREATE ARENA
arena = BroadcastGame(
rules=Connect4Rules(), # Default game rules
players=[my_player, random_player] # Our player and a random player
)
# PLAY
score = arena.play()
print(f'Score: {score}')
Play against our AI player
We can also play against our own player to see how it behaves.
We can use the generic PlayablePlayer, but we will better use a specific player made for Connect 4:
from IArena.arena.GenericGame import GenericGame
from IArena.games.Connect4 import Connect4PlayablePlayer
# CREATE PLAYERS
my_player = MyAIPlayer()
human_player = Connect4PlayablePlayer()
# CREATE ARENA
arena = GenericGame(
rules=Connect4Rules(), # Default game rules
players=[my_player, human_player] # Our player and a human player
)
# PLAY
score = arena.play()
print(f'Score: {score}')
Set specific positions
In order to test a specific position, we can set the position in the rules, by setting an initial position. This is useful to test our AI players in specific situations.
Short str board representation
The Connect 4 game has a short string representation of the board to make it easier to create positions. This representation is a string as:
First value
0or1for the next player.Then the number of rows
Finally, a stack of values
0or1.Every value separated by
|.
For example, the default empty board is: 0|6||||||||.
The Connect4Position class has 2 methods to convert from a matrix to a short str and vice versa:
convert_short_str_to_matrix_str(short_str: str) -> str: Converts a short str to a matrix str.convert_short_str_to_matrix(short_str: str) -> List[List[int]]: Converts a short str to a matrix.
And a position can be created from a short str by using Connect4Position.from_str(rules: Connect4Rules, short_str: str) -> Connect4Position.
Let’s see how to use this functions to check what position a short str represents, and how to create a position from it:
from IArena.games.Connect4 import Connect4Position
# CREATE POSITION FROM SHORT STR
short_str = '0|6|111|111|111||0111|0111|0111|'
position = Connect4Position.from_str(None, short_str)
print(f'Position: {position}')
# CONVERT POSITION TO SHORT STR
s = str(position.position)
print(f'Short str: {s}')
Tournament
Finally, there is a special arena that is able to play several games in a row, in order to generate a better approach of how good a player is.
This is the TournamentGame class.
Let’s compare our player with 2 other players: a random one and a last player.
from IArena.arena.TournamentGame import TournamentGame
from IArena.players.dummy_players import ConsistentRandomPlayer, LastPlayer
# CREATE PLAYERS
my_player = MyAIPlayer(name="My Player")
random_player = ConsistentRandomPlayer(seed=42, name="Random Player")
last_player = LastPlayer("Last Player")
# CREATE ARENA
arena = TournamentGame(
rules=Connect4Rules(), # Default game rules
players=[my_player, random_player, last_player], # Players
matches=100 # Number of games to play
)
# PLAY
scores = arena.play()
print(f'Scores: {scores}')