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.
Complete playthrough video, with win
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.
The complete code is shown below with inline comments.
# 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.
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.
To squeeze the game down to fit the 120 character line limit I used the following tricks.
MOD31to wrap the player position, rather than comparisons.
DIVto calculate the move for the agents.
- The &8000 address was stored in a variable
osaving 4 chars each, or 2 for
- Specific ordering of each loop block (originally the draw order was different).
- Statements were folded back into lines using
:, including breaking
ELSEblocks across lines. SAM BASIC doesn't care about line grouping.
EXIT IF 1is redundant, we could just use
RUN, but the earlier
EXIT IF bx=x AND by=ywould become
IF bx=x AND by=y: RUN: END IF(+8 chars) overflowing that line.
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.