Seminar 14 Snake game, part 2

Our plan for today is to continue developing the snake game. Our original plan was as follows and we got up to #4.

  1. Create boilerplate code to initialize PsychoPy.
  2. Figure out how to place a square. We need this because our snake is made of square segments and lives on a rectangular grid made of squares.
  3. Create a single segment stationary snake.
  4. Make the snake move assuming a rectangular grid.
  5. Implement “died by hitting a wall”.
  6. Add apples and make the snake grow.
  7. Add check for biting itself.
  8. Add bells-and-whistles to make game look awesome.

As before, create a new folder for this seminar, copy the latest version of utilities.py to it and base your further development code on the latest exercise from the previous seminar (should be exercise08.py).

14.1 Hitting the wall

The last thing that we implemented during the previous seminar was our ability to control the snake. However, you could steer it off the screen and make it go through itself. Let us fix the former!

Develop a new function hit_the_wall and put it into utilities.py. The function should take snake variable as a first parameter and check whether the head of the snake (which segment is it?) is still within the grid that we defined (how do you check for that?). The function should return a logical value. True, if the head of the snake is outside of the grid limits (so, it is true that the snake hit the wall). False, if it is still inside. I mentioned that you should definitely pass the snake as an obvious parameter of the function but do you need other parameters? If yes, which ones? Document the function!

Test the function by adding a new condition inside the main game loop. Check whether the snake hit_the_wall() and, if that is the case, the game should be over. Think about the optimal place where to check for this. You could do it on every iteration but there is a more logical place for it, inside that main loop. Where is it?

Put function hit_the_wall into utilities.py and your updated code into exercise01.py.

14.2 Is this the snake?

In the next section, we will be adding apples to the game. The catch is that these apples should appear at a location that is not occupied by the snake. Otherwise, we would generate apples directly into snake’s stomach. Practical for the snake but defeats the purpose of the game. To rephrase this problem, we need a function that checks whether a particular grid location is occupied by the snake.

Develop a new function is_inside_the_snake that takes the snake as a first parameter and a tuple with a grid location as a second parameter and returns a logical value whether that grid location is occupied by the snake (True) or not (False). Document the function!

My approach would be to work on this function in a Jupyter Notebook first. To debug it, create a snake by hand hardcoding the list of dictionaries. Note that you only need pos part of the segment’s dictionary for this function, so can happily ignore the visuals key. Once you feel the function works, copy it to utilities.py.

Put function is_inside_the_snake into utilities.py.

14.3 An inedible apple

Let us add that highly desirable fruit: the apple! We will represent it as a differently colored square and snake won’t be able to eat it yet. I assume that you used green color for the snake, so apples would yellow, red, or orange (but feel free to pick any color of your fancy). Just like a snake segment, an apple is characterized by its position on the grid and by its visuals, so you can use the same dictionary structure as for the snake’s segment.

You need to write a new function create_apple that will be fairly similar to create_snake_segment() function you developed the last time. It should also return a dictionary with pos and visuals field. However, instead of taking a predefined grid location, it should generate one randomly within the grid making sure it is not on the snake. The is_inside_the_snake function will help you to ensure the latter. Think about parameters that you need for this function. Remember to document it!

In the main loop, create a new variable apple and initialize it via create_apple() function. Think about where and when do you need to initialize it. Also, you need to drawi the apple in the main loop. Again, think about where should you do it.

Put function create_apple into utilities.py and your updated code into exercise02.py.

14.4 Eating an apple

Apples exist for snakes to eat them! Let us add this functionality. The general idea is very simple. If the head of the snake moves on to the grid location with an apple, you should not trim its tail. See how useful it was to split growing and trimming into two separate functions? Told you, strategic thinking!

You need to add a conditional statement that if the snake’s head is on the apple, you should generate a new one the same way as you generated it initially (add the next apple). What should you do, if there is no apple at that location?

Also, if you haven’t already done that, modify you code so that you start with a single segment snake.

Put your updated code into exercise03.py.

14.5 Eating yourself

