Refactoring the Python Snake Game I Wrote 4 Years Ago | by Emmanuel Murairi | Jun, 2022

Lessons on consistency and best practices

Photo by Lorenzo Herrera on Unsplash

I wrote this snake game back in 2017, around the time I wrote my first hello world in Python.

Going over this old codebase 5 years later was quite interesting. A mixture of ambiguous french variables in an overly repetitive coding pattern, not to mention the comments saying things like “this is a stop sign” on a stop sign — remember that meme? Not to betray my younger self, I found there were quite a number of code inconsistencies and questionable engineering decisions. Maybe I would say the same thing 5 years down the line, looking back at this very article, hopefully, the first of many more to come.

As programming became one of my hobbies, I quickly went from playing games to building my own. This was more fun! I didn’t really invent a new game, mostly wrote my own versions of existing ones. But programming the game meant deeply understanding the rules, writing them, or even bending them — intentionally or unintentionally (with bugs).

Sometimes, the game would break while I showcased it to my siblings, then I would have to fix it again and again. Those were my first baby steps. I still believe it’s the best way to learn to program, not building games per se but building things you’re interested in.

Solving small problems, and moving to bigger problems which are usually just a bunch of small problems put together. In my case, I moved from writing these small games to automation tools, business software, and websites, and now exploring the world of data at mdoc.

Enough with the long prelude. In this first article, we will be reviewing my old snake game. It was one of my favorites growing up. The rules are pretty simple: move a snake to eat some fruits, avoid walls, gain points and grow. To program this, all you need is:

  • An empty canvas
  • A bunch of images to make up your snake, the walls, and the food
  • A way to move the snake (a bunch of images moving synchronously)
  • A way to tell if the head of your snake (a special image) hits the bricks or itself, for which case the user fails
  • A way to tell if the head hits a fruit, for which case the snake eats the fruit and another fruit appears. You also need to grow the snake here, maybe every time you eat 2 fruits, the snake grows by one unit.

Putting all this together, I got this: a very simplistic snake game with multiple levels.

Not the best-looking snake game for sure, but hey, it works! Now you might ask, what’s wrong with the game if it works? Isn’t that the goal? Sometimes, that’s the only goal. Write something that works and never comes back to it. However, in most cases, you’ll need to come back to it. You’ll want to add some new cool features, like a bonus fruit, a pause button, a change in the snake color, etc. If the codebase is poorly written, it would be harder to make any changes, and even harder to team up with a friend and launch your cool snake game!

As a software engineer (or any engineering position that involves writing programs), you need to make sure your program is maintainable: you or anyone else can come back to it to make changes, without having to spend a year trying to understand what a single function does.

I have made available here on GitHub the old version of the game (snakev1.py), and a newer refactored version (snakev2.py). I tried not to change too much of the core logic. I mainly changed the architecture and patterns that could make the program more maintainable. I also fixed a few bugs on the way, and translated the whole source code into English — not to say writing in French was a bad decision. Some of the main issues I found in my old code were consistency, patterns, following conventions, and using best practices.

You shouldn’t have to remember small things.

I can’t even start thinking how many times I had misplaced my school uniform in primary school. My mind just couldn’t comprehend why going through the trouble of putting the uniform in the closet if it could very well sit on the chair, or the table, or even my mum’s sewing machine. Besides getting into trouble for having my clothes everywhere when my mum or dad sees them, finding them the next morning becomes a treasure hunt. Having one place to put my uniforms not only made the house more organized, or at the very least, set me on good terms with mum and dad, it also saved me the trouble of remembering where I put them.

You shouldn’t have to remember small things. No one should. You shouldn’t need to remember your variable names or case, file locations, functions case, etc. You shouldn’t need to ask yourself questions like: Did I name the snake head HEAD or head? Or is it head_image or headImage? Or is it even head to start with, did I find it easier to use h to refer to head?

One way to have peace with those small things is by following conventions. I remember I used to skip those in my early days, they were boring to me. I only used to read the fun parts of the book.

Not following conventions means creating habits, and patterns, good or bad ones, but most probably inconsistent with the “industry standards” — basically what other devs do/use. Following conventions, or at least not going too far from them even when you have good reasons to, saves you from a lot of trouble.

Give your variables names you won’t need to remember.

Old code snippet:

snake1.py

From this snippet, now, I can’t even tell what fl means on self.flmenu, how embarrassing! And did you see the menshort for Menu? Did you see that camel case trying to find its place within the python snake case?

Refactored version:

snake2.py

On this version, we don’t have to remember that men stands for menuand we have context that this menu is for configuration.

Also, our menu button for configuration is now config_menu_buttoninstead of flmenu. Now, with a good coding editor with auto-complete, when you need your configuration menu button, you’re pretty sure it will recommend you exactly the variable you chose from writing the first few letters of configuration or menu. You could build more patterns like prefixing all your menus by menu_ if that fits your context.

Create consistent patterns in your naming.

Old code:

snake1.py

Refactored version:

constants.py

Here, I not only have less inconsistency with the case and file names, but I also have a more concise code! I am loading all my images with 2 lines instead of 10, and can easily load more by adding the image name on the list, given I am consistent with the file name and extension.

In the new version, I use dictionary comprehension to populate a dictionary that will hold all instances of my image objects, with the filename (without extension) as the dictionary key.

Now, to access an image, instead of referring to its variable as from the previous approach, I would need to refer to the photo_images attribute with the image name as the key.

The first approach is not too bad, especially with the small number of images to load. Simple variable declarations, that’s all. However, the second approach reinforces the idea of ​​consistency and makes the code shorter without trading readability.

It also gives room to separate the declaration of image names from the main program logic. Here, we declare them as constants on the constants.py, making the program more modular.

Write single-purpose functions

Old code:

Refactored version:

Here, there is a clear break of best practices in writing a function. The first part of the function is initializing the board, and the second part tries to set a fruit on an empty spot on the board.

A good function should only do one task. In this case, it should only prep the board. If you go over the whole code base, you’ll see there are a few other places you need to reset the fruit location, like when the snake eats the fruit or when you restart the game.

This means I had to duplicate this particular logic. 4 times to be exact. This not only makes the code base unnecessarily bigger, but it also makes it harder to maintain. When I need to change something about how I place the fruit, I need to change it in 4 different places! The best practice here, called DRY (Don’t Repeat Yourself) encourages writing code without duplicated logic.

Hope you’ll notice some more improvements made on the new version, or better, find opportunities for improvement on the new version. There is almost always something that could be improved, something that could be added. Something that needs to be read, and understood.

Something that needs to be readable, and understandable — hopefully easily understandable. I hope, if you’re a developer when that time comes, you have a codebase that doesn’t give you too much trouble. If you’re not a developer and got this far, hope this could be applied to what you do as well.

Maybe getting your kids or younger siblings not to misplace their school uniforms, or create more patterns in whatever it is you do to make the world a better place, and hopefully have fewer things to remember. After all, who likes remembering things!

Leave a Comment