invent SAM Coupé

Remaking the classic Atari game ET in 10 lines of SAM Coupé 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)

After finishing my first attempt Snake in 80 char lines, I wanted to have a go at something a little more complex under the PUR-120 category. I chose a remake of the Atari "Classic" ET: the Extra Terrestrial.

Gameplay video Complete playthrough video, with win

Playing

You control ET using the keys Q, A, O & P. The objective is to collect all the bits of phone and then escape in the rocket. Bits of phone are hidden in pits on the map, which you need to explore. To explore a pit, move over it and press SPACE. Explored pits are cleared from the map.

Trying to stop you are 3 agents which follow you around. They can move more quickly over diagonals and will get more desperate as you collect more pieces of the phone, getting faster. Once you have 5 bits of phone the rocket will appear, and you must race to get there before the agents catch you.

The game is hard, but completable (see the included GIF). The trick is to choose carefully the order of the pits you clear and use the agents behaviour on diagonals to herd them out of the way.

You can download a disk image containing Snake and ET from here. To play, you can use the SimCoupe emulator. Insert disk image with File>Open. Load & Run with LOAD "et" 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
#      p: number of parts collected
#      o: holds memory address &8000 (start of pits x, y)
#      b: holds o+10 (start of agents x, y)
#      c: memory location of user defined graphic 150
LET o=&8000,c=UDG CHR$ 150,b=o+10,x=10,y=10,p=0:

# Poke the pit locations (10 bytes) and agent locations (6 bytes) to memory
POKE o,5,5,15,5,26,5,11,15,19,15,4,4,18,6,10,7:

# Poke the custom character graphics to memory.
POKE c,0,124,124,96,50,20,124,126       # ET character
POKE c+8,0,60,126,255,255,126,60,0      # hole
POKE c+16,24,60,60,60,126,60,102        # rocket
POKE c+24,24,60,24,27,62,24,24,24       # agent
POKE c+32,112,120,24,24,88,56,24,60     # telephone / space dildo

# Set the background colour to a calm green.
PALETTE 0, 65

# Built in SAM Coupe sound effect.
ZAP

# Main loop
DO
    LET i$=INKEY$

    # Clear the players current location.
    PRINT AT y,x;" "
    IF i$="q":LET y=y-1: ELSE IF i$="a": LET y=y+1: ELSE IF i$="o": LET x=x-1: ELSE IF i$="p": LET x=x+1: END IF

    # Constrain positions to map.
    LET x=x MOD 31
    LET y=y MOD 18

    # Draw the new location.
    PRINT AT y,x; PEN 7;CHR$ 150

    # Handle agents.
    FOR k=0 TO 2

        # Get this agents location from memory.
        LET bx=PEEK (b+k*2), by=PEEK (b+k*2+1)

        # Random number 0-7, if less than the number of pieces move. As the player collects
        # more pieces, this evaluates to TRUE more frequently.
        IF RND(7)<=p
            PRINT AT by,bx;" "

            # Calculate diff to player, as a -1..+1 delta, update.
            LET xd=(x-bx), bx=bx+xd DIV ABS xd: LET yd=(y-by), by=by+yd DIV ABS yd

            # Put the new location back in memory.
            POKE (b+k*2),bx,by
        END IF

        # Draw the agent in black.
        PEN 8: PRINT AT by,bx;CHR$ 153

        # If the agent has hit the player, exit: game over.
        EXIT IF bx=x AND by=y

    NEXT k

    # Handle the holes.
    FOR k=0 TO 4

        # Get hole's x & y location from memory.
        LET hx=PEEK (o+k*2), hy=PEEK (o+k*2+1)

        # hx is 0 if the hole has been explored.
        IF hx
            # Draw the pit (PEN will still be 8 from the agents).
            PRINT AT hy,hx; CHR$ 151

            # h=(x=hx AND y=hy) means h=TRUE if the player is over this hole.
            LET h=x=hx AND y=hy

            # If the player is over the hole and pressing Space
            IF i$=" " AND h

                # Erase the hole's coords (we could just wipe the x)
                POKE (o+k*2), 0,0

                # Increase the parts count and make a ZAP noise.
                LET p=p+1: ZAP

                # Draw the space dildo.
                PRINT AT 18,p; PEN 11;CHR$ 154:

            END IF

        END IF
    NEXT k

    # If the players got 5 parts.
    IF p=5
        # Draw the rocket.
        PEN 10: PRINT AT 10,20;CHR$ 152

        # If the player is standing on the rocket.
        IF x=20 AND y=10

            # Draw a rocket launch.
            FOR ry=1 TO 10
                # Draw rocket (red), backspace, line feed, * (yellow)
                PRINT AT 10-ry,20;PEN 10;CHR$ 152;CHR$ 8;CHR$ 10;PEN 14;"*"

                # Sound effect
                POW
            NEXT ry

            # Exit the game, won!
            EXIT IF 1

        END IF
    END IF
