diff --git a/community/games/README.md b/community/games/README.md index fa8aa2ec3..aa071422c 100644 --- a/community/games/README.md +++ b/community/games/README.md @@ -19,6 +19,7 @@ This is exactly what we'd like to replicate in this folder. Here you'll find bas * [Random Terrain Generation](random_terrain_generation.ipynb) - A simple example of using quantum computers for the kind of procedural generation often used in games. +* [Quantum Animations](quantum_animations.ipynb) - A simple example of making pixel art animations with quantum computers. ## Contributing diff --git a/community/games/game_engines/outputs/animation.png b/community/games/game_engines/outputs/animation.png new file mode 100644 index 000000000..45c4bd55c Binary files /dev/null and b/community/games/game_engines/outputs/animation.png differ diff --git a/community/games/game_engines/outputs/image1.png b/community/games/game_engines/outputs/image1.png new file mode 100644 index 000000000..efd43149e Binary files /dev/null and b/community/games/game_engines/outputs/image1.png differ diff --git a/community/games/game_engines/outputs/image2.png b/community/games/game_engines/outputs/image2.png new file mode 100644 index 000000000..e04fff2e5 Binary files /dev/null and b/community/games/game_engines/outputs/image2.png differ diff --git a/community/games/game_engines/outputs/new_animation.png b/community/games/game_engines/outputs/new_animation.png new file mode 100644 index 000000000..10e3d2c4b Binary files /dev/null and b/community/games/game_engines/outputs/new_animation.png differ diff --git a/community/games/quantum_animations.ipynb b/community/games/quantum_animations.ipynb new file mode 100644 index 000000000..be5738471 --- /dev/null +++ b/community/games/quantum_animations.ipynb @@ -0,0 +1,501 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\"Note: Trusted Notebook (and the image needs to be where this notebook is expecting it)\" width=\"500 px\" align=\"left\">" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# _*Quantum Animations*_\n", + "\n", + "The latest version of this notebook is available on https://github.com/qiskit/qiskit-tutorial.\n", + "\n", + "***\n", + "### Contributors\n", + "James R. Wootton, IBM Research\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the notebook on [random terrain generation](random_terrain_generation.ipynb), we used qubits to make black and white pixel images.\n", + "\n", + "Here we will use the same principle to make colour images. For this we use the fact that each colour can be expressed using the [RGB_color_model](https://en.wikipedia.org/wiki/RGB_color_model). This uses three values to expressed a colour, which tell us how much red, green and blue must be mixed together. These values are typically in the range from 0 to 255. For example, black is (0,0,0), white is (255,255,255) and red is (255,0,0). With this in mind, we can simply have three separate monochromatic process for these three colour channels, and then combine them at the end.\n", + "\n", + "Another new aspect we'll introduce here is the ability to encode an existing image. For example, consider the 'image' below." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "plumber = {(0, 0): (255, 255, 255), (0, 1): (255, 255, 255), (0, 2): (255, 255, 255), (0, 3): (255, 0, 0), (0, 4): (255, 0, 0), (0, 5): (255, 0, 0), (0, 6): (255, 255, 255), (0, 7): (92, 64, 51), (1, 0): (255, 255, 255), (1, 1): (255, 0, 0), (1, 2): (255, 255, 255), (1, 3): (0, 0, 255), (1, 4): (0, 0, 255), (1, 5): (0, 0, 255), (1, 6): (0, 0, 255), (1, 7): (92, 64, 51), (2, 0): (255, 0, 0), (2, 1): (255, 0, 0), (2, 2): (255, 192, 203), (2, 3): (255, 0, 0), (2, 4): (0, 0, 255), (2, 5): (0, 0, 255), (2, 6): (255, 255, 255), (2, 7): (255, 255, 255), (3, 0): (255, 0, 0), (3, 1): (255, 0, 0), (3, 2): (255, 192, 203), (3, 3): (255, 0, 0), (3, 4): (0, 0, 255), (3, 5): (0, 0, 255), (3, 6): (255, 255, 255), (3, 7): (255, 255, 255), (4, 0): (255, 255, 255), (4, 1): (255, 0, 0), (4, 2): (255, 255, 255), (4, 3): (0, 0, 255), (4, 4): (0, 0, 255), (4, 5): (0, 0, 255), (4, 6): (0, 0, 255), (4, 7): (92, 64, 51), (5, 0): (255, 255, 255), (5, 1): (255, 255, 255), (5, 2): (255, 255, 255), (5, 3): (255, 0, 0), (5, 4): (255, 0, 0), (5, 5): (255, 0, 0), (5, 6): (255, 255, 255), (5, 7): (92, 64, 51), (6, 0): (255, 255, 255), (6, 1): (255, 255, 255), (6, 2): (255, 255, 255), (6, 3): (255, 255, 255), (6, 4): (255, 255, 255), (6, 5): (210, 180, 140), (6, 6): (255, 255, 255), (6, 7): (255, 255, 255), (7, 0): (255, 255, 255), (7, 1): (255, 255, 255), (7, 2): (255, 255, 255), (7, 3): (107, 92, 72), (7, 4): (210, 180, 140), (7, 5): (107, 92, 72), (7, 6): (255, 255, 255), (7, 7): (255, 255, 255)}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This is an 8x8 pixel picture of a tanuki plumber, expressed as a Python dictionary. For each coordinate of a pixel, it gives the corresponding RGB values. This can then be turned into a proper image. In this notebook, we'll use the `PIL` package to do this." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADUklEQVR4nO3dsY1VMRBA0We0HbE5ENID2wGtUAAxCQ2QISHyzSjo0cEmXyPLuucUYI8sXTmz133f19HW2j3BY04//8O92z0A7CQA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQ9je9w+vv906bPx/8Db3IDkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKTN/w8w/T7939fZ9ad9eN49QZobgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBtDb/ef61rdof7WqPrTzv+fIb/f/j68nl0fTcAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQNrx/wPwttP/B/j369vo+m4A0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgLSn3QNwtun3+7///D26vhuANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIG3d9z27wRpd/hoef5zz2csNQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZC2vnx8v3uGh/z487p7hIe8fHrePcJDTj9/NwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRA2n8dmy0sa+CUzwAAAABJRU5ErkJggg==\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from PIL import Image\n", + "from IPython.display import display\n", + "\n", + "def save_image(image,filename='image.png',scale=None):\n", + "\n", + " img = Image.new('RGB',(8,8))\n", + "\n", + " for x in range(img.size[0]):\n", + " for y in range(img.size[1]):\n", + " img.load()[x,y] = image[x,y]\n", + "\n", + " if scale:\n", + " img = img.resize((256,256))\n", + "\n", + " img.save('game_engines/outputs/'+filename)\n", + "\n", + " \n", + "save_image(plumber,scale=[300,300],filename='image1.png')\n", + "display(Image.open('game_engines/outputs/image1.png'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now turn this image into a quantum state. More precisely, we'll turn him into three states, with one for each colour channel. We will assign a bit string to each coordinate in the image, and use the probabilities of those bit strings as the value for the colour channels of that coordinate.\n", + "\n", + "The above image has $8x8=2^6$ pixels, and so needs 6 qubits." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "n = 6" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we set the probabilities for each bit string, we need to account for the fact that we do not have the freedom to do this arbitrarily. As probabilities, they must all be in the range from 0 to 1, and they must all sum to 1. We can do this simply by renormalizing. The corresponding amplitudes of the quantum state are then the square roots of these values.\n", + "\n", + "First we must decide which bitstrings belong to which values. This can be done in exactly the same way as for the terrain generation notebook." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "L = int(2**(n/2))\n", + "\n", + "grid = {}\n", + "for y in range(L):\n", + " for x in range(L):\n", + " grid[(x,y)] = ''\n", + "\n", + "for (x,y) in grid:\n", + " for j in range(n):\n", + " if (j%2)==0:\n", + " xx = np.floor(x/2**(j/2))\n", + " grid[(x,y)] = str( int( ( xx + np.floor(xx/2) )%2 ) ) + grid[(x,y)]\n", + " else:\n", + " yy = np.floor(y/2**((j-1)/2))\n", + " grid[(x,y)] = str( int( ( yy + np.floor(yy/2) )%2 ) ) + grid[(x,y)]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following function then takes an image and a mapping of coordinates to bit strings and makes three quantum states, one for each colour channel. Each is expressed as a Python lists of amplitudes." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "def image2state(image,grid):\n", + " \n", + " N = len(grid)\n", + " state = [[0]*N,[0]*N,[0]*N] # different states for R, G and B\n", + "\n", + " for pos in image:\n", + " for j in range(3):\n", + " state[j][ int(grid[pos],2) ] = np.sqrt( image[pos][j] ) # amplitude is square root of colour value\n", + "\n", + " for j in range(3): \n", + " Z = sum(np.absolute(state[j])**2)\n", + " state[j] = [amp / np.sqrt(Z) for amp in state[j]] # amplitudes are normalized\n", + " \n", + " return state\n", + "\n", + "\n", + "state = image2state(plumber,grid)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can load the states into quantum circuits using the `initialize()` function of Qiskit, which initializes a circuit with a given quantum state. We can then manipulate the image in a quantum way. Here we'll use the statevector simulator to do this, which outputs the final quantum state." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from qiskit import *\n", + "\n", + "backend = Aer.get_backend('statevector_simulator')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final statevector is a set of amplitudes, but our encoding of RGB was done via the probabilities. For this reason, it would be useful to have the standard counts dictionary as we get from other simulators and real quantum devices. The following function performs the conversion." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "def ket2counts (ket):\n", + " \n", + " counts = {}\n", + " N = len(ket)\n", + " n = int( np.log(N)/np.log(2) ) # figure out the qubit number that this state describes\n", + " for j in range(N):\n", + " string = bin(j)[2:]\n", + " string = '0'*(n-len(string)) + string\n", + " counts[string] = np.absolute(ket[j])**2 # square amplitudes to get probabilities\n", + " \n", + " return counts" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's have a simple example where we load the image in and then extract the results." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "q = QuantumRegister(n)\n", + "\n", + "counts = []\n", + "for j in range(3): # j=0 for red, j=1 for green, j=2 for blue\n", + " qc = QuantumCircuit(q)\n", + " qc.initialize( state[j], q )\n", + " job = execute(qc, backend)\n", + " counts.append( ket2counts( job.result().get_statevector() ) )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The only remaining job is to turn the three dictionaries of counts back into an image. This is going to be a bit ambiguous, because we need to undo our earlier normalization step. To do this, we'll assume that, for every colour channel, there is at least one pixel with the value 255. This means that the maximum probaility for each colour channel will be given the value 255, and all other values are assigned proportionally. This is done by the following function." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADYElEQVR4nO3dsWkdQRRA0b8fVWSUC4eqwagDtyLnjpW4gZ8JhHJl6metDpQsj2W45xQw+xj2MtnMtu/7ZWnX69kTHLP6/i9u8b8HjhEAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANLuxr+w+v3906b3x/sD3/J3kiYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKTNvw8wfT/9+8fs+tMe7s+eIM0JQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZC2Dd/ef7leZr+wL97w8vsz/P7D76fH0fXX/nvgIAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIG359wH43urvA3ze/oyu7wQgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASLs7ewDW9nl7Hl3/77/X0fWdAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBpAiBNAKQJgDQBkCYA0gRAmgBIEwBp277vox+4Dic2PP641fdnm13+8n94fScAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQJoASBMAaQIgTQCkCYA0AZAmANIEQNr26+HH2TMc8vL2cfYIhzz9vD97hEPm93/2BQInAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBECaAEgTAGkCIE0ApAmANAGQJgDSBEDaF+faLh/C7eNFAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def counts2image(counts,grid):\n", + " \n", + " image = { pos:[0,0,0] for pos in grid}\n", + "\n", + " for j in range(3):\n", + "\n", + " rescale = 255/max(counts[j].values()) # rescale so that largest probability becomes value of 255\n", + "\n", + " for pos in image:\n", + " try:\n", + " image[pos][j] = int( rescale*counts[j][grid[pos]] )\n", + " except:\n", + " image[pos][j] = int( rescale*counts[j][grid[pos]] )\n", + "\n", + " for pos in image:\n", + " image[pos] = tuple(image[pos])\n", + "\n", + " return image\n", + "\n", + "\n", + "save_image( counts2image(counts,grid), scale=[300,300], filename='image2.png' )\n", + "display(Image.open('game_engines/outputs/image2.png'))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Our brave adventurer has been inside a quantum circuit and emerged intact!\n", + "\n", + "Now let's do some simple manipulation. We'll simply apply a rotation around the y axis over the course of a number of frames." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [], + "source": [ + "frame_num = 20" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "These will then be compiled into an animated PNG using the `apng` package. Note that you'll need to run this notebook yourself in order to see this." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "from apng import APNG\n", + "import os\n", + "\n", + "state = image2state(plumber,grid)\n", + "\n", + "filenames = []\n", + "for f in range(frame_num):\n", + " \n", + " circuits = []\n", + " for j in range(3):\n", + " qc = QuantumCircuit(q)\n", + " qc.initialize(state[j],q)\n", + " qc.ry(2*np.pi*f/frame_num,q)\n", + " circuits.append( qc )\n", + "\n", + " job = execute(circuits, backend)\n", + "\n", + " counts = []\n", + " for j in range(3):\n", + " counts.append( ket2counts( job.result().get_statevector(circuits[j]) ) )\n", + " \n", + " frame = counts2image(counts,grid)\n", + " \n", + " filename = 'frame_'+str(f)+'.png'\n", + " save_image( counts2image(counts,grid), scale=[300,300], filename=filename)\n", + " filenames.append( 'game_engines/outputs/' + filename )\n", + "\n", + "APNG.from_files(filenames,delay=250).save('game_engines/outputs/animation.png')\n", + "\n", + "for file in filenames:\n", + " os.remove(file)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](game_engines/outputs/animation.png)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the above example, each qubit from each circuit is rotated in the y axis at the same rate. These rotations all cover the range from $0$ to $2\\pi$ over the course of the frames. To explore different possibilities, we could choose different fractions of $2\\pi$ to be covered over the frames. Setting this $>1$ will cause a faster rotation, and $<1$ will cause a slower one. Setting it to an integer will mean that the corresponding qubit will return to its original state by the final frame (or almost, anyway). For a non-integer, it will not.\n", + "\n", + "Below you can choose values of these fractions for each qubit for each colour channel via Jupyter widgets." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "2969de7e3f194c0c9616a4372d8aee08", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Tab(children=(VBox(children=(FloatSlider(value=1.0, description='qubit 0', max=5.0, step=0.01), FloatSlider(va…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import ipywidgets as widgets\n", + "\n", + "def make_box():\n", + " children = [widgets.FloatSlider(value=1,max=5.0,step=0.01,description='qubit '+str(qubit),show=True) for qubit in range(n)]\n", + " box = widgets.VBox(children)\n", + " return box\n", + "\n", + "tab = widgets.Tab()\n", + "tab.children = [make_box() for j in range(3)]\n", + "channels = ['Red Channel','Green Channel','Blue Channel']\n", + "for j in range(3):\n", + " tab.set_title(j, channels[j])\n", + " \n", + "tab" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once you've chosen, run the cells below to extract the values and create the animation." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "fraction = [[],[],[]]\n", + "for j in range(3):\n", + " for qubit in range(n):\n", + " fraction[j].append( tab.children[j].children[qubit].value)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "state = image2state(plumber,grid)\n", + "\n", + "filenames = []\n", + "for f in range(frame_num):\n", + " \n", + " circuits = []\n", + " for j in range(3):\n", + " qc = QuantumCircuit(q)\n", + " qc.initialize(state[j],q)\n", + " for qubit in range(n):\n", + " qc.ry(2*np.pi*fraction[j][qubit]*f/frame_num,q[qubit])\n", + " circuits.append( qc )\n", + "\n", + " job = execute(circuits, backend)\n", + "\n", + " counts = []\n", + " for j in range(3):\n", + " counts.append( ket2counts( job.result().get_statevector(circuits[j]) ) )\n", + " \n", + " frame = counts2image(counts,grid)\n", + " \n", + " filename = 'frame_'+str(f)+'.png'\n", + " save_image( counts2image(counts,grid), scale=[300,300], filename=filename)\n", + " filenames.append( 'game_engines/outputs/' + filename )\n", + "\n", + "APNG.from_files(filenames,delay=250).save('game_engines/outputs/new_animation.png')\n", + "\n", + "for file in filenames:\n", + " os.remove(file) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "![](game_engines/outputs/new_animation.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/community/games/random_terrain_generation.ipynb b/community/games/random_terrain_generation.ipynb index 3bbf6b703..ae321d222 100644 --- a/community/games/random_terrain_generation.ipynb +++ b/community/games/random_terrain_generation.ipynb @@ -765,7 +765,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Clearly our Terrain maps are blockier than the example of Perlin noise shown at the top. This could be dealt with by adding more qubits, or simply by being careful in choosing how we apply the method. When generating terrain with Perlin noise, multiple layers are often used to create a good effect. Perhaps in future, some of those layers could be quantum." + "Clearly our Terrain maps are blockier than the example of Perlin noise shown at the top. This could be dealt with by adding more qubits, or simply by being careful in choosing how we apply the method. When generating terrain with Perlin noise, multiple layers are often used to create a good effect. Perhaps in future, some of those layers could be quantum.\n", + "\n", + "For another example of generation images using qubits, see the [Quantum Animations](quantum_animations.ipynb) notebook." ] } ],