Crazy Snake

In this post we will dissect a simple Snake game written completely in Basic. As always there are many ways to write a game like this and in my opinion there is no best way to do it, it comes down to personal preferences and goals. My goal is to make it simple to follow and learn from but at the same time try to polish it to the point that it looks like complete game with title screen, levels, appealing visuals etc.

To follow this article you should know basics of Basic or some other programming language.



Please enjoy and leave comments for future improvements.

Project specs:

  • Emulator Version R.43
  • Default character mode 80x60
  • No sprites
  • Standard color palette
  • Snake moves around the screen with goal to eat as many hearts as possible
  • Each eaten heart awards points and grows the length of snakes body
  • 5 levels with increasing speed and snake growth
  • Each level is time limited and to progress to next level at least 10 hearts need to be eaten
  • Keyboard controls:
    • Left arrow – turn left from snake perspective
    • Right arrow – turn right from snake perspective
  • Snake is not allowed to hit side walls
  • Snake is not allowed to bite itself
  • No sound in this version

Program Structure

The goal of this project is to be easy readable so we will break it up into logical sections and try to explain each important component. We will start with variables and data structures, followed by main loop. Next part will be game logic and will finish with graphics subroutines.

Game setup

We will create all necessary data structures and initialize all variables. Remember in Basic for X16 scope of all variables is global, meaning they will be visible in all subroutines and can be changed in all subroutines.

Line 10 allocates space for the snake positions. We will store integers for X, Y and PETSCII of character to be drawn at that position.
In lines 50, 60 and 70 we initialize starting positions and character to be drawn in first five cells of all three arrays.
In line 80 we initialize pointers to arrays with T% representing the tail of the snake and H% representing the head of the snake. To illustrate, at the start we have the following content of above mentioned arrays and variables:


Few things to note. We are using % to indicate integer values, which means that each array element takes two bytes. So all three arrays together occupy 30,000 bytes. Of course there are more efficient ways to store coordinates and we don’t really need to store the character of the snake but for clarity and potential future enhancements I thought it doesn’t hurt to save it. 5000 elements of course limits the duration of the game but it is quite enough if we reset it for every level.
In line 20 we initialize three more variables. SC is used to store score. We use simple trick to display leading zeros where we start with very large number and simply display only right portion of the score. LV is used to store Level number, starting with 1 of course. Variable OVER is indicator if game is finished or not and can contain value 0 for false or not finished and 1 for true when the snake dies or we come to the end of the game.
In line 90 we initialize direction variables D% contains directions 1-Down, 2 –Left, 3-Up and 4-Right. Variables DX% and DY% contain the corresponding Delta X and Delta Y and each can contain values -1, 0 or 1. We use this to minimize number of IF statements in Game loop.
In line 100 we initialize variable that will be used to calculate X and Y position of the heart. Since in Basic we don’t need to declare variables and at the moment we don’t use these this is not really necessary but I like to list all of the variables that might be later used globally at the beginning so I can keep track of them.
Variable GROW initialized in line 110 is used to indicate if the snake is growing. Whenever we eat a heart that variable is increased and used to count down until we start deleting the tail and with that lengthening the snake.
The variable COUNT is used to count number of hearts eaten per level. That is needed because we decided that at least 10 hearts must be eaten in allocated time for being allowed to move to the next level.
We have two lines in this section that deal with keyboard input. First one is in line 40. In Basic GET function does not wait for keyboard but continues with next command and stores empty string into the parameter. Therefore line 40 keeps running until something else than empty string is found in string A$ which essentially make it to loop to wait for any key. We call this right after returning from subroutine starting in line 9000 which draws the title screen.
Other keyboard related like is the last in this section – line 150. Commander X16 (like Commodore 64) has buffer for 10 keyboard presses so in order to clear that buffer before going into Game loop we simply read from it for 10 times and with that clear it of any unwanted keystrokes that could affect the beginning of the gameplay.
The rest is just calls to subroutines. In line 120 we call 6000 where we clear the screen and prepare playing field.
Then we call 6500 where we warn player that game is about to start and wait few moments and then in line 140 we call Heart Spawning subroutine that starts at line 3500.
Before we go into the game loop just one more important consideration. We have three entry points into the game setup section.
Start at line 10 is executed only once when we initially start the program. It is very important to only initialize arrays once.
  • Entry at line 20 happens every time when we start a new game.
  • Entry at line 50 happens every time we start a new level. That is why we initialize some variables in line 20 and most in lines after 50.

