Saturday, 4 July 2015

BASIC games in Python - 1982 returns (Part 2)

Welcome to part 2...

We're going to break down an old 1k ZX81 game written in BASIC in 1982.  This code is a scan I made from the first-ever book I bought...  "34 Amazing Games for the 1K ZX81" by Alastair Gourlay.  It taught me a load of techniques that would have taken a long time to pick up on my own, such as reading keys and drawing characters moving on screen.

In the books foreword, written by Tim Hartnell (a name well known in the 80's when it came to programming books), it's pretty clear that the aim of the book is more then just a collection of games to play...

You'll probably want to enter the programs just as they are the first time you read through the book.  We'd like you to adapt them, improve and modify them to put your own stamp on them. And we're sure the programming tricks Alastair has used to compress the programs into 1K can be used by you in other programs.

This book is much more then just a collection of games. It is a guidebook to show you how to make the most of your 1K ZX81

The game - DROPOUT, catching coins

Lets take a look at what's going on here.

This game is, like most of that era (and for that little memory) very simple.  The player gets the chance to catch a coin that drops from the top of the screen (each time positioned randomly across the screen) ten times.  The aim of the game is to get the most catches out of 10.

Its simple, but when you compare the game play to something like the runner game concept that is all over the mobile market where the aim is to just collect as many items while avoiding the obstacles, the concept of simplicity is really no different.  The appeal comes in the whole 'one more go to get a better score' of what is a very basic game play model.

Breaking down the code into blocks, there are 4 things that are happening in here.

(A) We initialise the game by resetting the score, and setting the player starting position.

(B) This is followed by the game in not one, but two loops...  The blue loop (game loop (a)) runs the game 10 times - or in the game, this is the 10 coins that drop down and the player has to catch.

(C) The inner white loop (game loop (b)) is the main game - a coin falls down and the player has to get underneath to catch it.  Once the coin reaches the bottom, the blue loop repeats this game loop until 10 coins have been dropped.

(D) The last section of the game prints the players score - how many coins were caught out of the 10 that dropped, then waits for a key to be pressed before starting a new game.

Onto the code...

So what is happening here... What does all that BASIC code do, and how does this game work?  I'll break down the code into small snippets and explain what each is doing.  Once we've got the whole thing broken down, we'll be ready to jump forward to modern day python and re-create this 33 year old game in mear seconds... Eh, maybe more like between 10-20 minutes...

Is BASIC really basic?  Sure is...

T is a variable that is going to keep track of our score, and P is the horizontal position of our player on screen.  In here, we're setting both to a starting value of 0.  But in line 20, why not just type LET T=0 rather the use LET T=P?  Code takes up precious memory in the computer.  Because of the lack of it (1K), using a variable to set another saves memory (when you've only got 1024 every byte counts).

Memory?  Limits? What?

Today, this level of "optimisation" doesn't really make much sense I'm sure - after all, machines have gigabytes of memory.

As far as the technical explanation goes, a numeric value like "0" is stored in the computer as a binary representation based on its value, and the number of digits (ie. the text characters) entered...  This could be up to 6-8 bytes of memory.

Referencing another variable (P) means no need to store a value - instead just storing the reference to the P variable which takes less memory.  Yes - the 1980's were a very primitive time for computing, and we're talking about things here that you will mostly never-ever have to even think about these days.

Alastair Gourlay, the author of this book, has a section at the back of it that lists a LOT of optimisation tips such as this.  Valuable sage advice for that time!

