# Intermediate

Quantum games work in a similar way like games that involve an element of probability. Consider a game where different players have to beat a house or referee. The players are not allowed to communicate with each other during the duration of the game and have to prepare a strategy in advance. To win, the outputs or results given by the players have to satisfy a certain condition or rule of the game. In this tutorial, we will build and play a quantum game by exploring different classical and quantum game strategies.
import numpy as np
import random as rand
from qiskit import BasicAer
from qiskit import QuantumCircuit, ClassicalRegister, QuantumRegister, execute


## Introduction

Similar to classical computation, quantum computation can be performed using the circuit model of computation, applying different logic gates . Qubits are the analogues of bits and quantum gates   operate on qubits, whilst manipulating quantum mechanical properties . What makes a quantum gate special is its reversibility. In order to have reversible quantum gates, we need to implement it using unitary operations; operations which preserve the sum of the probabilities of seeing each measurable value of the qubits. The sum of the probabilities of each outcome must add up to 1.

### Qubits

In quantum computation the objects of the computation are quantum objects called qubits. Qubits are represented as |0⟩ and |1⟩. The qubit state |Ψ⟩ can be expressed as a superposition of |0⟩ and |1⟩, or |Ψ⟩=a|0⟩+b|1⟩ where a and b are complex numbers, proportional to measuring the probability of its corresponding outcome.

More specifically, the probability of measuring the qubit state as |0⟩ is a^2 and |1⟩ is given by |b|^2

respectively. Another interesting property that we must know of  is entanglement, which Einstein described as "spooky action at a distance". If two qubits are entangled with each other, then if we know the value taken by one of them, then without even observing the second qubit we know it's state/value. The correlations between the values of entangled qubits is unique to quantum probabilities and something we cannot perform on a classical computer. In fact, it is not possible to separate an entangled system in to its independent parts. Whenever we want to observe the value of a quantum system, we must make a measurement of their state. This, causes the system to take collapse to one value. It is not possible to determine with certainty which value the system will take, but we can think about the various probabilities for the allowed values.

### Quantum Gates

The most common quantum gates are:

X gate: flips the state of a qubit from |0⟩ to |1⟩ and vice-versa

Z gate: flips the phase of a qubit (changes relative sign of a and b)

Y gate : flips the state of a qubit and its phase.

H gate: creates superposition or a linear combination of |0⟩ and |1⟩

Rx, Ry and Rz gates: Rotates qubit, by changing the coefficients a and b in a way that depends on the angle of rotation inputted into the gate as a parameter

### Two-Qubit Gates

The most important two qubit gate is the controlled-not gate:

CX: flips the state of a qubit on the state of another qubit based on a condition. It consists of a control qubit and a target qubit. If the control qubit is in the state |0⟩, then no operation is performed on the target qubit. However, if the control qubit is in the state |1⟩, then a X gate is performed on the target qubit. Creates an entangled bell pair in combination with the H gate.

## Programming a Quantum Game

Let's make use of quantum properties we just learnt to make our game!

Our game involves two players (Alice and Bob) who play against the house. Alice and Bob are allowed to discuss and pick a strategy before the game starts, but not allowed to communicate after the beginning of the game. A referee generates two random bits x and y which are given as inputs to Alice and Bob respectively. Alice and Bob do not know each other's inputs. They can manipulate their own input however they wish, according to their strategy, and then choose a bit to output. Alice will output a bit a and Bob a bit b

Alice and Bob win against the house if the they satisfy the following condition:

### Classical Strategy

In the classical world, Alice can only devise a strategy based on her value of the input bit x and Bob according to its input value y. An easy but bad strategy is that they output their input or output the opposite of their input. They can also always input 1 or 0. Can you think of a strategy that will be winning 75% of the time for Alice and Bob?

### Quantum Strategy

Alice and Bob play the game by sharing an entangled pair of qubit. Two qubits form an entangled pair and then Alice and Bob are given one of the qubit from the pair.

