Showing posts with label Basic. Show all posts
Showing posts with label Basic. Show all posts

Wednesday, 8 January 2025

Basic Blitz: A Surprisingly Addictive VIC-20 Remake

The game Blitz was written and self-published by Simon Taylor for the unexpanded VIC-20 in 1981, then later sold to Mastertronic.

https://www.eurogamer.net/lost-and-found-blitz

I always thought it looked like a game that must have been written in Basic, but I never got around to testing that until the beginning of 2025.

So, here's my version in all its glorious 64 lines of code!


Mine seems to be based on the later Mastertronics' version, because my plane is just one graphic character instead of 2 or 3 and my buildings are multicoloured instead of just black. Multi-coloured buildings adds to the fun, given most actual buildings are grey.

Also, mine doesn't speed up during each flight; it does get faster per level while the number of buildings it generates also increases by 2. My current high score is 533. Game control is pretty simple: you just press 'v' to drop a bomb as the ship flies across the screen. Only one bomb can be dropped at a time.

Design

Enough of the gameplay, let's discuss the software design. The outline of the game is pretty simple:
  • Line 5 reserves memory for the graphics characters then calls a subroutine at line 9000 to generate them.
  • Line 7 defines a function to simplify random number generation.
  • Line 8 is a bit of debug, see later.
  • Line 9 resets the high score. So, this only happens once.
  • Line 10 starts a game with a width of 5 (so 5x2=10 buildings are generated) and a delay of 100 between each frame.
  • Lines 30 to 60 are the main loop of the game. It really is that tiny. The loop terminates when the  plane lands or hits a building. Within that the plane is drawn (by displaying it in its next position then erasing the previous position to avoid flicker).
  • Bomb handling is done in lines 45 to 50, but the explosion is handled in lines 200-300.
  • End of game is handled in lines 66 to 80 including displaying "Landed" or "Crashed", updating the high score and handling the user wanting to quit.
  • Line 99 resets the graphics characters back to the normal character set so that you can carry on editing it.
  • The subroutine at line 100 performs the equivalent of a PRINT AT.
  • The subroutine at lines 200 to 250 handle a bomb hitting a building (a random number of floors are destroyed).
  • The subroutine at lines 8000 to 8070 generate a new level based on W, the width of the cityscape.
  • The subroutine at lines 9000 to 9010 generates the graphics characters and sets the sound level to 5.
  • The data from lines 9012 to 9090 are the graphics characters themselves, in the sequence: 'blank', 'solid square', 3x building types, 2x roofs, plane, grass.
  • The subroutine from lines 9500 to 9520 wait for a key to be released, then pressed, returning the key in A$.

Graphics

Because VIC-20 graphics are weird, programmers end up with bespoke graphics routines, so it's always worth discussing them. Firstly, VIC-20 graphics are tile-based, somewhat like the Nintendo Entertainment System. Video memory contains character codes between 0 and 255, and each character code points to an 8x8 bit pattern at CharacterMemoryBaseAddress+(CharCode*8). Usefully, the base address for the character bit patterns (and the video base address too) can be set by poking 36869. That base address can be set to RAM (which gives the programmer 256 tiles to play with), ROM (which is the default and provides caps+graphics or a caps+lowercase+some graphics option) or can be made to straddle both (which gives the programmer up to 128 tiles to play with + an upper case character set). This is the case even though the user defined graphics (UDGs) have addresses below 8192 while the ROM tiles are above 32768, because of the way the 14-bit VIC-chip's address space is mapped to the VIC-20's, full 16-bit address space.


In practical, unexpanded VIC-20 applications, programmers will want to use as few UDGs as possible to maximise program space while retaining much of the conventional character patterns. In Basic Blitz we therefore set the graphics to straddle mode (value 0xf, giving a CharacterMemoryBaseAddress of 0x1c00) which means characters 0..127 are in RAM and 128..255 are in ROM.

Intuitively, you might imagine that you'd want to start using tile 0 first, but that would waste of most of the tile space, so in fact we always count the UDGs we need backwards from tile 63, because tiles 64 to 127 overlap with video memory itself by default (and are therefore unusable!). Also, because the VIC-20 ROM characters aren't in ASCII order, and amazingly enough don't include the filled-in graphics character I have to provide that. When Basic Blitz is run, it first shows the entire usable character set.


I added this as a bit of debug, because I initially wasn't sure the ROM characters would print out OK. Also, I then made it print Hello in red to test both my PRINT AT subroutine and embedded colour control codes.

Graphics characters can easily be printed, because they're the normal characters '6, '7', 8', 9', ':', ';', '<', '=', '>', '?'. Normal text can be displayed, but you have to force 'inverse' characters which is achieved by preceding each print statement with <ctrl>+9 and ending with a true character <ctrl>+0.

Colours