Game Loop

Game Loop is of course one of most important concepts in game programming, regardless of programing language or platform. It is where the game “runs” and from where we do basic things like:
  • Read user controls
  • Update graphics
  • Check for collisions
  • React to other events like dying, timers, level changes etc.

Let’s check our game loop:

First part is pretty self-explanatory. In lines 510-530 we simply read keyboard input and jump to subroutines. If left arrow is pressed we go to line 1000 and in case of right arrow we jump to line 1500. We will look at both change of direction handlers later.
Line 540 is very important. It determines how fast the game is going. Commander X16 is fast enough to run simple games like snake very fast even in Basic therefore we have to implement wait loop to make game a little easier. Line 540 uses variable LV containing levels from 1 to 5 to calculate number of idle loops. In level 1 it make (5-1)*30 = 120 loops, in level 2 it makes 90 loops and so on. That way the games runs faster and faster as we progress towards the last level 5.
Then we have series of subroutine calls:

  • In 550 we call subroutine starting at 2000 where we update snake movements.
  • Line 560 is again very important because it goes to subroutine at line 3000 which takes care of collisions.
  • The last step is to draw changes to snake movement. That is done in subroutine at 4000 which is called from line 570.

So far the Game loop was very straight forward. Line 580 is a bit messy. In single line we increment the timer TM and update the time bar on the screen. Experienced programmer will notice that TM variable is actually the same as H% all the time and could be omitted, however for clarity purpose I decided to keep both and use them for their specific purpose, it is just a personal style. This is also the first time we are using PRINT and VPOKE to display something to the screen. We will talk about that in more detail later, for now it should be enough to know that PRINT CHR$(19) sets the (invisible) cursor to the top left corner of the screen. VPOKE is used to draw directly to the screen memory. When initializing the screen we draw the time line and this VPOKE simply replaces characters representing bar line with empty space and moving right as the TM variable increases and creating effect of disappearing Time bar.
Next step in line 590 checks if timer (and also H% pointer to snake head) is at the end of array which means that we finished the level and jump over line 650 where we check if we have game over variable OVER set to true (1) which would mean that we previously determined one of several conditions to end the game.
If timer TM is 5000 then we continue to next command at 600 where we check if we are currently in level 5 which means that we came to the end of the game (end of time in last level) and again we set game over flag and jump directly to 660 where we start doing Game Over processing.
If we are not in Level 5 and reached end of time we have to do some more checks if we can move play to next level. We first notify player by displaying a message in subroutine starting at line 6700 called from line 610. Then we check if we collected minimum of 10 hearts in that level in line 620. If we haven’t we again set Game over indicator and jump to game over processing at 660
If we passed all that then we can finally increment the Level counter and jump to line 50, which is the third entry point of out game initiation section.
If we bypassed all that checking because the timer was below 5000 we arrived to line 650 which sends us back to the beginning of the game loop where next step starts all over again.

Game Logic

Under Game logic section we will focus on core functionality that gets called from main loop and includes managing player controls, moving things around and reacting to collisions between different entities. Of course with simple game like snake all these things should be fairly simple. Let’s start with the simplest, user controls.

As we saw in the main loop we call subroutine at 1000 when turning left and 1500 when turning right. Both are pretty self-explanatory. We simply test the current direction D% with IF statement and change all directional variables: new direction in D% and DX% and DY%. Because we want to use special corner characters for turns we store the correct one into character array C% for later drawing. It is important that we do all these steps in single line and end it with RETURN. Commander X16 Basic does not have brackets or similar to group more than one line of code into a “structure”. It also doesn’t have Else statement or Switch/Case so we use series of IFs and use single line after it to group more than one command.
After we have done with processing turns we have to update snake – meaning move it next position based on the direction it is moving in.

