Quantum Tic-Tac-Toe

The latest version of this notebook is available on https://github.com/qiskit/qiskit-tutorial.


Contributors

Maor Ben-Shahar


An example run of quantum Tic-Tac-Toe is provided below, with explanations of the game workings following after. Despite the ability to superimpose moves, a winning strategy still exists for both players (meaning the game will be a draw if both implement it). See if you can work it out.


In [1]:
#Import the game!
import sys
sys.path.append('game_engines')
from q_tic_tac_toe import Board

In [2]:
#inputs are (X,Y,print_info).
#X,Y are the dimensions of the board. print_info boolean controls if to print instructions at game launch.
#Since it is our first time playing, lets set it True and see the instructions!
B = Board(3,3,True)

In [3]:
B.run()


Welcome to Quantum tic tac toe!
At each turn choose if to make one or two moves.
Playing one move at a time is a classic tic tac toe game.
At each turn the game state is printed.
This constitutes a 3x3 grid (standard game!).
You will see empty cells if no move was made on that part of the board.
Moves made by X are marked with Xi, 'i' some number.
e.g. X3 is the third move, played by X. When a move is made in a super position,
You will see its label, say X3, appear in several places.
This means your move is in a superposition of two classical moves!
A superposition of classical moves does not guarantee that a spot is occupied,
so other players can attempt to occupy it too.
Then the new move will be anti-correlated with the move already in that spot.
And so the game branches out into many simultaneous states.
The outcome is then computed by simulation...
so don't make too many quantum moves or it will take long to compute!
Enter 'q' at any time to quit
Enter 'end' to end the game, and compute the winner(s).
Good luck!
PLAYER X :
Play in 1 or 2 cells?q

When playing the game, the two players are asked in turn if to make a classical (1 cell) or quantum move (1 or 2 cells at most, for now). When making any move there are several scenarios that can happen, they are explained below. The terminology used:

  • Each turn a "move" is made
  • Each move consists of one or two "cells", the location(s) where the move is made. It is a superposition of classical moves.

Quantum moves are restricted to two cells only due to them requiring an increasing number of qubits, which is slow to simulate.

One move on an empty cell

This is the simplest move, it is a "classical" move. The game registers this move as a set of coordinates, and the player who made the move. No qubits are used here. It is registered as such:
Play in one or two cells?1 x index: 0 y index: 0
First the player is asked how many moves (we chose 1, classical). Then it asks for the index of the move. And the board registers it as
[['O1','',''], ['','',''], ['','','']]
This move is always present at the end of the game.

Two-cell moves in empty cells

This is a quantum move, the game stores a move that is in a superposition of being played at two cells. Ordered coordinates for the two cells to be occupied need to be provided. A row in the board with a superposition move would look like so
[X1,X1,'']
Two qubits were used in order to register this move. They are in a state $|10>+|01>$, if the first qubit is measured to be 1 then the board becomes [X1,'',''] and vice versa. Why can we not use just one qubit to record this? We can, and the qubit would have to be put into a state $|0>+|1>$ but I did not implement this yet since this is method will be consistant with later types of quantum moves.

Let us see this in action:


In [4]:
B = Board(3,3)
B.run()


PLAYER X :
Play in 1 or 2 cells?2
x1 index:0
y1 index:0
x2 index:1
y2 index:0
[['X1' 'X1' '']
 ['' '' '']
 ['' '' '']]
PLAYER O :
Play in 1 or 2 cells?end
simulation:  COMPLETED
{'010': 53, '100': 47}
X wins: 0
O wins: 0
Shots: 53
[['x' '' '']
 ['' '' '']
 ['' '' '']]
X wins: 0
O wins: 0
Shots: 47
[['' 'x' '']
 ['' '' '']
 ['' '' '']]