Colours on a VIC-20 are strangely limited. There's a block of colour attribute memory, one location for each video byte, but each one is only 4 bits, which means you can only select an INK colour for on pixels. The PAPER colour is global, defined by bits 4..7 of 36879. The VIC-20 partially gets around this by normally making characters 128 to 255 inverse characters, but also by defining bit 3 of 36879 as normal or inverse mode.

The upshot though is that with the ROM character sets you can choose a common PAPER colour with any INK, or the common PAPER colour as INK, with any INK colour as PAPER. But when you select the character set to straddle RAM and ROM, you can only choose any INK colour + the common PAPER colour.

Hence in Basic Blitz, the background is white (as that seems most useful) and I have to define a UDG just so that I can get a filled in green character for grass with a building on top.

Sound

BASIC Blitz, sound is pretty simple. The initialisation routine switches audio on to level 5 (POKE 36878, 5); and leaves it there. There are 3 voice channels, which are individually switched on if bit 7 is set. In practical terms, each voice has a range of about 2 octaves, the first one having values from 128 to 65; then the next octave from 64 to 33. Beyond 32, the frequency ratio between each note is 1.03 to 1.06, close to that of a semitone 1.059 making most note intervals unusably out of tune.

The plane makes a drone sound using the lowest pitch audio channel (address 36874) OR'd with the bottom 4 bits of the jiffy clock at PEEK(162).

The bomb uses the high octave channel (at 36876) just generating an ascending tone. If the bomb hits a building it's silenced and the noise channel with a fixed low pitch of 129. The important thing, finally is to turn off all the sounds when they're done, by poking the channels with 0.

Playing The Game

You can run this VIC-20 Javascript emulator and type in the code (if the keyboard mapping allows it):


I've found this emulator is better than the Dawson one for .prg files. Here's how to load the .prg on a desktop/laptop. First download the BasicBlitz.prg from my Google Drive. Then drag the file from wherever you downloaded it from to the emulator in the browser. It will automatically load and run!

However, it's also useful to be able to type in code directly for editing, debugging and other stuff.