First we simply move head pointer one step ahead in line 2010. Next two lines 2020 and 2030 are determining if we have to move tail too. If yes then we increase it by one which will have effect during drawing routine to essentially remove the last character of the snake creating an illusion that it is moving. If on the other hand variable GROW is not zero it means that snake is still growing from last eaten heart(s) we count down the growth which increases the size of the snake. We will set the GROW values in the collision routine.
In lines 2040 and 2050 we calculate and store new X and Y position of the newest character of the snake. In 2060 and 2070 we decide on the character that will be drawn. To reduce the number of IF statements we just set it to horizontal line (PETSCII code 64) in 2060 and then with one IF test if snake is actually moving up (3) or down (1) and overwrite with vertical line (PETSCII code 66).
After we know where the next location of the snake’s head will be we have to check if it overlaps with some obstruction. We do that in subroutine starting at 3000:

The first step is to read the content of the location of newly calculated snake head location. That is done in line 3010. We will explain that later in the Graphics chapter.
We will mostly be on the empty space therefore we first check for it in line 3020. Blank space has code 32 and if we are on it we simply return to the game loop no further action is necessary and game loopy with draw the snake’s head into that location.
Next check is for the heart, if we heat it means we can increase the score etc. and we will do that from line 3100 on.
If we passed these two IF statements it means we hit something else which is always a death of the snake and restart of the game because as we know snakes unlike cats only have one live. So we set game over flag in OVER to true (1) jump to subroutine at line 7000 which will determine why the snake died and display appropriate message. After the message is displayed in that subroutine we return to the main loop which will take care of restarting the game.
If we ate the heart we are now in line 3100. First we increase the GROW variable by the rate at which snake will grow. We multiply the level with 5 which means that in Level 1 every time we eat the heart snake will grow by 5 characters, in level 2 by 10 and so on until in the last, level 5 it will grow by full 25 characters to make it grow very fast and make it very difficult.
The score in 3110 is calculated in the same way to reward every eaten heart more by the level. We also increase COUNT by one. Remember COUNT is used to make sure we eat at least 10 hearts in each level to be allowed to move to the next one.
Then in line 3120 we change the color attribute of the character where the heart was. Hearts are drawn with Orange on Blue color but the snake is Yellow on Blue so we store Yellow (7) on Blue (6) to that position.
Next two lines 3130 and 3140 are updating the score on the screen. First CHR$(19) we go to “home” or top left position and then with tab we move 11 characters inside. With RIGHT$(STR$(SC),8) construct we first change the numeric value of variable SC to string and cut out right 8 characters. Remember that we started with very large number and we cut away that part plus a leading space which provides nicely formatted score. Please note that this technique is not suitable for every situation. Basic is typically not very good at garbage collection and creating and manipulating lots of strings can cause problems where garbage collection (cleaning old remains of those manipulations) could cause delays which reflect in stuttering of the movement.
The last step is to jump from line 3150 to subroutine at line 3500, which is responsible for creating new heart at random position on screen.

To calculate the location of heart we use RND function. It generates the value between 0.0 (inclusive) and 1.0 (exclusive) so for horizontal (X) position we have to multiply by 78 and add 1 and round it to integer so the resulting numbers can be between 1 and 78 (inclusive). Similarly, vertical (Y) position value will be between 1 and 58 (inclusive).
In line 3530 we check if the new location is empty (has blank space 32 in it) and if it is not repeat the procedure until we find an empty space to put a heart into. We do that in line 3540 and in line 3550 we set the color to orange on blue background. Then we can return to the calling program.
Ok finally it is time focus on the display and graphics.

Graphics

