Four In A Row

Last updated 26 days ago

Four in a Row is the classic two player game where you take turns to place a counter in an upright grid and try and beat your opponent to place 4 counters in a row.

Objective

At the start of the game you are assigned a disc colour either yellow or red. The game is played with a seven-column and six-row grid, which is arranged upright. The number of columns may be changed from six to ten, creating a greater variety of the game, but the number of rows is usually seven.

The starting player is randomly chosen and can place a disc in any row. Each player then alternately takes a turn placing a disc in any column that is not already full. The disc falls down straight and will only be stopped by another piece. The aim is to be the first of the two players to connect four discs of the same colour vertically, horizontally or diagonally, an example is shown below:

An example Four In A Row board

Connecting five or six instead of four pieces in a row is a different version of this game and is played with a bigger grid. If each cell of the grid is filled and no player has already connected four discs, the game ends in a draw, so no player wins.

Strategy

Four in a Row is a strongly solved perfect information strategy game by that we mean the player who has the first move has a winning strategy whatever his opponent plays.

To play this perfect game the code required is quite complex but below are some basic strategies.

Check for a win

Produce a sub-routine to check if there is a winning solution on any given board. You can then use this to simulate what will happen if you play any valid move.

Win if you can

Simply check if you can play a counter to win the game.

Block if you can

Next check to see if you need to play a move to block your opponent from winning.

Eliminate Moves

Eliminate moves that if you play them your opponent can play a winning move i.e. you should never play under an opponent winning positions. So in the example below yellow should not play a counter in position 'x':

Try to create Magic 7

Try and get into a position where you can force a win no matter what you opponents does. This can be achieved by creating a sequence called the 'magic 7' as shown below:

In this position red can force a win as they have positions 'x' and 'y' where both result in a win for red.

Programmer's Reference

gamestate

An example of the gamestate JSON

{
'IsMover': True,
'ResponseDeadline': 1543862692119,
'Connections': 4,
'GameStatus': 'RUNNING',
'Board': [
[-1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1],
[1, -1, 1, -1, -1, 0, 1],
[0, 0, 1, -1, 1, 0, 0]
],
'GameId': 2398056,
'OpponentId': 'housebot-practise'
}

A description of each field in the JSON

Key

Description

Connections

An integer that shows how many counters you need to connect to win.

IsMover

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

ResponseDeadline

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

Board

A 2-dimensional array of integers representing the state of the board also see 'Understanding The Board' below.

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 board

There can be various shapes of a board, depending on the game style that is selected. An example of a rectangular board in the gamestate is:

[
[-1, 0, 1, 1, -1, -1, -1],
[-1, 0, 0, 1, -1, -1, -1],
[-1, 0, 0, 0, 1, -1, -1],
[-1, 1, 1, 0, 1, -1, -1],
[-1, 0, 1, 1, 1, -1, -1],
[ 0, 1, 1, 0, 0, 0, 0]
]

Would visually be displayed as follows:

Boards with a non-rectangular shape, such as the octagonal board are represented as follows in the gamestate:

[[-2, -2, -1, -1, -1, -1, -2, -2],
[-2, -1, -1, -1, -1, -1, -1, -2],
[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1],
[-1, -1, -1, -1, -1, -1, -1, -1],
[-2, -1, -1, -1, -1, -1, -1, -2],
[-2, -2, -1, -1, -1, -1, -2, -2]]

Which would be visually represented as:

The boards integers in the Gamestate can contain the following values:

  • -2 - No hole is present at this space and it is not part of the board. A counter cannot be put here.

  • -1 - A hole that hasn't been used and that contains nothing.

  • 1 - A hole containing one of your counters.

  • 0 - A hole containing one of your opponents counters.

Making a valid move

To submit a move you need to return in calculateMove, an integer of the column you want to place your counter in. This starts with column 0 and the highest column is len(board[0])-1.

You cannot submit a move for a column that is already full. For example:

return {"Column": 4}

Would place a counter in the 5th column from the left. The game goes on until the board is full or a player connects the required number of counters (gamestate['Connections'] which is dependent on the gameStyle).