<jupyter_start><jupyter_text>Your First Quantum Game If you've already read some of this textbook, you should know at least a few quantum gates. If you are wondering what to do with that knowledge, perhaps making a game can be the answer. Making simple games can be a fun way to try out new programming knowledge.If we are going to make a game, we'll need a game engine. Here we'll introduce a simple game engine that can run here in the Jupyter notebooks of this textbook. With this we'll make make a very simple game based on single qubit gates.First, we import the game engine.<jupyter_code>import qisge<jupyter_output><empty_output><jupyter_text>Step 1: A strangely mobile treeIn this folder there are some images that can be used for games. Such as this one of a tree.To use these images in our game we first tell the game engine about them by defining an `ImageList` as follows.<jupyter_code>images = qisge.ImageList([
    'terrain-water.png',
    'terrain-red-flower.png',
    'terrain-grass.png',
    'terrain-path.png',
    'terrain-grass.png',
    'terrain-purple-flower.png',
    'terrain-tree.png',
    'qubit.png'
    ])<jupyter_output><empty_output><jupyter_text>Now we are able to refer to these images by index according to where they are in this list. This starts with the index 0 for 'terrain-water.png', then index 1 for 'terrain-red-flower.png' and so on. Our tree is at the position with index 6.You might have noticed that the grass image is listed twice here. That's just because it will be easier for us to access it using both index 2 and index 4. Other, less lazy methods are also possible!To display an image of a tree we set up a sprite object using the index for the image we want.<jupyter_code>tree = qisge.Sprite(6)<jupyter_output><empty_output><jupyter_text>Before we can display the image by running the game, there is one last ingredient. We need to define a `next_frame` function which defines how the things on screen change when each new frame is drawn.Here's an example where the position of the sprite (defined by the attributes `x` and `y`) is changed based on the user input.<jupyter_code>def next_frame(userinput):
    
    if 0 in userinput['key_presses']:
        tree.y += 0.5
    if 1 in userinput['key_presses']:
        tree.x += 0.5
    if 2 in userinput['key_presses']:
        tree.y -= 0.5
    if 3 in userinput['key_presses']:
        tree.x -= 0.5<jupyter_output><empty_output><jupyter_text>Here `userinput['key_presses']` is something that the game provides every time the `next_frame` function is called. It contains a list of the buttons that the user is pressing during that frame, referred to by numbers as follows.* 0: up;* 1: right;* 2: down;* 3: left;* 4: space;* 5: A;* 6: S;* 7: D.Now we have enough to define a game, we can run it. You should see the tree at the bottom left, and be able to move it around with the arrow keys.<jupyter_code>@qisge._app.run_me_every(1/qisge.FPS)
def game_loop(ct, dt):
    next_frame(qisge.update())
    
qisge._app.run()<jupyter_output><empty_output><jupyter_text>The size of the tree here is the default size that sprites are rendered in this game engine, which we refer to as a cell. The screen is 28 cells wide and 16 cells high. Step 2: Some flowers in a qubitIn what follows, we'll only be using the 10x10 section in the middle. So for the rest we'll need a background image. We'll use 'qubit.png' (index 7 in our image list) and scale it up to 28 cells wide.<jupyter_code>background = qisge.Sprite(7,size=28)<jupyter_output><empty_output><jupyter_text>Now we will create a 100 sprites, one for each cell in the central 10x10 region. These are all initialized with the red and yellow flowers image (index 1), and with different positions.<jupyter_code>sprite = {} # sprites for each tile
# loop over a 10x10 square of tiles in the center (remembering screen is 28x16)
for dx in range(9,19):
    for dy in range(3,13):
        sprite[dx,dy] = qisge.Sprite(1,x=dx,y=dy)<jupyter_output><empty_output><jupyter_text>We are going to generate a park that has more than just one type of flower. We are going to use all six terrain images available to us.<jupyter_code>terrain_types = 6<jupyter_output><empty_output><jupyter_text>To determine what type of terrain belongs at each point we define a function which returns an image index for each position `(x,y)`.As an initial example, here we just use the sum of `x` and `y` to determine the image, and keep cycling through the 6 possibilities.<jupyter_code>def get_image_id(x,y):
    
    return (x+y)%terrain_types<jupyter_output><empty_output><jupyter_text>Using this we can now change the images based on their position.<jupyter_code>for dx in range(9,19):
    for dy in range(3,13):
        sprite[dx,dy].image_id = get_image_id(dx,dy)<jupyter_output><empty_output><jupyter_text>Step 3: A walk in the park We are now going to make it so we can move through the park. For that we won't change the position of the sprites on screen. Instead we will change which position in the park they represent. For this we need to define a position for the walker.<jupyter_code>pos_x = 0