Before we go into actual drawing and subroutines used in Crazy Snake program we still have to look at how the data for display is actually organized. Since this game is only using simple character graphics we will only look at what we need.
First of all Commander X16 (unlike Commodore 64) has separate memory for video display and for program execution. Therefore we use different commands to access it: VPOKE and VPEEK and not the standard POKE and PEEK that are used to access the regular program memory (that includes ROM, RAM and some other special addresses). It is also important to know that Video memory has 128 Kilobytes so for it to be addressed we need 17 bits and is therefore addressable through two sections called banks. Default display starts at address $1B000, so in Bank 1. For our purposes we will only use default bank 1 so all our VPOKEs and VPEEKs will be in bank one and will have bank parameter set to 1.
In default text mode each character on screen uses two bytes. First byte determines the actual character and the second the color attribute. As we know each byte has 256 possible values from 0-255. That means that we can have 256 different characters in character set and 255 different color combinations. By default character set is similar to that in Commodore 64 so many of the references and programs can be ported fairly easily. Since each character has foreground and background color we can reserve half of byte (4 bits or nibble) to each of those which gives us 16 foreground colors and 16 background colors (16x16=256). By default Commander X16 has default palette equivalent to standard 16 Commodore 16 colors.
Let’s look at display memory layout for the default graphics mode which is 80 by 60 text mode with 16 colors:





It starts at the top left character, so byte 0 in video memory is first character visible in the top left corner of the screen. So if we want to draw some character to that location we would use a command with following format VPOKE bank, memory location, character code. We already said that by default we only use bank 1, we know that we want to draw at first position so memory location is $B000, we just have to figure out the character code. We can look at all of the codes later but to test this let’s just write letter A which has screen code 1. Remember that PETSCII codes used in Basic PRINT command are different than the codes stored directly in video memory. If we type command VPOKE 1,$B000,1 letter A magically appears in the first location. Now there are only two more things to know. Firs is that next byte in video memory does not represent the second character on screen but instead is color attribute for the first one. We will look at those later. The second character is in position 2, next one in position 4 and so on. The last thing we need to know is that even though on screen we see 80 characters (80 x 2 bytes = 160 bytes) the memory allocated for each line is actually 256 bytes (0 – 255).  That means that 96 bytes after first 160 are actually invisible by default. If we want to write some character on the first position of second row we need to start at memory position 256. So to write B at the first position of second row we use VPOKE 1,$B000+256,2 and voila.
Armed with that knowledge we can write simple program to write all 256 characters



So we can finally write down the formula for writing any character from the set of default character set with formula:
VPOKE 1,$B000+Y*256+X*2,CharCode
Reading is done similarly with:
CharCode=VPEEK(1,$B000+Y*256+X*2)
Since we are already at this we can get both formulas for writing and reading color attributes right now. They always follow the character code in next byte so we only need to add one:
VPOKE 1,$B000+Y*256+X*2+1,ColorCode
Reading is done similarly with:
ColorCode=VPEEK(1,$B000+Y*256+X*2+1)
We talked about color codes a little bit already. We said that four top bits represent background color lower four bits foreground color. Commander X16 has a fairly powerful graphics and therefore can display many more colors than default 16 we us now. Below is a sample of colors with a simple program that displays them.





The loop simply goes through all colors 0-15 and by multiplying by 16 we move them to high for bits so we are actually setting background color. Try to experiment with changing the foreground and combine drawing characters with changing color attributes to get comfortable with the concepts.
I hope this clarifies the inner workings of the default text mode. Of course we only scratched the surface of all the capabilities of the video adapter on Commander X16 but this should help us get through the rest of the code fairly quickly.

Above three snake drawing routines only use what we learned about drawing directly into screen memory.
The one that starts at 4000 draws three characters, the head of the snake is drawn at the current position (diamond character is used with a screen code 90) in line 4010. Then the previous head is overwritten with the snake body from saved array and last the blank space overdraws the last character or the tail of the snake.
Routine starting in line 5000 draws the whole body of snake from tail (T%) to head (H%) using characters from the array C%. This is called at the beginning of each level only.
Routine at line 5500 is very similar but instead of taking the character information from the array it simply draws Xs (screen code 24) to represent the dead snake.
Now we can tackle the rest of drawing procedures. Let’s firs look at the Clear Screen which is responsible for preparing the playing field.