Once our snake grows beyond four segments, it has an opportunity to bite itself22. For this, we need to check that, after the snake moved, its head is not at the same location as one of the segments of its body. Create a new function snake_bit_itself() that takes the snake as a single parameter and returns True or False based on whether that is the case. The function is very similar but not identical to is_inside_the_snake() function you implemented earlier. What is the critical difference and why cannot you simply reuse that function?

Once you implemented snake_bit_itself() function, you should check for that eventually after it moved and, if that is the case, the game should be over.

Put function snake_bit_itself into utilities.py and your updated code into exercise04.py.

14.6 Bells and whistles: score

Now that we have a fully functional game, we can start adding non-essential but nice features to it. The first one will be the score that is equal to the length of the snake. It should read Score: XXX. Place it at the top of the window. You will need to use TextStim class for this. Think about when to create the text stimulus, when to draw it, and how and when to update the text.

Put your updated code into exercise05.py.

14.7 Bells and whistels: three lives

Let us give the player three attempts to achieve the top score. They have three lives, every time the snake dies, the game but not the score resets: A single segment snake appears at the center and a new random apple appears elsewhere (where should you put the code to create them?). Once the snake dies three times, the game is over. Think how you can implement this three repetitions.

The score should be cumulative, so at the beginning of round two it should be equal to the final score of round one plus current length of the snake (1 at the very beginning). Think how you can achieve this. Another important point: now you have two nested loop, one is for the game, one is for the round. When the snake dies, the round is over and, if you run out of lives, the game as well. When the player presses escape both round and the game are over. Think about how you can implement it.

Put your updated code into exercise06.py.

14.8 Bells and whistels: showing lives

Let us not just repeat the game three times but show the player how many lives they still have. Download the heart.png23 and use it show remaining lives at the top-left corner of the screen: three hearts in round one, two hearts in round two, and just a single heart in round three. You will need to use (ImageStim)[https://www.psychopy.org/api/visual/imagestim.html#psychopy.visual.ImageStim] for this. Think about the size of images and their location.

Put your updated code into exercise07.py.

14.9 Bells and whistles: difficulty

At the moment, the difficulty of the game, the speed with which the snake moves, is fixed and the player has no way of choosing it. Let us create dialog that appears before we create the window and start the game that will allow the player to choose between easy, normal, and difficult24. I leave it up to you to decide which snake speeds correspond to each difficulty (and you can have more than three options, if you want).

To create and run the dialog, use Dlg class from giu module of PsychoPy. Your challenge for today is to figure out how to use it based on the manual along. Take a look at the example and experiment with in a separate file.

Put your updated code into exercise08.py.

14.10 Bells and whistles: sounds

Download game-over-arcade.wav25 and 8-bit-game-over-sound.wav26. Use the former whenever the snake dies and use the latter when the player runs out of lives. You will need to use Sound class from sound module of PsychoPy.

Put your updated code into exercise09.py.

14.11 Bells and whistles: blinking game over message

Once the game is over, show a blinking “Game Over” message superimposed over the final static game screen. Thus, you need to draw all the game objects and messages (but without moving the snake) plus you show a text message that is on for 0.5 second and off for 0.5 seconds until the player presses Space button. Hint, it should be a separate loop after the main game loop over rounds and clock/timers have definitely something to do with it.

Put your updated code into exercise10.py.

14.12 Next stop: classes and objects

Nice game! Next time you will learn about classes and objects and you will rewrite the Snake Game using them. This way, we can concentrate on figuring out classes, as you already know how the game works.


  1. Why at least five? Draw it on the grid and figure out whether it can do so with four.↩︎

  2. This image was downloaded from openclipart.org and was created by cliparteles↩︎

  3. Or, if you played Doom, between I’m Too Young To Die, Hey, Not Too Rough, Hurt Me Plenty, Ultra-Violence, and Nightmare.↩︎

  4. Downloaded from freesound.org and created by myfox14↩︎

  5. Also downloaded from freesound.org and created by EVRetro↩︎