LOOP

# Both EXIT IF calls end here, Restart
RUN

The sprites are drawn using User Defined Graphics the SAM's custom character graphics. Each character is 8x8 monochrome (1 bpp) and can be poked to memory directly as a series of 8, 1 byte values. The expression UDG CHR$ 150 returns the memory address of the character in code position 150. Poking 8 bytes to this address will replace that character.

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.

PUR 120

To squeeze the game down to fit the 120 character line limit I used the following tricks.

  1. Use MOD 18 and MOD31 to wrap the player position, rather than comparisons.
  2. Use MOD, ABS and DIV to calculate the move for the agents.
  3. The &8000 address was stored in a variable o saving 4 chars each, or 2 for o+1, o+2.
  4. Specific ordering of each loop block (originally the draw order was different).
  5. Statements were folded back into lines using :, including breaking IF and ELSE blocks across lines. SAM BASIC doesn't care about line grouping.
  6. The EXIT IF 1 is redundant, we could just use RUN, but the earlier EXIT IF bx=x AND by=y would become IF bx=x AND by=y: RUN: END IF (+8 chars) overflowing that line.
basic
1LET o=&8000,c=UDG CHR$150,b=o+10,x=10,y=10,p=0:POKE o,5,5,15,5,26,5,11,15,19,15,4,4,18,6,10,7:PALETTE 0, 65:ZAP
2POKE c,0,124,124,96,50,20,124,126:POKE c+8,0,60,126,255,255,126,60,0:POKE c+16,24,60,60,60,126,60,102
3POKE c+24,24,60,24,27,62,24,24,24:POKE c+32,112,120,24,24,88,56,24,60:DO:LET i$=INKEY$:PRINT AT y,x;" ":IF i$="q"
4LET y=y-1:ELSE IF i$="a":LET y=y+1:ELSE IF i$="o":LET x=x-1:ELSE IF i$="p":LET x=x+1:END IF:LET x=x MOD 31
5LET y=y MOD 18:PRINT AT y,x; PEN 7;CHR$150:FOR k=0 TO 2:LET bx=PEEK (b+k*2), by=PEEK (b+k*2+1):IF RND(7)<=p
6PRINT AT by,bx;" ":LET xd=(x-bx),bx=bx+xd DIV ABS xd:LET yd=(y-by),by=by+yd DIV ABS yd:POKE (b+k*2),bx,by:END IF
7PEN 8:PRINT AT by,bx;CHR$153:EXIT IF bx=x AND by=y:NEXT k:FOR k=0 TO 4:LET hx=PEEK (o+k*2), hy=PEEK (o+k*2+1)
8IF hx:PRINT AT hy,hx; CHR$151:LET h=x=hx AND y=hy:IF i$=" " AND h:POKE (o+k*2), 0,0:LET p=p+1:ZAP
9PRINT AT 18,p; PEN 11;CHR$154:END IF:END IF:NEXT k:IF p=5:PEN 10:PRINT AT 10,20;CHR$152:IF x=20 AND y=10
10FOR ry=1 TO 10:PRINT AT 10-ry,20;PEN 10;CHR$152;CHR$8;CHR$10;PEN 14;"*":POW:NEXT ry:EXIT IF 1:END IF:END IF:LOOP:RUN

I didn't fully understand the 120 line limit and the possible packing with SAM BASIC until the game was "finished" (and the above can actually be packed more e.g. PEN 14 can be entered as PEN14 for example). But the longest line is now line 10 with 119 chars.

The game is available on an disk image here.

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.