Line 6010 sets everything up. Just in case we were not in the default screen mode (80 x 60 character mode) we set it with command SCREEN 0 then we set foreground color for PRINT statements to Yellow and then we clear the screen using CLS command. These codes are adopted from Commodore 64 and there are plenty of resources on the internet that describe them, just search for PETSCII. Note, however that codes used in PRINT command are different than screen codes that we VPOKE directly into video memory. Commander X16 Basic interpreter takes care of translating them when printing to screen.
Lines 6020-6030 draw the horizontal lines at top and bottom. Lines 6040-6050 draw vertical lines and finally 6060 and 6040 draw the special corner characters in top right corner and bottom left.
After that we again jump to the top left corner with control code 19 and prepare HUD with labels for Score, Time and Level. In line 6120 we use VPOKE to change the color of those elements.
At the end in lines 6130 to 6150 we draw the initial score and level. We can’t hardcode that because this Clear Screen routine is used for starting of all levels so the score is not always 0 and level is not always 1 but whatever it after previous level.
To make game user friendly we need to add some messages and notifications to inform player of what is happening. Follow four routines that display different messages at different stages of game: to get ready at the start of level, notify the player that he/she ran out of time, that snake hit the wall or bit itself and of course the dreaded Game Over message.

All four routines have many repetitive parts so we could write one single procedure with different messages and save some memory but since game is so simple we don’t really need to worry about that and can make it as easy readable as possible.
All four procedures start with jumping to the top left corner of the screen using PRINT CHR$(19) and then move cursor down bunch of times to have messages centered in the middle of the screen. We do that with cursor down screen code printed. We can use that using CHR$(17) in hexadecimal notation that $11, that is why the source code above shows \X11. On Commander X16 screen after copy/paste or loading the program you will see inverted Q instead. It is all the same character, trust me.
Next step is center the text horizontally by using TAB function, which simply moves cursor to the right by the number of characters in the parameter. Then we select the color using another display code and print out the message.
There is one small trick used in Get Ready message. After displaying the message we wait for a second in line 6560 empty loop to 5000 and then we delete the message before returning to main program and starting the game loop. We do that by overwriting the message with blank spaces. In order to that we have to move cursor up again. We could do that by starting at the top again but I chose to use cursor up code 145 ($91 hex) and on Commander X16 screen you will see inverted circle symbol.
And finally we have to look at how the first thing we see when starting the game is displayed – the Title screen.

This actually took quite a bit of time to format because I didn’t want to use any external tools for this first tutorial so it was done by hand. But the concept is very simple. The only trick used is using inverted characters so I could use blank space for colored boxes. In line 9005 we set the color to Yellow, clear the screen and move one line down.
Then from line 9010 to 9395 we use two lines of code per line displayed. First one is always tab shift by 10 characters so the text is centered and then CHR$(18) to turn on inverted character set. Then we just draw, cursor right (code 29 or $1D hex showing as inverted square bracket on Commander X16 screen) for blanks and actual blank spaces for full, yellow blocks.
Then at the end in lines 9400-9530 we just print out instructions and other information.

That is all for a very simple game that still has all the necessary components to make it look like a user friendly complete game. It can also be great starting point for many experiments and improvements.

Practice/Improvements

There is plenty of interesting challenges to improve the included source code and learn by doing it. Some ideas
  • change it to run in 40 x 30 character mode
  • change controls to allow all four directions
  • introduce additional targets to hearts and change scoring for each
  • have more than one target on screen at any time
  • add additional obstacles for more diverse and different levels
  • use Joystick to control the snake
  • create your own character set for more interesting graphics
  • replace arrays with some other way to store data (hint PEEK/POKE directly to memory)
  • etc.

Download Source Code


Comments

Popular posts from this blog

Commander X16 Premium Keyboard