invent SAM Coupé

Writing Snake in 10 lines of SAM Coupé BASIC

Simple Snake game in 10 lines of BASIC

BASIC10 Series

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.

basic
# 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
Tiny games for your BBC micro:bit.

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.

PEEK POKE 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.

  1. The &8000 address was stored in a variable o saving 4 chars each, or 2 for o+1, o+2.
  2. Statements were folded back into lines using :, including breaking IF and ELSE blocks across lines. SAM BASIC doesn't care about line grouping.
  3. Re-order statements to fit, e.g. PEN 12 is moved to the DO.. 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 --

basic
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