The game outcome is almost 50% in each cell, as we would expect. There is a redundant bit at the end of the bit code (to be removed soon!). And note that the bit strings are the inversed order to what we write here, this is because the quantum register in qiskit has positions $|q_n,...,q_0>$.

One-cell move plyed in a maybe-occupied cell

It is possible that after the game is in state [X1,X1,''] one would definitely want to make a move at position (0,0). This could be when the game is completely full perhaps, since it is not a very good strategy. Such a move can be erased from the game history! Let us see how it is recorded. The first row of the board is now [X1 O2,X1,'']
and the state of the game qubits is
$$ |100>+|011> $$
with the first qubit recording sucess of the first move at cell (0,0), the second qubit is the success of the first move in cell (0,1) and the third qubit is the move by player O, which is anti correlated with the move by X at cell (0,0).
Notice that this move can be completely erased!


In [5]:
B = Board(3,3)
B.add_move([[0,0],[0,1]],0) #Directly adding moves, ([indx1,indx2],player) 0=X, 1=O.
B.add_move([[0,0]],1)
B.compute_winner()


[['X1' 'X1' '']
 ['' '' '']
 ['' '' '']]
[['X1 O2' 'X1' '']
 ['' '' '']
 ['' '' '']]
simulation:  COMPLETED
{'0010': 48, '1100': 52}
X wins: 0
O wins: 0
Shots: 48
[['x' '' '']
 ['' '' '']
 ['' '' '']]
X wins: 0
O wins: 0
Shots: 52
[['o' 'x' '']
 ['' '' '']
 ['' '' '']]

Once again note that the move could be erased completely, and in fact this happens 50% of the time. Notice how the bit string output from QISKIT is translated into a board state.

Two-cell moves in maybe-occupied cells

Instead of the above, player O might like to choose a better strategy. Perhaps O is interested in a quantum move on cells (0,0) and (0,2). In such a case the game records the two moves in the order they are entered.

  • In order (0,0) then (0,2): The state of the game is first made into $ |100>+|011> $ as above, with the third qubit recording the sucess of player O getting position (0,0). Then the (0,2) position is registered, anti-correlated with suceeding in position (0,0): $|1001>+|0110>$. Now, unlike before, player O suceeds in registering a move regardless of the outcome.
  • In order (0,2) then (0,0): Now playing at (0,2) is not dependent on anything, and so the game state is $(|10>+|01>)\otimes (|1>+|0>) = |101>+|100>+|011>+|010>$. And when the move in position (0,0) is added too, it is anti correlated with BOTH the move in (0,2) AND the pre-existing move in (0,0). So qubit state becomes $|1010>+|1000>+|0110>+|0101>$. Notice how now the move could be erased, so order does matter!

In [6]:
B = Board(3,3)
#Instead of running the game, for the purpose of demonstrating, we can just create the appropriate state manually.
#Directly adding moves, ([[y1,x1],[x2,y2]],player) with player=0->X, 1->O.
B.add_move([[0,0],[0,1]],0)
B.add_move([[0,0],[0,2]],1)
B.compute_winner()


[['X1' 'X1' '']
 ['' '' '']
 ['' '' '']]
[['X1 O2' 'X1' 'O2']
 ['' '' '']
 ['' '' '']]
simulation:  COMPLETED
{'10010': 42, '01100': 58}
X wins: 0
O wins: 0
Shots: 42
[['x' '' 'o']
 ['' '' '']
 ['' '' '']]
X wins: 0
O wins: 0
Shots: 58
[['o' 'x' '']
 ['' '' '']
 ['' '' '']]

Exercise: what if player O chose coordinates (x=0,y=0) and (x=1,y=0) instead?

Exercise: At this stage, can player X ensure that no matter what O plays, both (x=0,y=0) and (x=1,y=0) are occupied by X?

And that is all there is to quantum tic tic toe! Remember, to run the game, import the board:

from q_tic_tac_toe import Board

Create the board you want to play on:

B = Board(3,3,True)

and run!

B.run()