pos_y = 0<jupyter_output><empty_output><jupyter_text>Now we will make it so the sprite at `(dx,dy)` displays the terrain at position `(pos_x+dx,pos_y+dy)`. We'll also change the `next_frame` function so that the arrow keys change the position of the walker. Then, when the walker position changes, we redraw the sprites.<jupyter_code>def next_frame(input):
        
    global pos_x,pos_y
        
    pressed = False
    if 0 in input['key_presses']:
        pos_y += 1
        pressed = True
    if 1 in input['key_presses']:
        pos_x += 1
        pressed = True
    if 2 in input['key_presses']:
        pos_y -= 1
        pressed = True
    if 3 in input['key_presses']:
        pos_x -= 1
        pressed = True
    if 5 in input['key_presses']:
        pressed = True
                
    if pressed:
        for dx in range(9,19):
            for dy in range(3,13):
                sprite[dx,dy].image_id = get_image_id(pos_x+dx,pos_y+dy)<jupyter_output><empty_output><jupyter_text>Step 3: Making a park from a qubitNow let's make a more interesting landscape to walk around by changing the `get_image_id` function. And since this is a textbook on quantum computing, let's use a qubit! By experimenting with different single qubit quantum circuits in the `get_image_id` function, we can build up some experience of how qubits work and how they can be put to use.First we need to import some tools from qiskit.<jupyter_code>from qiskit import QuantumCircuit
from qiskit.quantum_info import Statevector

import numpy as np<jupyter_output><empty_output><jupyter_text>Here's a single qubit circuit with just a simple rotation around the y axis.<jupyter_code>theta = np.pi/2

qc = QuantumCircuit(1)
qc.ry(theta,0)<jupyter_output><empty_output><jupyter_text>We can use Qiskit to determine the statevector that results from this rotation, and determine the probability that we'd get the outcome `0` from a measurement.<jupyter_code>state = Statevector.from_instruction(qc)
probs = state.probabilities()
probs[1]<jupyter_output><empty_output><jupyter_text>As you can see, for this rotation and this angle we get a probability of 0.5. If you try other angles you'll find what you might expect from the Bloch sphere: the probability is 1 for an angle of 0, then decreases to 0 as the angle goes to $\pi$, and then cyles back to 1 for the angle of $2\pi$ and so on.With this we can use `ry` for another way of implementing our park. We can use the value of $x+y$ to parameterize the angle of the rotation. Just like before, this will lead to a park cycling through different types of terrain.<jupyter_code>def get_image_id(x,y):
    
    theta = np.pi*(x+y)/(terrain_types)
    
    qc = QuantumCircuit(1)
    qc.ry(theta,0)
    
    state = Statevector.from_instruction(qc)
    probs = state.probabilities_dict()
    
    return int(round(probs['0']*(terrain_types-1)))<jupyter_output><empty_output><jupyter_text>Now scroll back up to the game screen to see this new, qubit-powered park. Note that you'll need to start trying to move around (or press 'A'), before it will redraw the sprites.As you might notice, movement is a bit more sluggish now. This is a consequence of trying to do 100 single qubit simulations every frame! Certainly not a recommended way of trying to use quantum computers in games, though it is fine for this simple tutorial. Step 4: Experiment! Now you have everything you need to start experimenting. You can use different single qubit rotations and different rotation angles with different dependencies on the position to generate different kinds of terrain.Here's an example for you to look at. In order for each run to create something different, we'll use some randomly generated parameters.<jupyter_code>import random
s = [0.5*random.random() for _ in range(6)]<jupyter_output><empty_output><jupyter_text>Using these, as well as the coordinates, we can define different angles for x, y and z rotations on our qubit and generate a park full of paths, lakes and woodland.<jupyter_code>def get_image_id(x,y):

    # set up a single qubit circuit
    qc = QuantumCircuit(1)

    # define some (x,y) dependent angles
    tx = (s[0]*x + s[1]*y)*np.pi/7
    ty = (s[2]*x - s[3]*y)*np.pi/7
    tz = (s[4]*(x+y) + s[5]*(x-y))*np.pi/7

    # add rotations by these angles to the circuit
    qc.rx(tx,0)
    qc.rz(tz,0)
    qc.ry(ty,0)

    # simulate it to get the probability of a 0 outcome
    state = Statevector.from_instruction(qc)
    probs = state.probabilities_dict()
        
    # scale and round the result to get an image id
    image_id = int(round(probs['0']*(terrain_types-1)))

    return image_id<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.07845540913269997, 'fraction_numerical': 0.013484523444682808, 'mean_word_length': 4.101615424700364, 'pattern_counts': {'":': 0, '<': 73, '<?xml version=': 0, '>': 73, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '179123', 'license': 'Apache-2.0', 'n_tokens_mistral': 3126, 'n_tokens_neox': 2933, 'n_words': 1295, 'path': 'first-quantum-game.ipynb', 'repo_name': 'quantumjim/qisge-jupylet', 'size': '9789'}