Noughts & Crosses

Noughts and crosses is an excellent beginner's introduction to developing game playing bots

Noughts and crosses strategy is very simple but stills allows users to gain an understanding of the basic approach to game playing. Also the game is solved so there is no excuse for not playing perfectly!

Objective

Play against one of our Housebots, or, play against your own bots or your friends bots. The aim of the game is to get three of your pieces, noughts "O" or crosses "X" in a line. Player's take turns adding one of their pieces to the grid, you can only add a piece to a blank position on the grid. The winner is the first to get three of their pieces in a line, if there are multiple deals in the game style being played then the winner is the player who has won the most games from the total number of deals.

Adding intelligence to your bot

Editing the Code

Once you are ready to start improving your bot you should draw your attention to the calculate_move() function, this is where all the action happens. This function is called every time the server wants you to make a move. It receives a dictionary of information containing the state of the game, namely the gamestate. For greater detail on the contents of the gamestate check out the gamestate explanation later in this guide.

From this information it is your task to determine where you want to move next.

You can specify your move by returning a valid Python dictionary, as detailed in the Programmer's Reference later on this page) from the calculate_move() function.

For example:

def calculate_move(gamestate):
move = {"Position": 4}
return move

Case Study

Let’s say we would like to implement the following strategy:

  1. Prioritise moving into the centre first.

  2. If we can't, try moving into one of the corners.

  3. If we still can't, move into one of the edges.

The code looks like the following:

from random import choice
def calculate_move(gamestate):
if(gamestate["Board"][4] == " "): # If the middle is available
move = {"Position": 4} # Move in the middle
elif(gamestate["Board"][0] == " " or gamestate["Board"][2] == " " or gamestate["Board"][6] == " "
or gamestate["Board"][8] == " "): # If one of the corners is available
options = [0,2,6,8]
move = {"Position": choice(options)}
while(gamestate["Board"][move] != " "): # Keep randomly choosing a corner until an empty one is chosen
move = {"Position": choice(options)} # Move to that empty corner
else: # Otherwise guess edges
options = [1,3,5,7]
move = {"Position": choice(options)}
while(gamestate["Board"][move] != " "): # Keep randomly choosing an edge until an empty one is chosen
move = {"Position": choice(options)} # Move to that empty edge
return move

Strategy

There isn't much strategy to noughts and crosses, but here are some suggestions to improve your bots performance.

Sort out your priorities

Randomly choosing your moves probably won't get you very far. Try checking for certain patterns on the board and react accordingly, similar to the case study above. A good starting set of priorities is to check whether you can win, then check whether you can stop yourself from losing, then check whether you can move to the centre, followed by corners, and finally edges.

A solved game

Noughts and crosses is a solved game and there is a perfect strategy, you can hard code this strategy if you know what it is. If you are feeling a little more adventurous however, you could try creating a minmax algorithm that will play the game perfectly every time.

Programmer's Reference

gamestate

An example of the gamestate JSON

{
'Board': [' ', ' ', 'O', 'O', 'X', ' ', ' ', ' ', ' '],
'IsMover': True,
'Role': 'X',
'ResponseDeadline': 1543868919361,
'MyScore': 0,
'OppScore': 0,
'GameStatus': 'RUNNING',
'GameId': 2398094,
'OpponentId': 'housebot-practise'
}

A description of each field in the gamestate

Key

Description

Board

A list of strings representing the state of the board.

IsMover

A Boolean value indicating whether it is your turn to move. It will always be true when in the calculate_move() function

Role

A string of value either "O", indicating you are playing as noughts, or "X", indicating you are playing as crosses

ResponseDeadline

The epoch time, in milliseconds, that a successful move has to be sent and received by to prevent you from timing out.

DealNumber

Which deal you are up to in the match, this key only appears when the value of DealsTotal is greater than 1

DealsTotal

The total number of times you are going to play a whole game of battleships in this match, this key only appears when the value is greater than 1.

MyScore

Your current score in this game, increases by one for every deal you win. The player with the largest score at the end wins

OppScore

Your opponent's current score in this game, increases by one for every deal they win. The player with the largest score at the end wins

GameStatus

A string that will have value "RUNNING" if the game is in progress or a reason the game has ended otherwise.

GameId

An integer representing the unique game id for the current game

OpponentId

A string containing the name of your opponent

Understanding The Boards

The strings in your board can contain the following values:

  • " " - A position that is blank.

  • "O" - A position that contains a nought.

  • "X" - A position that contains a cross.

The indices of the board represent the following positions on the noughts and crosses grid:

Making A Valid Move

The value you have to return from the calculate_move() function is a key value pair, 'Position', which has value equal to the index of the position on the board you wish to make your move:

Here is an example of a valid noughts and crosses move:

{
"Position": 4
}

And its result on a blank board when playing with role "X":