The Basic 10 Liner contest has been running for 11 years. This year (2021) the 10th competition, and the first time I've heard of it, thanks to Dean Belfield.
The competition is simple: write a game in some BASIC language in 10 lines of code. Like the arcade games for the micro:bit the fun comes from trying to squeeze as much of a game as possible out of impossible limits.
Since many BASIC dialects allow you to stack multiple statements onto the same line (SAM BASIC is no exception) there are additional category limits on characters per line, e.g.
- Category "PUR-80": Program a game in 10 lines (max 80 characters per logical line, abbreviations are allowed).
- Category "PUR-120": Program a game in 10 lines (max 120 characters per logical line, abbreviations are allowed)
I aimed for the PUR-80 category and got there with Snake.
This isn't a notable achievement, go see some of the great games from previous years like Sim City! But for a first attempt, I'm pretty chuffed.
Playing
You control the Snake with the keys Q, A, O & P for up, down, left, right. Eating food gains you a point and extends your Snake length by 1. If you chomp your own tail you die.
@chipper:l@ I mean, it's Snake, what do you expect?
You can download a MGT disk image containing 10 line Snake.
To play, you can use the SimCoupe emulator. Insert disk image with File>Open. Load & Run with LOAD "snake80" LINE 1
. Press ESC
to break to source.
Readable code
The complete code is shown below with inline comments.
# vars x, y: current coords
# xs, ys: current x/y speed (always -1, 0 or 1)
# l: length, starts at 1
# fx, fy: x, y position of food
# die: 1 if dead
LET x=10, y=10, xs=1, ys=0, l=1, fx=0, fy=0, die=0
# Pre-fill the tail memory with the starting location.
POKE &8000,x,y,x,y
# Setup the flashing effect for the food. Alternates color 11 between palette 50 (pink) & 127 (white).
# flashing every 5/50ths of a second.
PALETTE 11, 50, 127: POKE &5a08,5
DO
# Move the head by current xs & ys.
LET x=x+xs, y=y+ys
# Wrap around on screen edges.
IF x<0: LET x=31: ELSE IF x>31: LET x=0: END IF
IF y<0: LET y=18: ELSE IF y>18: LET y=0: END IF
# Draw the head. Only the head is drawn.
PEN 12: PRINT AT PEEK &8000, PEEK &8001;"█":
# Blank out the square behind the tail.
PRINT AT PEEK (&8000+l*2), PEEK (&8000+l*2+1);" "
# If our head is at the same position as the food, eat it. Update score.
# ZAP is a built-in SAM BASIC sound effect. fx is set to zero when no food.
IF x=fx AND y=fy: ZAP : LET l=l+1: LET fx=0: PEN 15: PRINT AT 0,0; l: END IF
# Move the tail memory down 2 bytes, making way for the new head position.
POKE &8002, MEM$ (&8000 TO &8000+l*2)
# Poke current head location to the top of the tail memory.
# Poked as 2 bytes, y then x (cols by rows, because we're printing).
POKE &8000, y, x
# Collision check (using pixels) the position in front of the snake. If we
# see ourselves, we're dead.
IF POINT (x*8+4, (19-y)*9)=12: LET die=1: END IF
# If fx is zero (no food) add some randomly on the map and print it.
IF fx=0: LET fx= RND (25)+3: LET fy= RND (10)+4: PEN 11: PRINT AT fy,fx;"▟": END IF
# Scan the keyboard for input & update directions.
LET i$= INKEY$ :
IF i$="q": LET xs=0, ys=-1:
ELSE IF i$="a": LET xs=0, ys=1
ELSE IF i$="o": LET xs=-1, ys=0:
ELSE IF i$="p": LET xs=1, ys=0:
END IF
# Exit when die=1.
LOOP UNTIL die
# POW is a built-in SAM BASIC sound effect.
POW
# Restart the game.
RUN
To support developers in [[ countryRegion ]] I give a [[ localizedDiscount[couponCode] ]]% discount on all books and courses.
[[ activeDiscount.description ]] I'm giving a [[ activeDiscount.discount ]]% discount on all books and courses.
How it works
Each tick the head of the Snake moves 1 block in whichever direction it is going, by incrementing the x
& y
variables.
We PRINT
the head to the screen on each move. Since things remain where they are printed, we don't need to re-print the
tail. But we do need to keep track of where it is both for collisions and to clear up after the tail.
To do this we POKE
our current x & y positions into memory using one byte for each for simplicity sakes. On each tick
we shift 2 x length bytes from &8000
to &8002
and poke the new y & x position into &8000
and &8001
respectively.
By reading memory at &8000+2*l
and &8000+2*l+1
we can get the last segment of our current length tail and delete it
from the map by printing an empty space.
Tail is POKEd to memory, shifted right 2 bytes on each tick & the current x & y coords are POKEd to the head. The value shifted off the end is used to clear up after the tail.
Food is placed randomly on the map and we compare x & y positions of the food, to that of the Snakes head to register an "eat".
We're using two SAM-specific features here. First, is the use of the SAM palette flashing to flicker the food. By setting PALETTE 11, 30, 127
we color palette 11 (which we are using for our food PEN
) to alternate between system color 30 & 27. The POKE &5a08, 5
sets
the rate of alternating to every 5/50ths of a second to highlight the food. Second, is the use of the SAM BASIC built-in sound effects POW
and ZAP
which make pow and zap sounds, kind of. One negative is that doing this freezes execution of the BASIC program, so there is a small
freeze when picking up food. But it's not too bad.
PUR 80
To squeeze the game down to fit the 80 character line limit needed the following tricks.
- The &8000 address was stored in a variable
o
saving 4 chars each, or 2 foro+1
,o+2
. - Statements were folded back into lines using
:
, including breakingIF
andELSE
blocks across lines. SAM BASIC doesn't care about line grouping. - Re-order statements to fit, e.g.
PEN 12
is moved to theDO..
line.
I thought I'd need to drop the palette cycling effects at first, but by jiggling statements around it came in under. The final code is --
1LET x=10,y=10,xs=1,ys=0,l=1,fx=0,fy=0,o=&8000,d=0:PALETTE 11,50,127
2POKE o,x,y,x,y:POKE &5a08,5:DO:LET x=x+xs,y=y+ys:PEN 12:IF x<0:LET x=31
3ELSE IF x>31:LET x=0:END IF:IF y<0:LET y=18:ELSE IF y>18:LET y=0:END IF
4PRINT AT PEEK o,PEEK (o+1);"ď":PRINT AT PEEK (o+l*2),PEEK (o+l*2+1);" "
5IF x=fx AND y=fy:ZAP:LET l=l+1:LET fx=0:PEN 15:PRINT AT 0,0; l:END IF
6POKE o+2,MEM$(o TO o+l*2):POKE o,y,x:IF POINT(x*8+4,(19-y)*9)=12:LET d=1
7END IF:IF fx=0:LET fx=RND(25)+3:LET fy=RND(10)+4:PEN 11
8PRINT AT fy,fx;"č":END IF:LET i$=INKEY$:IF i$="q":LET xs=0,ys=-1
9ELSE IF i$="a":LET xs=0,ys=1:ELSE IF i$="o":LET xs=-1,ys=0:ELSE IF i$="p"
10LET xs=1,ys=0:END IF:LOOP UNTIL d:POW:RUN
I wasn't originally packing the BASIC as compact as it can go, and this still isn't
as small as it can go: you can even do FOR a=1to10
for example. As a result the longest
line is line 9, with 74 chars.
The game is available on an disk image here.
Bugs
When placing food there is no check to make sure it's not going placed in the tail. Since we only draw the food when first placed, this means it disappears once the tail moves over (the end-tail blanking removes it). You have to try and remember where it is.
If you're looking for a reference to SAM BASIC you can take a look at the original User Manual and the Complete Guide to SAM Basic by Graham Burtenshaw.
Continue reading the BASIC10 series with Remaking the classic Atari game ET in 10 lines of SAM Coupé BASIC