The keyboard on my MacBook M4 doesn't map correctly to VIC-20 keys, because the emulator does a straight translation from character codes to VIC-20 keys rather than from key codes. This means that pressing Shift+':' gives you ';' on this emulator rather than '[' as marked on a VIC-20 keyboard.

Mostly this makes typing easier, but the VIC-20 uses a number of embedded attribute key combinations. Basic Blitz doesn't use many, here's how to type what it does use, it isn't easy!

In Chrome, you need enable console mode, by typing function key F12. Then tap on the Console tab. In Safari, you need to choose Safari:Settings... then Select the 'Advanced tab'; and click on "Show features for web developers" at the bottom. Then the "Develop" menu appears on the menu bar and you can then choose Develop:Show JavaScript Console.

So far so good. Now, you can type most of the text as normal, but whenever you need to type a special code, type pasteChar(theCode) in the console followed by Enter (e.g. pasteChar(147) for the clear screen code). Here are the codes you'll need:
  • Inverse 'R' => 18. This is for Reverse text, which ends with inverse nearly underline => 146.
  • Inverse '£' (Red) => 28.
  • Inverse '┓' (Black) => 144.
  • Inverse 'S' => 19 (this is the home code).
  • Inverse heart => 147 (this is the clear screen code).
  • Inverse up-arrow => 30 (this is green).
  • Inverse 'Q' and inverse '|'can be typed directly just using the down cursor and left cursor respectively.
  • The codes in line 8045 are more colour codes used for the buildings. They are 144 (Black), 28 (Red), 159 (Inverse filled diagonal=cyan), 156 (checkered-black character=purple), 30 (inverse up arrow = green), 31 (inverse left arrow=blue), 158 (inverse 'π' = yellow).

Conclusion

The original VIC-20 Blitz program, though derivative in its own way, is so simple it could have been written in BASIC, as this version proves. The arcane design of the VIC-20 hardware and its lousy BASIC implementation means there's a lot of subtle complexity even in a simple game. Finally, although there are many emulators for the VIC-20, both the Javascript implementations I know of have limitations and bugs which make distributing this game and/or modifying it non-trivial.




Friday, 12 April 2019

Plottastic ZX80!

A guide to plotting pixels in Basic on a ZX80!


Introduction

Both the ZX80 and ZX81 support 8x8 pixel character-only displays and contain 16 graphic characters that can be used to plot fat 4x4 pixel pixels on a two-by-two grid within each character:

These are called Battenberg graphics after the cake of the same name 😀

On a ZX81 it's easy to use these characters to plot (crude) graphics on the screen, the computer provides a PLOT x,y and UNPLOT x,y for this purpose. But with half the ROM on a ZX80, it's much harder - so I wondered, how much harder is it to plot pixels? It turns out it's pretty formidable!

Challenges


  • The ZX80 doesn't have any PLOT or UNPLOT commands.
  • The screen on a ZX80 is like that on a ZX81, when you run a program, the screen first collapses to a minimum of 25 newline characters and expands as you display text. However, on a ZX80, unlike the ZX81, you can't print anywhere on the screen as there's no PRINT AT function, this means we'll have to poke onto the screen.
  • The memory map on a ZX81 has the display immediately after the program, but on a ZX80, the display comes after the variables and the editing workspace which means that it'll move around just by creating a new variable or possibly by performing input (which is a potential problem).
  • Ideally, to convert from pixel coordinates to Battenberg graphics you'd want to map the odd and even x and y coordinates to successive bit positions to index the character.


  • But, unlike the ZX81, the character set on a ZX80 doesn't have the characters in the right order to display Battenberg characters. Instead they contain gaps; the last 8 codes are generated from the first 8 but in reverse order; and some of the first 8 are taken from what ought to be the inverse characters!

The Solution

The solution really comes in a few parts. Firstly, the easiest way to be able to map an ideal pixel value to its Battenberg character is to create a table in RAM, by putting them into a REM statement (the ZX80 has no READ and DATA commands so it's hard to put a table of data into an array, but the first REM statement in a program is at a fixed location). However, even this presents a problem, because only 8 of the graphics characters can be typed. The easiest way to handle that is to write a program which converts a different representation of the characters into the correct ones.

So, on the ZX80, first type this:
After you run it, you'll get this:
Even this was tricky; I had to use character codes above 32, since symbols such as +, -, /, ", etc get translated into keyword codes for the Basic interpreter. The above program illustrates an interesting feature of ZX80 Basic that differs from ZX81 and ZX Spectrum Basic in that AND and OR operations are actually bitwise operations rather than short-circuit boolean operations. Thus P AND 15 masks in only the bottom 4 bits.

Once we have generated the symbols we can delete lines 10 to 30.

The next step is to actually write the code to generate pixels and plot them. Once we know how, it's actually a bit simpler. Firstly, we fill out the screen with spaces (or in my case, with '.'):
This gives us 20, full lines of characters. Because it's going to be difficult to plot a pixel by reading the screen, figuring out the plotted pixels then incorporating the new pixel; I cache a single character in the variable P and its location in the variable L. All my variables a single letters to save space if you try to run it on a 1Kb ZX80. The idea is that if the new print location in L changes, we reset P back to 0, otherwise we incorporate the new pixel.

Next we start the loop and calculations for plotting, in this case a parabola. We loop X=0 to 63 and calculate Y on each loop (it's a parabola that starts at the bottom of the screen):

Finally we perform the pixel plotting and loop round.

This involves saving the previous location in K so we can compare it later; then calculating the new location based on X and Y (since each character position is 2 pixels, we need to divide X and Y by 2 and since there are 32 visible characters and an invisible NEWLINE character on every screen line we must multiply Y/2 by 33). Note, a quirk of ZX80 Basic means that multiplies happen before divides, so Y/2*33 would calculate Y/66!

The pixel bit calculation in line 50 makes use of ZX80 bitwise operators, we want to generate 1 if X=0 (so 1+(X AND 1) will generate either 1 or 2) and then we need to multiply that by the effect of the Y coordinate, which is 1 on the top line, and 4 on the bottom: 1+(Y AND 1)*3 will do that. Hence this will generate the values 1, 2, 4, 8 depending on bit 0 of X, Y.

We must POKE the location L plus an offset of 1 (because the first character of the display is a NEWLINE) and also we must add the location of the display file (it turns out that these calculations don't make the display file move around). We poke it with the pixel value we want indexed into the right character code from the REM statement. Finally we loop round X.

This code generates this graph:
It looks reasonable even though it's generated with integer arithmetic. It's the first pixel plotted graph I've ever seen on a ZX80!

More Examples

My original reason for doing this was to see if I could generate sine waves using a simple method I once found for a Jupiter Ace. Here's the program and the result:
It looks fairly convincing, especially as it's all done with integer arithmetic. Because the code generates both the sine and cosine value, it's easy to turn this into a circle drawing program which produces the following:

It looks a bit odd, that's because the algorithm doesn't quite generate sine and cosine curves. What it's really doing is computing a rotation matrix, but only one of the terms is computed for sine and cosine each time. Hence, the circle looks a bit like an oval with a slight eccentricity.

My graph drawing algorithm has one very serious limitation. Because it doesn't read the screen in order to compute a new pixel, if the graph goes over the same part of the screen twice, the second pass will muck up what was there before. Doing the correct calculations is possible using some table lookups and bitwise operations, though it would slow down the graph generation. I didn't bother, because I only wanted to generate simple graphs.

Conclusion

The ZX80 and ZX81 have very similar hardware, but a number of design decisions in the ZX80's firmware made drawing circles much harder than you might expect. With a lot of effort it is possible to generate some simple graphs 😀