According to the input they receive from the house they will rotate their entangled qubit using the Rx, Ry and Rz gates we mentioned and then measure it. A bad strategy would be to rotate the qubit by a random amount or the same angle. However, it is possible to find the angle of rotation that maximizes the probability of winning the game, which is around 85%. Read this article to find out more!

There are 3 possible choices the user can pick

1. Don't rotate the qubit

2. Rotate a random amount

3. Rotate to maximize winning probability

Let's start coding!

## Step One : Code the algorithm that executes choice 1, 2 or 3

def qAlice_output(strategy, inp):
if(strategy == 1):
return 0                           #dont rotate the qubit

elif(strategy == 2):
return rand.uniform(0,2*np.pi)     #rotate the qubit randomly

elif(strategy == 3):                   # the strategy that maximizes chances at winning
if(inp == 0):
return 0
elif(inp == 1):
return np.pi/2
else
return 0

def qBob_output(strategy, inp):
if(strategy == 1):                    # dont rotate qubit
return 0

elif(strategy == 2):
return rand.uniform(0,2*np.pi)    # rotate qubit randomly

elif(strategy == 3):                  #maximize winning chance
if(inp == 0):
return np.pi/4
elif(inp == 1):
return -np.pi/4
else
return 0



## Step Two : Input the choice from the user

# Alice's strategy
Alice_strategy = int(input('select the choice for Alice, input 1,2 or 3 '))

# Bob's strategy
Bob_strategy = int(input('select the choice for Bob, input 1,2 or 3 '))


## Step Three : Initialize the circuit and counters


shots = 1 # the number of times the circuit is run, to keep a track of the measurement outcomes
backend = BasicAer.get_backend('qasm_simulator') #initialize the quantum device to run the circuit

#fixes the numbers of games to be played
N=100

# initializes counters to keep track of the numbers of games won and played by Alice and Bob
count_win = 0 # counts games won
count_tot = 0 # counts games played


## Step Four : Play N games and execute the quantum game

#play N games
for i in range(N):

# creates a quantum register to specify the number of qubits to be used
q = QuantumRegister(2)
# creates a classical register which stores the results of the measurement of the qubits
c = ClassicalRegister(2)

# creates quantum circuit
game = QuantumCircuit(q, c)

# Prepare the entangled Bell pair by applying a hadamard and cx gate
# Alice will have qubit 0 and Bob will have qubit 1
game.h(q[0]) # Hadamard gate on qubit 0
game.cx(q[0],q[1]) # CNOT gate on qubit 1 controlled by qubit 0

# generates two random inputs x and y to be given to Alice and Bob
random_num1 = rand.random()
random_num2 = rand.random()

if(random_num1 >= 1/2): # Convert the decimal to 0 if it is less than half
x = 0
else: x = 1

if(random_num2 >= 1/2):
y = 0
else: y = 1

# fix different rotation angles for their qubit according to the input x,y
theta = qAlice_output(Alice_strategy, x) # fixes Alice's rotation for her qubit
phi = qBob_output(Bob_strategy, y) # fixes Bob's rotation for his qubit

# Gates applied to rotate Alice and Bob qubit
game.ry(theta,q[0]) #rotates Alice's qubit by an angle theta
game.ry(phi,q[1]) ##rotates Bob's qubit by an angle phi



## Step Five : Extract the output

  game.measure(q[0], c[0]) # measure Alice's qubit
game.measure(q[1], c[1]) # measure Bob's qubit

# executes circuit and store the output of the measurements
result = execute(game, backend=backend, shots=shots).result()

data = result.get_counts()
for outcomes in data.keys():
output = outcomes


## Step Six : Print the probability of winning the game

		if(output == '00'):
a = 0
b = 0
if(output == '01'):   #qiskit reads from right to left
a = 1
b = 0
if(output == '10'):
a = 0
b = 1
if(output == '11'):
a = 1
b = 1

# if winning condition is met, increase count_win by 1
if(x*y == a^b):
count_win += 1

count_tot += 1
qProb_win = count_win/count_tot

print('Alice and Bob won the game with probability: ', qProb_win*100, '%')


q_Prob_win is 85%, which is better than if we used a classical strategy to play the game!