(Note that the BASIC commands (like LET) were stored as a single byte - a numeric value that represented the command - rather then the 3 characters of the command.  The Sinclair BASIC language had a keyword mode you would use to enter these (usually the first thing BASIC would do, only allowing numbers to be entered first), where all the available program commands were accessed through a single key.  That's why the keyboard of these machines appeared to have a lot of commands and symbols printed on them)

Lets continue...

The game is all about catching falling coins.  Its really a game of getting "best out of 10", and this loop here repeats the game 10 times.

When we start each round, we start by clearing the screen using CLS (CLear Screen)

This line prints the score stored in T into a character location of the screen.  The command AT was followed by the vertical(row) and horizontal(column) positions.  In this case, 12 characters down, and 0 characters (the left side of the screen) across.

R is a random value between 0 and 17.  As with todays languages, INT forces the value to be a whole number and not a floating point number.  And as this game is all about catching one coin at a time, the coin falling down is done by looping from top to bottom (10 characters is how far a coin will travel).  In a way, this is a game-loop within a game loop.

The first line inside this Y loop is to draw a coin (the letter "O") on screen, at Y (vertically - starting at the top) and R (our random location across the screen)

Letter O?  Where's the graphics?!

In the ZX81, there really were almost no graphics capabilities other then some custom graphical characters and the ability to plot a chunky block to a location on screen (something like 64 x 44 blocks (what they called "pixels" but at a (very) low-resolution)). Drawing blocks one at a time was SLOW - so this is why so many games you would use text for their graphics elements.  That is why the letter O is used to represent a coin.

Nuff said, what's next...

N is a new variable that will be used to work out the new position of our player on screen, alongside P which stores the previous location.  Sounds confusing perhaps, but once we get further down in the code, it will probably start to make more sense.  Likewise, instead of using a +/- value here, instead we're relying on the True/False value (which is of course, 1 or 0) of a key check (the command INKEY$ checks if a key has been pressed).  Without using typed-in numbers in our code, we're saving a few bytes of precious memory...

Run me through this...  Hows that work?

Moving the player left and right by one character at a time just means adding or subtracting 1 from its horizontal value (which is currently being stored as N). Only one key can be pressed at a time, so the maths are fairly simple.

If 4 pressed, move P + (1 (True)) - (0 (False)).  That add's 1 to our position
If 1 pressed, move P + (0 (false)) - (1 (True)).  That subtracts 1 from our position

Don't fall off the screen! 

N contains the new location of the player.  This line simply tests the location is within the playing area. If it goes outside left or right then set N back to P (previous location).

Drawing the graphics, ahem, characters...

In Sinclair BASIC, multiple print location's can be placed in one line by separating them with semi-colons (;).  Rather then 3 lines (of course, which would consume memory) we can do three things in one.

The first AT deletes the players graphic by printing 2 spaces over it.  The next AT prints the players graphic (2 graphical characters) at its new location (N) and the last AT deletes the coin from the screen at its current location, ready to be moved and redrawn.

(Unlike modern systems like pygame where all the drawing is done on a surface in memory before its updated to the screen, all screen printing and drawing is done directly - think of the screen as being a "live update".  We delete the players graphic first by printing 2 spaces to blank the previous one out.  We then quickly print the player at the new location.  After that - as we've already seen the coin printed a few lines above - now we have to delete it from screen as we're going to move it and then print it at a new location.

If we don't delete it, we'll get a line of the letter "O" running down the screen. This is why you'll often see old BASIC games have fairly flickery graphics - even more flickery if the machine is very slow).

Update the players previous position

We'll copy the N location into P (to make it the previous location - remembering that N will of course be recalculated again to move it if a key was pressed)

Make the coin fall...

And we jump back and repeat our Y loop.  The loop essentially draws the coin coming down the screen until it reaches the bottom.  While this happens, the player can move left and right to position it underneath to catch the coin.

Did I win the game?  Did I, huh, did I???

Once the Y loop has completed, the code continues...  This is another of those ways to save bytes by using variables and a little math to add 1 to the score if the character was underneath the coin.  Like the INKEY$ calculation used for moving the character, we're again using a boolean (true/false) to add a point to the score.  P is of course our position on screen for the player.  R is the horizontal location of the coin...  Because the player graphic is 2 characters wide, we simply need check whether the coin was lined up with the first or second character to score a point (ie. +1 (if it was True))

We now roll back up to our next coin drop.  And it all happens again 10 times...

Lets play again...

Once we're finished the loop, we print the players score out of 10.  PAUSE 4E4 makes the code pause and wait for a key press, and RUN just executes the BASIC program again (RUN is of course the command for running a program)

Now you're a BASIC wizz-kid!

Sure - the games simple - but for 1982 it was a serious learning exercise and lots of fun (given there was nothing else to compare it to). If you're already savvy with python programming then you have probably already re-written that game in your head without much effort.

In part 3, I'm going to finally show you how I would re-write this in python, using of course pygame to relive the excitement of a 12 year old, some 33 years ago... 

After that, you can grab your mobile devices, play some flappy-roads, or crossy-bird games (with gameplay that is literally no more complex then something from the 80's) and journey back to 2015.

See you soon...


Post a Comment