History of computers and games
History of computers and games
The contest is international, however since 2019 I am still the only Ukrainian participant. I took the 1st (SCHAU-256) and 3rd (PUR-120) places in 2021, and the 2nd place (PUR-256) in 2022, showing talent and skills in this very specific area of computer programming. Now I want to share some techniques of 10-liner game design. I honestly hope this article will inspire the reader to write your first BASIC tenliner game and participate in the next year's contest.
Maybe you are a huge fan of some retro computer, say Sinclair ZX81. But this does not mean it is a good choice for your 10-liner game, unless your goal is participating but not winning. For instance, Microsoft BASIC in Mattel Aquarius allows only 72 characters per line. This is one missing line in PUR-80, as 72×10=80×9. There were many submissions for this platform, but none win for an obvious reason. Some dialects, such as Vilnus BASIC in BK0010-01, disallow multiple statements in a line, making programmer's task extremely hard.
Some platforms have features useful for crafting a BASIC game. Despite the mentioned line length limitation, Mattel Aquarius offer a character set containing game characters, walls, explosions, etc. To a lesser extent this applies to Commodore PETSCII character set and some other platforms. So one can PRINT such characters or POKE them directly to video memory, creating nice game graphics.
Sometimes platform does not define pseudographic symbols, but user can reprogram certain glyphs. This adds even more flexibility that does not come for free — new character definitions (often in DATA statements) occupy program space. ZX Spectrum offers UDG (User Defined Graphics) for that purpose. There are platforms with rich features, such as MSX. You can define hardware sprites in the manner similar to UDG characters, but position them with pixel accuracy (PUT SPRITE), and move them by specifying new coordinates without any need in restoring background in the previous position.
Last, but not least, is a performance of specified platform. Some computers and BASIC dialects are slower than others, and certain algorithms are just not suitable for implementation on a retro platform.
Below we will refer to Microsoft interpreters as MS BASIC (including MSX and PC), and to Sinclair interpreters as ZX BASIC. Some examples are given for both, since Bill Gates and Steve Vickers had done things in a very different manner.
Some platforms forcefully dictate the category — if BASIC line cannot physically exceed 80 characters, there is no good reason to submit your game to PUR-256. Otherwise it is your own decision. Some past years submissions were more or less simplified versions of classic games like Arkanoid, Tetris, or Soco-Ban. So if you have no better idea, just try to remake something from the golden era of videogaming — but note that reviewers will always compare your implementation with the original.
You can rethink the existing plot in a completely new way. My recent NLAW game was inspired by… Tapper (1983). Instead of serving drunkards by beer mugs, you "feed" coming russian tanks by shoulder-fired antitank missiles.
With great imagination, you can try to invent your own plot. But do not forget the main rule: your game should be playable at first.
Always formulate maximum, intermediate, and minimum goals. For example, the ability of enemy tanks to shoot was not implemented in NLAW, as no code space left for it. But it was optional by design (as a maximum goal). On the other side, game was released with rather sophisticated graphics. Simple graphics requires less space, and it was the worst scenario.
Eliminate space characters where possible. For Microsoft BASIC, there is no difference between
1 FOR I=1 TO 10
and
1FORI=1TO10
In some dialects, you can also omit zeroes in DATA statements:
DATA 1,0,2,0,0,3
is equal to
DATA1,,2,,,3
Numbering lines from 0 gives you one extra character, since the last line is then 9 instead of 10. Sometimes this cheap trick will save your project.
By the way, DATA statements are allowed everywhere. If there are completed lines not reaching category limit, you can put DATA there:
1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA1,2,3,4,5
2 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA6,7
3 xxxxxxxxxxxxxxxxxxxxxxxxxxxx:DATA8,9,Hello,World
where xx…xx is some meaningful BASIC code.
Also, DATA/READ can eliminate assignments, further squeezing your ZX BASIC code:
1 LET a=16384: LET x=128: LET y=96: LET u=32: LET v=0: LET w=80
can be written as
1 READ a,x,y,u,v,w: DATA 16384,128,96,32,0,80
Some dialects, such as Steve Wozniak's Integer BASIC for Apple II, operate solely with integer numbers. Others, such as early Microsoft interpreters, always deal with floating point.
MSX BASIC and similar dialects offer both integer and floating point. Adding a % suffix to variable name makes it integer, and code operating with integer variables will work much faster, what is very useful for game programming.
But in a 10-liner every character counts, and % after each variable will bring us closer to the limit. Fortunately, there is a solution:
DEFINT A-Z
declares all variables as integer. You can declare a subset, e.g. names starting with C, I, J, and K:
DEFINT C,I-K
If there are many string variables, the same approach can be applied to eliminate excessive $ suffixes:
DEFSTR A-D,S
declares string variables those names are starting with A, B, C, D, and S.
The most typical problem in a game is to control some object using the keyboard. Consider single dimension, where x is a horizontal coordinate, and the valid range is between 0 and 199. Our BASIC does not support the ELSE clause. Keys are [o] to move left, and [p] to move right:
0 LET x=99
1 REM Draw sprite
2 LET k$=INKEY$ : IF k$="" THEN GOTO 2
3 IF k$="o" AND x>0 THEN LET x=x-1
4 IF k$="p" AND x<199 THEN LET x=x+1
5 GOTO 1
We definitely need more compact way to express things done in lines 3 and 4.
In ZX BASIC, the result of logical expression, such as k$="o" AND x>0, is either 1 (true) or 0 (false). So we can eliminate lines 4-5, and rewrite line 3 as:
3 LET x=x-(k$="o" AND x>0)+(k$="p" AND x<199) : GOTO 1
How this works?
[o] and can move left: x = x-1+0 = x-1
[p] and can move right: x = x-0+1 = x+1
neither: x = x-0+0 = x
In MS BASIC, the result of logical expression is either -1 (true) or 0 (false), so we just have to change signs:
3 x=x+(k$="o" AND x>0)-(k$="p" AND x<199) : GOTO 1
To move objects with given step (for instance, 8), just multiply the corresponding logical expression:
3 x=x+8*(k$="o" AND x>0)-8*(k$="p" AND x<199) : GOTO 1
Here we will talk about conjunction and disjunction. Despite the section title, results of these operations are not boolean, and differ between dialects. Consider the test program:
1 PRINT "1. "; 2>1 AND 13
2 PRINT "2. "; 2<1 AND 13
3 PRINT "3. "; 2>1 OR 13
4 PRINT "4. "; 2<1 OR 13
5 PRINT "5. "; 13 AND 2>1
6 PRINT "6. "; 13 AND 2<1
7 PRINT "7. "; 13 OR 2>1
8 PRINT "8. "; 13 OR 2<1
that will produce the following results:
+----+---------+
|Test| BASIC |
| +----+----+
| No.| MS | ZX |
+----+----+----+
| 1 | 13 | 1 |
| 2 | 0 | 0 |
| 3 | -1 | 1 |
| 4 | 13 | 1 |
| 5 | 13 | 13 |
| 6 | 0 | 0 |
| 7 | -1 | 1 |
| 8 | 13 | 13 |
+----+----+----+
The most useful result is shown in tests 5 and 6. In both dialects,
IF e THEN LET x=v ELSE LET x=0
can be written as
LET x=v AND e
where e is a boolean expression, and v is any integer number. If v is floating-point, it also works for ZX BASIC, while MS BASIC leaves only the integer part (as logical operations there are essentially bitwise).
Other expressions are platform-dependent and less useful, but still applicable for specific tasks, saving some program space.
The drawback of most microcomputer BASICs is the lack of ENDIF statement. THEN and ELSE (where available) clauses are in force until the end of line. This makes expressing your ideas in ten lines much harder.
0 REM This is just a dream
1 INPUT x: IF x > 5 THEN PRINT "Here": ENDIF: PRINT "There"
Some tricks to overcome this limitation were already presented above. But there is a way to write the explicit IF ... ENDIF: just use the FOR ... NEXT statement.
0 REM This is reality (ZX BASIC)
1 INPUT x: FOR i=1 TO x>5: PRINT "Here": NEXT i: PRINT "There"
As we know, x>5 gives 1 for true condition, so the loop will be executed once. For false condition it transforms into FOR i=1 TO 0, so the loop body will be skipped.
For MS BASIC, x>5 gives -1 for true condition, so the we can write it as:
0 REM Is this still just a dream? (MS BASIC)
1 INPUT x: FOR i=x>5 TO -1: PRINT "Here": NEXT: PRINT "There"
Unfortunately, Bill Gates decided to execute the loop body before checking its exit condition, and the trick does not work on early Microsoft interpreters, including MSX BASIC. It does work on IBM PC, though.
An interesting feature of ZX BASIC is that line number can be taken from the variable or expression:
GO TO 7*(k$="")
is a valid statement. It jumps to line 7 if k$ is an empty string, and to line 0 otherwise. Note that line number must be integer. The same approach works with GO SUB.
In other dialects the similar effect can be achieved using the ON ... GOTO and ON ... GOSUB statements.
The DEF FN statement, present in most dialects, can be used to compress the code. If the same parametrizable expression is used many times in your BASIC game, define it as a function. For instance, the keyboard navigation code (MS BASIC) from the imaginable two-player game
1k$=INKEY$
2u=u+(k$="a"ANDu>28)-(k$="d"ANDu<228):v=v+(k$="w"ANDv>28)-(k$="s"ANDv<228)
3x=x+(k$="j"ANDx>28)-(k$="l"ANDx<228):y=y+(k$="i"ANDy>28)-(k$="k"ANDy<228)
can be rewritten as
0DEFFNK(P,A$,B$)=P+(k$=A$ANDP>28)-(k$=B$ANDP<228)
1k$=INKEY$:u=FNK(u,"a","d"):v=FNK(v,"w","s"):x=FNK(x,"j","l"):y=FNK(y,"i","k")
saving 80 characters!
Symmetry surrounds us. People, animals, plants, almost everything on the Earth and outside is symmetrical. This is also true for many artificial objects, such as cars or planes.
Mirroring is a kind of symmetry. An astronaut turned left is a mirrored version of himself turned right. A racing car moving up the screen (forward) can be turned 180 degrees to create the vehicle moving in opposite direction.
You can use this feature for game size optimization. Consider ball sprite definition in MSX BASIC:
0 SCREEN2:FORI=1TO8:READX:A$=A$+CHR$(X):NEXT:SPRITE$(0)=A$
1 DATA&B00111100
2 DATA&B01111110
3 DATA&B11111111
4 DATA&B11111111
5 DATA&B11111111
6 DATA&B11111111
7 DATA&B01111110
8 DATA&B00111100
Using the vertical symmetry, it transforms into:
0 SCREEN2:FORI=1TO4:READX:A$=CHR$(X)+A$+CHR$(X):NEXT:SPRITE$(0)=A$
1 DATA&B11111111
2 DATA&B11111111
3 DATA&B01111110
4 DATA&B00111100
In a similar way, we can create jet fighter directed up and down from single pattern:
0 FORI=1TO8:READX:A$=A$+CHR$(X):B$=CHR$(X)+B$:NEXT:SPRITE$(0)=A$:SPRITE$(1)=B$
1 DATA&B00011000
2 DATA&B00011000
3 DATA&B00111100
4 DATA&B01111110
5 DATA&B11111111
6 DATA&B00011000
7 DATA&B00111100
8 DATA&B01111110
The &B notation in DATA statements is used for illustrative purposes, decimal numbers give more compact representation.
Like in Silicon Valley series, compression algorithms can magically transform something big into a little. The only issue is that decompressor occupies program space, so one needs the perfect balance between decompressor complexity and compression ratio. There is no universal recipe, but RLE algorithms often give good results.
Consider the tank cannon sprite from my NLAW game:
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
255 ■■■■■■■■■■■□■■■■ 239
255 ■■■■■■■■■■■□■■■■ 239
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
0 □□□□□□□□□□□□□□□□ 0
It seems perfect for RLE encoding: 4×0, 2×255, 14×0, 2×239, 10×0. We will
encode data as:
256 * (count - 1) + value
giving DATA768,511,3328,495,2304. Just 25 characters to define the 32×32 sprite.
It is clear that many sprites differ only in details, for instance:
□□□□□□□■■■□□□□□□ □□□□□□□■■■□□□□□□
□□□□□□■■■■■□□□□□ □□□□□□■■■■■□□□□□
□□□□□□■□□□■□□□□□ □□□□□□■□□□■□□□□□
□□□□□□■□□□■□■□□□ □□□□□□■□□□■□■□□□
□□□□□■■■□■■■□□□□ □□□□□■■■□■■■□□□□
□□□□■■■■■■■■■□□□ □□□□■■■■■■■■■□□□
□□□■■■■■■■■■■■□□ □□□■■■■■■■■■■■□□
□□□■■□■■■■■■■■□□ □□□■■■■■■■■□■■□□
□□□□□□■■■■■□■■□□ □□□■■□■■■■■□□□□□
□□□□□□■□■□■□■■□□ □□□■■□■□■□■□□□□□
□□□□□□■■■■■■□□□□ □□□□□■■■■■■□□□□□
□□□□□■■■■■■■□□□□ □□□□□■■■■■■■□□□□
□□□□□■■■□■■■■□□□ □□□□■■■■□■■■□□□□
□□□□□■■■□□■■■□□□ □□□□■■■□□■■■□□□□
□□□□■■■□□□□□□□□□ □□□□□□□□□□■■■□□□
□□□□■■■□□□□□□□□□ □□□□□□□□□□■■■□□□
If some bytes match to the corresponding ones in the previous sprite, we encode data as:
-(count - 1)
giving -6 for 8×7 pixels fragment at the top left corner.
With the approach described above I saved about two lines of code in the award-winning NLAW game.
A very different technique was used to define cowboy graphics in my Wild West themed game. Image was described by scanlines, where two values specify start and end coordinates. Negative start prevents prevents the transition to the next scan line (absolute value is used). Zero end is a stop mark.
It is hard to squeeze many levels into ten lines, if each level presents different maze or terrain. Often it is possible to pack levels into bitmaps like sprites (e.g., air=&B00, water=&B01, ladder=&B10, brick=&B11). Compression techniques described above are also applicable.
The completely different approach is procedural generation. It is rarely used in BASIC, as algorithms are generally slow. However, procedural generation of 5 levels was implemented in my game Escape! for Mattel Aquarius.
Writing a BASIC tenliner game, like playing chess or solving puzzles, is an interesting exercise for your brain. In the world where program code and data is measured in megabytes and gigabytes, it is a way to show how much can be achieved with so little. Some submissions to the contest made in past years were really exciting. Only a few programming tricks were explained in this article, and you are welcome to discover something new. So why do not refresh your BASIC knowledge (or learn this simple yet powerful language if you are a newcomer), and then try to express your idea in just 10 lines?
Direct transfer to PayPal account: paybox@it8bit.club or info@leocraft.com on my name Dmitry Cherepanov
Or subscribe to Patreon and get more.
This site uses cookies, both its own and from third parties. By using this site, you consent to the use of cookies
I agree