Hello!
This is part one of a multi-video series showing my take on the classic snake game using Python.
Although making real time games is not trivial even at the classic snake game level,
this tutorial is aimed at beginner Python programmers.
I do assume that you have some programming experience with another programming language
and having prior exposure to Python would be helpful.
This tutorial combines several concepts such as terminal control using the curses library,
as well as dictionaries, lists and tuple structures.
Also, I will use threading and show you how to handle runtime errors.
Don't get discouraged if my videos may seem over your head at times.
Ultimately you need to look at code that is beyond your current skill level in order to learn new things.
You can always slow down the video or re-watch segments that are too fast.
Obviously the snake game, being a classic, has been covered by other YouTube videos.
I looked at several of them and they tend to be either rushed or incomplete.
I would like to think that my tutorial is better, not just because it is in color,
but most of all it is better in terms of approach.
You will be shown everything in great detail and I'll explain all code as I go.
I do not use libraries such as pygame on purpose
and I do not recommend any graphical tools for editing Python programs for beginners either.
All you need is a Linux terminal here!
It is my strong belief that using highly developed libraries and graphical editors
impedes learning when introduced too early.
As I just mentioned, you need access to Linux command line to follow this tutorial.
I am using the Raspberry Pi OS, but any Linux distribution will do including one that you
access remotely using ssh or Putty.
You need Python version 3.6 or later to use my code directly.
You can check whether Python 3 is available using the following command: which python3
This command will tell you the path to the Python 3 interpreter.
Take a note of that if your path is different than mine as you will need to enter it at the beginning of your program.
Make sure that your python version is at least 3.6, using the following command: python3 --version
My full code is linked in the video's description.
You can download it now and follow my tutorial that way or you can type it alongside me.
You can use any text editor you like to type the code.
I use the vi editor but if you are a beginner you may want to give the nano editor a try
as it is very easy to use.
This command will use the nano editor to create a hello.py Python program: nano hello.py
A proper Python program requires a special line at the beginning which starts with: #!
followed by the path to the Python interpreter, as shown by the which command
I demonstrated a few moments ago.
This needs to be entered at the beginning of the file.
The actual program goes below that.
This is what a Python "Hello World!" program would look like...
The bottom of the screen lists nano keyboard shortcuts activated using the "Ctrl" key.
The two most important ones are: "Ctrl+o" to save and "Ctrl+x" to exit.
nano will prompt you to save changes when exiting.
You can save the changes under different file name or confirm the file name by pressing Enter.
A newly-created Python program does not have sufficient permissions to be run.
It is missing the execute flag.
You can set it using the following command: chmod u+x hello.py
To run the program located in your present directory we use the "./" prefix,
followed by the program name and the Enter key.
I suggest that you make for this game a dedicated directory
(what some people call a "folder"),
using the following command: mkdir snake-game
Use the "cd snake-game" command to enter that directory and create the snake program file in there.
If you are using nano, it needs to be configured for Python programming.
You need to create a configuration file called ".nanorc"
These four lines will make your Python programming using nano much easier.
You want the line numbers displayed and auto indent the code as you go.
Python code formatting affects the way the code runs.
Each indentation level should be a multiple of four spaces.
I prefer using tabs over typing multiple spaces.
Setting the tabs to four spaces and converting them automatically will take care of that.
These settings will take effect once you restart the editor.
All-right! Let's start the game code!
Start your program with the familiar Python 3 interpreter path,
making sure that it starts with the "#!" mark characters.
Because the entire game is implemented using text, I will use the curses library for terminal control.
This library is very well established and is perfect for this type of a program,
as it allows full control over the terminal input and output.
The first thing that needs to be done is the basic framework of the program:
initializing of the curses library and proper program termination.
The program needs to correctly restore the terminal to normal upon exit,
whether it's through normal termination using "Ctrl+c" key sequence or an unexpected runtime error.
This is extremely important when using curses.
Incorrect program shutdown will leave you with a broken, usable terminal,
forcing you to close it and open a new one.
The "try" keyword marks a section of the program that contains code, which may result in an error,
when that code is running.
One such situation may arise when the program will receive a kill signal.
Unless I will specifically say what to do then, the program will terminate immediately,
leaving the terminal still under the curses control which is not desirable.
This line tells the program to call the "safe_exit" function, when such signal is received.
My safe_exit function will be very simple and have only one statement inside:
an exit with a diagnostic status code of 1.
Any status code other than a 0 traditionally means abnormal program termination.
The safe_exit function will end the program, but since it is tied to the try block,
the program will also execute code following the try block contained inside the section called "finally"
I will add the "finally" section in a few moments.
The curses library needs to be initialized using the "initscr" function before it can be used.
In a most basic scenario you can create a terminal window that physically matches the terminal used.
As soon as this happens, the curses library has full control over the terminal
until explicitly deactivated.
This is permanent, even after the program exits,
therefore it is imperative to deactivate curses before the program ends.
The finally block at the end of the program is the perfect spot to take care of that.
Any code inside of this finally block will be always executed at the end
of the program execution as this finally block is located at the end of the program.
The "endwin" function should be sufficient to restore the terminal to normal state.
This game will work in a terminal of any size but for practical reasons
the terminal should have some minimal dimensions.
This line will determine the "y", which is the number of rows, and "x", which is the number of columns
of the terminal window.
It's all measured in characters, represented as a pair of integers.
Let's establish that the game needs at least a 20 rows by a 50 columns terminal.
If either of these requirements is not met, the program will raise an exception (a runtime error).
Since this exception is raised within the try block,
I add a section below the try block to tell the program what to do in such case.
When this RuntimeError exception is raised any code that will follow is skipped
and the program will go straight to the matching "except" section.
In this scenario a runtime error is raised due to an unrecoverable situation.
The program cannot continue in a terminal that is too small.
All it can really do, is inform the user about the problem and provide a suggestion to fix it.
Code in the finally block will be executed after that and the program will terminate.
The f-string expression in the third print statement allows including variables in
what is shown to the user.
Here I will inform the user what the current terminal dimensions are.
Note that I show that information to the user in an x-y order, which is what most people expect.
However, the curses library uses y-x coordinates order and I will follow
the y x approach for all notation within the program that is not shown to the user.
It's important to remember that the terminal is still under the curses control
when handling the RuntimeError exception.
The print statement, at least in its plain version, does not work well with curses,
where the output would be not aligned as expected.
As I need to deactivate curses anyway, I will actually do that before using any print statements.
Unfortunately, this also means that with the RuntimeError exception, the "endwin" function will
be called twice, as it is also inside the finally block causing undesirable terminal behaviour.
A simple condition will check whether curses are still active and call endwin when only if needed.
Let's test it! There's no need to exit the editor .
if you are using nano, just press "ctrl o" to save the program!
I like to use two terminals and use Alt+Tab to quickly switch between them
and i use the second terminal to run and test my code.
If this is a new terminal for you, make sure to use the cd command to enter the directory
where your program is saved and set the permissions before running your program.
My terminal is too small and the program is letting me know
Note that the program ends and the terminal appears to be functioning normally.
If I run the program full screen the program ends normally without any error message.
The curses setup is almost complete.
I will use the keyboard to control the snake movement,
but I do not want to see the cursor and the characters as I press the keys (or any keys for that matter).
These two lines will turn off the cursor and suppress the echo respectively.
I also would like to split the terminal into two sub windows with each serving as separate purpose.
For now, I will have a score display area at the top and a main game area at the bottom .
In case you're wondering, all terminal windows are rectangles under curses.
The score_board will be just one line located at the top of the screen spanning the
entire width of the terminal.
The four parameters used in the "newwin" function, starting from the left,
indicate the height and width.
They are both measured in characters.
Upper-left corner y-coordinate and upper-left x-coordinate.
Coordinates "0, 0" here indicate the upper left corner of my putty terminal window.
The game area is the remainder of the physical terminal,
with the one line height of the score_board deducted
and the upper left corner coordinate y starting at row 1, just below the score_board.
I would like to use the arrow keys to control the snake inside the game area sub-window.
Normally pressing the arrows generates complex multi-byte key codes which I do not want.
The "keypad" function will enable simple codes for the arrow keys.
For example, the code for the app arrow will be 259 that way.
I also would like the keyboard to be read continuously.
The "timeout" [function] sets the delay with the minimal interval being 1 millisecond.
You could easily change that to 10 milliseconds without affecting the game responsiveness.
The actual keyboard processing routine will be added later and with the exception of that,
the game framework is all set up!
We are ready to start coding the details of the game
Let me create a "start_game" function, which will contain initial code to display the game screen,
starting with a game_area surrounded by a border.
For now, I will just pause the program at the end of this function.
The program would end immediately without it.
The "border" function will create a simple visual edge on the screen,
outlining the valid snake movement area,
It's important to know that displaying anything in the terminal with curses is a two-step process.
You need to prepare the content first
and then explicitly call the "refresh" function to actually show it.
This was originally designed to preserve bandwidth when Internet connections were slow,
but as you will find out soon, it also helps to better control when content is shown to the user.
Let's have a look how all this works!
You can see the game area outline.
Notice that the top line of the terminal does not have a border.
This is the score_board, which I will configure later.
If you are using Putty, then your program output probably looks like this...
You will need to change a Putty setting to fix that...
The game should up you correctly after it is restarted.
This game is meant to be played in a fixed screen.
Resizing the terminal while the program is running will terminate it.
You can set the game window size before running the program
and the game area will adjust itself automatically then.
Of course, the minimal terminal dimensions still apply!
That's it for Part 1!
In the next video I will show you how to implement the full snake game in monochrome.
Please hit the "Like" button,
and if you are not a subscriber yet, please subscribe!
Thanks for watching!