Programming

Make a Snake game for Android written in Python – Part 2

If you followed Part 1 of this tutorial, hopefully your development environment should be all set. We’re ready to get down to business (in France we would say : “mettre les mains dans le cambouis”. Because you can learn coding, and useless french idioms at the same time!)

The objective

In this part of the tutorial, we will make the game engine of our snake. By making the game engine, I mean :

  1. Writing the classes corresponding to the skeleton of our app.
  2. Giving them the proper methods and properties so that we can control their behavior as we wish.
  3. Put everything in the main loop of our app and see how it goes (spoiler alert : it ought to go well since I tested the code every step of the way).

For every section, I will start by explicitly explain what we are doing then present the code and finally link to the corresponding version of the repository.

The classes

What’s a snake game if you decompose its elements? Well for starter : a playground and a snake. Oh, and don’t forget the fruit that pops from time to time! The snake in itself is composed of two main elements : a head, and a tail.

Thus, we will need to implement the following widgets hierarchy :

  • Playground
    • Fruit
    • Snake
      • SnakeHead
      • SnakeTail

We are going to declare our classes in the python file of our application, and if need be in a .kv file in order to separate the front-end from the back-end logic and to make use of the automated binding system.

main.py

snake.kv

Full code.

Properties

Now that we have our classes set, we can start to think about their content. To do that, we are going to define a few things.

The Playground is the root Widget. We will divide its canvas as a grid, setting the number of rows and columns as properties. This matrix will help us to position and navigate our snake. Every child widget’s representation on the canvas will occupy the size 1 cell. We also need to store the score and the rhythm at which the fruit will pop (more on this later), as well as a turn counter that will be used to know when to pop the fruit and when to remove it.

Last but not least, we also need to manage input. I’ll explain more precisely how in the next section. For now, we’re just going to accept that we need to store the start position when the on_touch_down event is triggered and a boolean variable stating if an action was triggered by the current pattern of input.

Note : don’t forget to import the kivy Properties we add along the way.

The snake now : the Snake object in itself doesn’t need to store anything else than its two children (head and tail). It will only serve as in interface so that we don’t interact directly with its components.

The SnakeHead however is a different matter. We want to store its position in the grid. We also need to know which direction it is currently set to, to choose the right graphical representation as well as to navigate the snake between turns (if the direction is left, draw a left-pointing triangle on the [x-1, y] cell etc.).

Position + direction will correspond to a certain set of drawing instructions. To draw a triangle, we need to store 6 points of coordinates : (x0, y0), (x1, y1), (x2, y2). These coordinates are no more cells of the grid as the position was : they are the corresponding pixels values on the canvas.

Finally, we’ll have to store the object drawn to the canvas in order to remove it later (for a game reset per example). So that we’re super safe, we’ll add a boolean variable indicating if indeed the object is drawn on the canvas (this way if we ask for the object to be removed wrongfully and the object was never actually drawn, nothing will happen. As opposed to our app crashing).

Now for the tail. It is composed of blocks, each occupying one cell and corresponding to the positions occupied by the head during the past turns. Thus, we need to define the size of the tail which will be set by default as 3 blocks. Moreover, we’ll want to store the positions of its constituent blocks, and the corresponding objects drawn on the canvas so that we can update them during each turn (ie : remove the last tail block and add a new one where the head was so that the tail moves with the head).

Finally the fruit. Its graphical behavior is similar to the head, so we’ll need a state variable and a property storing the object drawn. The fruit will pop from time to time, so we have to define the number of turns during which it will stay on board (duration) and the interval between the appearances. These two values will be used to compute the fruit_rhythm in the Playground class (remember, I said we would get back to that).

The App class also needs a quick modification. We are going to pass the Playground as a property so that we can continue to interact with it after build() is called. I’ll explain why in the next section.

One more thing : do we have anything to add into the .kv file ? Well yes we do Barry, yes we do. We need to set the dimensions of our widgets. Remember our imaginary grid ? We’ll use that to compute the width and the height of each widget. Whereas it is the fruit or the snake, the formula is the same :

  • width = playground width / number of columns
  • height = playground height / number of rows

The Snake will then pass on these values to its children. Oh and since we’re at it, let’s add a Label on the Playground to display the score.

Full code

Methods

Let’s start with the Snake class. We want to be able to set its starting position and direction, and to make it move accordingly. The counterpart would also be good : get the current position (to check if the player lost because the snake is outbound, per example), same thing regarding the direction. We also need to be able to instruct the snake to remove its representation from the canvas. Behind the scenes, the Snake will dispatch the right instructions to its components. We wouldn’t want to manually remove its children every time we want the snake gone. Its kids, its responsibility !

We called a number of methods involving the head and the tail but didn’t create them yet. For the SnakeTail, we want remove() and add_block().

For the head, move() and remove(). The former will implicate two steps : changing the position according to the direction (+1 cell up, or down, or…), and rendering a Triangle at this new position. We also want to check on remove if the object we’re removing is indeed on board (remember the state variable we created for that purpose ?).

What about the fruit ? We need to be able to make it pop on given coordinates, and to remove it. The syntax should start to become familiar by now.

We’re almost there, don’t give up ! We need to add control for the whole game now, which will take place in the Playground class. Let’s review the logic of the game : it starts, a new snake is added on random coordinates, the game is updated to go to the next turn. We check for a possible defeat. For now, a defeat happens if the snake’s head collides with its own tail, or if it exits the screen. In case of defeat, the game is reset.

How will we handle the user’s input ? When the screen is touched, the position of the touch is stored. When the user moves its finger across the screen, the successive positions are compared to the starting position. If the move corresponds to a translation equal to 10% of the screen’s size, we consider it as an instruction and check in which direction the translation was made. We set the snake’s direction accordingly.

Full code.

Congratulations, if you’re still reading you’ve passed the hardest part of the tutorial. Try to run it : nothing happens but we have our playground and the score, which is a good start. All our methods are ready. We just need to schedule them in the main loop.

The main loop

The update method of the Playground is the key here. It will handle the event scheduling for the fruit, and reschedule itself after each turn. This peculiar behavior is implemented so that we avoid any unintended update loop, and will be useful in the next part of the tutorial when we add some options to the game (like an increasing update rhythm). For now a turn will last one second.

Let’s not forget to unscheduled all events in case of a reset. By the way, you did import the Clock, right ? 😉

You’re almost ready to play your own snake ! Are you excited ? I’m excited (well I was the first time). Phrasing!

Anyhow. Recall that we made the Playground instance a property of our main App. Why is that ? Because we need to start the game when the App in itself starts, and not when build() is called. Otherwise the sizes we set in the .kv file would be initialized at their default values (100,100). That’s not what we want. We want the proper size of the screen. Here we go :

You can run your App now. Et voilà ! You can package it with buildozer if you want to give it a try on your phone, or wait for the next part of the tutorial that we add a nice welcome screen with some options.

Full code

6 Comments

  1. Hey nice work on the tutorial , this will certanly be helpfull. You do have a typo in SnakeTail line 47, instead of ‘self.tail_blocks.append’ you need ‘self.tail_blocks_objects.append’

  2. Hi there thank you for this tutorial, it is very helpful.

    It seems there is a error in the first snake.kv snippet :

    ;

    ;

    it should be :

    :

    :

    1. I’m glad that it can be of use.

      You are absolutely right about the mistakes (well there should not be either “:” or “;”). I don’t know how that got in there. Hopefully the repo version is correct. Thank you for finding it!

  3. Well the code as been removed in the comment but basically there is a ‘;’ instead of a ‘:’ in the Playgroud and Snake object declarations.

Leave a Reply