Copyright © 2020 by Víctor Parada
This is a little game for the 2020 NOMAM's BASIC 10-liners Contest. This program fits in the EXTREM-256 category, and it was written using FastBasic 4.4 for the 8-bits ATARI XL/XE. Development started on 2020-03-30, and it took 5+4+4 days. The final version's date is 2020-08-16.
UPDATE: It obtained the 2nd place of NNN entries in the category.
Drive to POINT Z, jumping over craters and other obstacles, shooting to destroy the big ones and UFOs.
Your mission is to arrive at POINT Z in your buggy, avoiding all the obstacles in your way. | |
You can accelerate to the right and brake to go back to the left. Note that you will win more points when you are in the middle of the screen, and less points when you are back near the border. | |
You can jump over craters, small rocks and mines, but you cannot go forward or backward while jumping. | |
You can't jump over big rocks, but you can destroy them with your short range missiles. You cannot fire missiles while jumping. | |
There are combination of obstacles, and you must do both jumping and firing in coordination. | An UFO will throw bombs to you. You can avoid them or destroy the UFO by shooting it with your vertical cannon. | If you crash into any obstacle, your buggy is destroyed and you have to start the stage again from the last checkpoint. |
When you arrive to a checkpoint, you will get an extra buggy. You can have 5 buggies at most. | |
Every stage is a bit more difficult than the previous. The first one has about 8 obstacles, but the last might have more than 20 in the same distance, so there is less space between obstacles!!! | |
The game is over when all your buggies crashed or when you reach POINT Z. Press the trigger to start again. |
In the 2020 NOMAM thread at AtariAge forum, I published a parallax demo with multi fine scrolling using FastBasic 4.3 and its brand new feature: DLI. This was in response to another post about complexities of horizontal fine scrolling in BASIC. I tried to make it look like Moon Patrol, so I added a static lunar buggy, but I got excited and added a simple animation of the wheels.
Horizontal muti fine scrolling demo
Demo in action
To build that demo, I used a custom display list with some blank zones (only BAK color register is in use) and some ANTIC mode 4 and 5 lines for the scrolling stripes. Each stripe was 1K long, that is about 25 screens of random obstacles, but for the mountains and hills there were repeating patterns, so the corresponding pointers could be reset back properly and smoothly. While the colors were managed easily by the DLI, I quickly found that glitches appeared when I set the horizontal fine scroll bit to some of the lines. It seemed to me that there is less CPU cycles for DLI when combining horizontal fine scroll bit with DLI enable bit in the same DL instruction, and a more complex DLI routine should be used. To bypass this problem, I redesigned the DL and inserted some bogus ANTIC mode 14 (graphics mode 15) lines with static data and the DLI enable bit, but without the fine scroll bit, leaving the fine scrolling lines without a DLI. This forced me to not split colors in the bottom stripe, one set of colors for the surface and another for the craters, because I wanted to add the plants and get a colorful playfield.
1st prototype with glitches
Glitches removed.
For the buggy I used simple bitmaps on player/missile graphics in single line resolution, using P0 for the wheels and cannons, and P1 for the body. The bitmaps were designed using a spreadsheet (to get the data byte values immediately) and I tried a simple 3-steps animation for the wheels.
While I was writing the post, I thought that it could be converted to a simple tenliner game, so I added the ability to move forward and backward and to jump using the joystick. I also modified the buggy bitmap and incuded a "boing" sound for the jumps.
The next day, a collision detection was included to know when the buggy crashed or fell into a crater. This was somehow tricky in order to simplify the algorithms. I changed the use of each PF color register in this way: PF0 for the irregularities of the ground and the body of the rocks, PF1 for the shadows of the rocks and craters, PF2 as a second color I later used for mines, and PF3 for an invisible object to be placed just over the craters. Even when I lost one color by defining PF3 with the same value of BAK, there was a simple routine to detect both a crash into an obstacle and a fall into a crater by checking the P0PF collision register but discarding collisions with PF0 (ground). The score was the same for all the obstacles that were jumped over (double for big rocks because the have double width), but I added a multiplier based on the buggy position in the playfield: 10 points for objects jumped at the left of the playfield, 30 points at the center and 20 for some range in the middle. I included special bitmaps for the destroyed buggy along with the explosion sound FX.
1st prototype with user control of the buggy
A buggy explosion
Even when I used too many coding space with data to initialize the graphics, I had enough space to include more features. From the demo, the terrain has always been generated randomly, but I wanted to generate it with some patterns between checkpoints, introducing different type of obstacles as the game advances, like in the arcade. That should also include an extra line at the bottom of the playfield to scroll with the letters. Another feature I wanted to include was shooting to the obstacles and to some UFOs as well as other kind of enemies as obstacles, like tanks or ships. Also, I wanted to replicate the scoreboard of the arcade, including the high score and the timeline with the course percentage.
I started with the horizontal cannon. I used a quadruple width missile M0 (8 color clocks wide) by 1 scan line. As M0 has the same color of P0, it was a black missile. Detecting a collision was simple, but it took me too much time to find a way to detect which one was the obstacle that it was hit and where it was in the scrolling ground stripe to remove it. After many tries, I decided to discard the collision register and scan the ground while the missile was moving and remove the missile when a rock was detected. Even when the big rocks could be jumped over and get score for that like any other obstacle, I assigned more than the double of the score if it was destroyed. This worked very well, except for the fact that a lag was introduced. The ground stripe no longer moved in a constant speed: it was slower than normal whenshooting, and faster than normal when jumping. Shooting while jumping was not allowed, but it was possible to jump just after firing, and the result was a strange acceleration during the play. I also had to backup the ground stripes in order to restore them to repeat the stage after a crash.
Adding a front missile and detecting collisions
Checking for objects where the missile is
By that time, the abbreviated code was using more than the available coding space for an EXTREM-256 tenliner, so it was time to optimize the code. After compressing data and reuse bytes, I could measure how much space was now available, and I had to decide which was the next feature to include that it could fit in that space. I thought that the vertical shooting should not use too much space, as it could be added to the existing firing routine, so I designed a couple of UFO types to be displayed using P/M graphics. Brainstorming this game in early stages of development, I decided to use another stripe of ANTIC 4 with different groups of UFOs, but by this development time, I had a few bytes available, and it was not enough if that stripe would use at least the same than any other horizontal fine scrolling stripe.
I selected M2 for the vertical shoot and P3 for the UFO, and at the same time, I changed the horizontal missile from M0 (black) to M3 (light blue). As soon as I tried to add one of the new UFO bitmap to the code, I found that I had no space for that. Instead, I just copied the bitmap from one of the ATASCII graphical characters, and the diamond was selected. Displaying it in single width and single line resolution result in a ship very similar to one of the arcade UFOs. But I also had to remove some of the existing features to complete the UFO routine and its bomb, which was implemented with P2 without a nice shape but a single block. While testing, I found a special situation where the bomb was dislayed as a line because a continuous drop.
Adding the UFO and the vertical shoot
"Line" bomb
The removed features were the high score and the buggy explosion, which was replaced by a just a partial wheels change on P0. I also reduced the stage length to the half to make shorter stages. I wrote a script to count the times a variable was used in the source code, and reassigned the one-letter variable names to the most used variables. I think I recovered more than 50 bytes of source code space by doing this a couple of times. I could manage to fit the full code in exactly 10 lines of BASIC program, leaving about a total of 12 bytes of free space at the end of few lines.
The game was limited to display just 26 stages, each of them trying to reach the corresponding checkpoint from A to Z, and the increasing difficulty was given by shorter plain segments between obstacles. Once the player reached that stage, it kept playing up to 99990 points, which it was the maximum displayable score I defined once to fit the scoreboard (and because FastBasic could only manage integer numbers up to 32767, so I limited it to 9999 with a static 0 in the scoreboard).
But I was not happy with the result. Even when the UFO could be killed, there was no explosion sound, no points were added to the score and the "line" bomb was was still present. Also, the gameplay was not that hard, but there was no way to get extra buggies, and it was hard to reach many checkpoints by simple mistakes. While writing comments into the code, I decided to hack the game to test which could the highest score be, an it was a few more than 99990 point, so I removed the score limit and forced a game over when POINT Z is reached. That gave me the coding space to rewrite the UFO and its bomb routine to include the explosion sound and bonus score.
Simplified scoreboard and a buggy crash
The latest changes where to recover a buggy at the end of every stage, to limit the height of the jump, disabling the posibility of jumping over the big rocks, forcing to shoot to them in coordination to jump over small rocks and craters in front or behind them, and an adjust in the stage generator, to force a constant area without obstacles at the beginning, but making harder to shot the UFO at the stage start. I couldn't fix the line missiles bug because it would require an extra flag variable and too much coding space to code the logics, so I just converted it into a rush of missiles. These simple changes made me to reorganize the code to fit the maximum line length and keep only 10 lines of code.
Rush of bombs
Unable to jump over big rocks
3 months after I finished the game, a new beta of Fastbasic was announced, and it included 2 improvements that I was expecting for: function name abbreviation and being able to omit function parentheses in certain conditions. Also, there was a change in the abbreviations of the "IF-THEN-ELSE-ENDIF" structure that it could almost save 2 bytes on every use of the "IF" statement without and "ELSE", or save just 1 if it is used. With all this changes, this game could be shrinked from 2551 bytes to 2445 bytes, saving 106 bytes for extra code.
As the max number of bytes could be 2550 bytes (plus 10 for the new-line characters), I had 115 chars to improve the game. I fixed the rush of bombs issue and allowed the UFO to appear more than once in every stage. I also speeded up a litle the mountains and hills parallax to make the game feel more fluid, which allowed me to optimize a couple of conditions and save more bytes. I splitted the end of game message in two, showing either "game over" only when no more buggies are left to continue, or "completed" when you arrive at POINT Z.
To make the game more challenging, I shortened even more the length of a stage and assigned the obstacles in a way that during the first 11 stages one or two harder obstacles are introduced every time, including some new ones, and during the remaining stages it is more evident that there is less space between obstacles to shoot or jump, and to avoid the UFO's bomb.
The final version of Patrol has a size of 2532 bytes (plus 10 EOLs), that is 18 bytes less than the 2550 maximum for 10 lines of Fastbasic code. 10 lines of source code seem to be few space for a game, but this one has 240 statemens which include more than 350 bytes of binary data for sprites, display list, playfield, color palette, etc.
Final version
Get the PATROL.ATR file and set it as drive 1 in a real Atari (or emulator). Turn the computer on and the game should start after the loading completes. A joystick in port 1 is required.
The abbreviated BASIC code is the following:
The full and expanded BASIC listing is:
|
(Moon) Patrol (c) 2020 Víctor Parada 2020-08-16 |
|
$A150-$BC20 cleaned area by GR.8 $B000 FastBasic PMBASE when in GR.0 $BC40 GR.0 screen data area dpeek(88) |
|
$A000-$A040 DL $A100-$A12F Filler $AA $A130-$A15F Filler $55 $A180-$A183 Buffer Multi HSCROL $A190-$A193 Buffer HPOS P0-P3 $A194-$A197 Buffer HPOS M0-M3 $A200-$A2FF Parallax far mountains $A300-$A3FF Parallax middle hills $A400-$A5FF Parallax ground $A600-$A7FF Parallax underground $A800-$A9FF Active parallax ground $AA00-$ABFF Active parallax underground $B000-$B1FF Charset (1st half) $B200-$B3FF Charset (2nd half, with modified 3rd quarter) $B300-$B3FF P/M M0-M3 $B400-$B4FF P/M P0 $B500-$B5FF P/M P1 $B600-$B6FF P/M P2 $B700-$B7FF P/M P3 |
graphics 8 |
Cleans 8K at top of RAM |
graphics 0 |
Sets text mode (replaced by custom DL) |
poke 82,1 |
Sets left margin |
poke 752,1 |
Disables cursor |
poke 756,$B0 |
Points to new charset (same as PMBASE) |
d=adr("{binary data}")+1 |
Data - Custom DL (45) - Charset (121) |
data cb() byte = 0,$96,$B8,$FA data c2() byte = $8E,$CE,66,$F4 data c1() byte = $96,$B8,$F4,$B8 data hs() byte = 0,0,0,0 hh=adr(hs) |
DLI vectors (BAK,PF1,PF2,HSCROL) |
w=adr("{binary data}")+1 |
More data - Mountains (8+8) - Hills (23+1) - Buggy bitmaps (53+1) - Color palette (9) - Crash bitmap (8) - Obstacles (68) |
s0=d+15 s1=d+23 s2=d+34 s3=d+37 |
Memory address of pointers to each stripe: Mountains, Hills, Ground, Underground |
move d,$A000,50 |
Installs DL |
dli set dl = hs into $D404, cb into $D01A, c1 into $D017, c2 into $D018 |
Display List Interrupt for fine scrolling and color palette |
dli dl |
Enables DLI |
mset $A100,48,$AA mset $A130,48,85 |
Fills the fillers |
move $E000,$B000,512 move d+45,$B20F,121 |
CHARSET Copies the first half to be used by scoreboard and defines bitmaps for some of the second half |
move w,$A200,16 move $A200,$A210,$F0 |
Builds the mountain pattern |
move w+8,$A300,24 move $A300,$A318,$E0 |
Builds the hill pattern (reusing some of the mountains) |
pmgraphics 1 |
Initializes P/M graphics in single line resolution |
dpoke $D008,257 |
P0 & P1 in double width |
poke $D00C,192 |
M3 in quadruple width and M2 single width |
data yy() byte = "{binary data}" |
Sequence for jump (reverse order) (string length is automatically ignored by routine) |
move w+84,704,9 |
Changes color palette |
dpoke 560,$A000 |
Enables custom DL |
do |
MAIN LOOP |
v=5 |
Lives |
k=0 |
Killed flag (forces a stage creation) |
r=0 |
Stage counter |
t=0 |
Wheels timer |
s=0 n=0 |
Score |
print "{binary data}" |
Displays a clean scoreboard |
repeat |
Game loop |
mset $A190,8,0 |
Removes all P/M from screen (in the buffer) |
mset $B300,$500,0 |
Removes P/M |
l=$A200 m=$A300 p=$A800 q=$AA00 |
Resets block scrolling pointers (corresponding to S0, S1, S2 and S3 addresses in DL) |
dpoke s2,p dpoke s3,q move d,$A000,50 |
Scrolls back to the left of the ground stripes |
poke 77,0 |
Disables attract mode |
pause 0 poke $D01E,0 |
Resets collision registers |
x=72 y=0 |
Coordinates of the buggy |
j=0 |
Resets the jumping flag |
i=0 |
Resets the game timer |
if k=0 |
Sets up a new stage? |
mset p,512,0 mset q,512,80 |
Cleans the terrain |
b=-r for a=10 to 511 |
First delay long enough to start with a clean terrain in the first screen |
if b>33-r+rand(4) and a<325 |
Distance between obstacles depends on the stage (plus a small random delta to avoid patterns) |
c=rand(17) |
Selects an obstacle |
if r<10 |
Increasing difficulty during the first stages |
c=4-r/2+rand(3+r/2+r) endif |
Introducing new obstacles each stage |
move w+101+c*4,p+a,4 |
Puts the obstacle in the ground stripe |
if c<5 |
Crater? |
move w+169+c*4,q+a,4 endif |
First 5 obstacles require the crater part in the underground |
b=0 |
Restets the distance counter |
a=a+3 else |
Skips the obstacle in the loop to avoid it being overwritten by ground |
poke p+a,64+rand(3) |
Puts a random ground bitmap when there is no obstacle |
inc b endif next |
Increase the distance counter |
move p,$A400,$400 |
Backups ground for the stage |
else |
Repeating the stage... |
move $A400,p,$400 endif |
Restores ground backup |
move $E300,$B742,7 |
Resets UFO |
u1=1 |
Marks UFO as alive |
u3=1 |
Enables bomb |
poke $B3B9,192 |
Shooting Draws an horizontal missile (M3) |
f=0 |
Fire counter |
z=0 |
Horizontal position of laser |
o=0 |
Explosion counter |
u=0 |
Hit-a-rock flag, avoids hitting two rocks with the same shot |
nf=0 |
Trigger release flag |
e=0 h=0 g=0 |
Fine scrolling counters |
k=0 |
Resets the killed flag |
repeat |
Stage loop |
if i&3=2 |
Scrolls the mountains |
inc e if e=4 |
Fine scroll |
inc l e=0 |
Block scroll |
if l=$A210 l=$A200 endif endif |
Scroll back? |
dpoke s0,l |
Block scroll in DL |
poke $A180,8-e endif |
Fine scroll for DLI |
if i&1 |
Scrolls the hills |
inc h if h=4 |
Fine scroll |
inc m h=0 |
Block scroll |
if m=$A318 m=$A300 endif endif |
Scroll back? |
dpoke s1,m |
Block scroll in DL |
poke $A181,8-h endif |
Fine scroll for DLI |
i=(i+1)&$FF |
Next step |
if u1*peek($D00A) |
Is UFO still alive? |
o=9 |
UFO was hit, sound FX please |
n=n+25 |
Adds many points to the score |
u1=0 endif |
Disables the UFO for a moment |
u2=i>=x and u3 if u2 |
Drop a Missile? |
u3=0 |
Disables continuous drop |
poke $A192,i endif |
Locks horizontal position for the missile |
poke $A193,u1*i |
Locks the UFO out of screen if it was killed, or advance one color clock |
if not i |
Start new UFO sequence? |
poke $D01E,0 |
Clear collision register |
u1=1 |
Enable UFO |
u3=1 endif |
Enable bombs |
if g |
|
inc p inc q |
Block scroll every 2 steps (2 color clocks) |
dpoke s2,p dpoke s3,q endif |
Block scroll in DL |
g=2-g |
Scrolls de ground |
poke $A182,8-g poke $A183,8-g |
Fine scroll for DLI |
nf=nf+strig(0) |
Confirms trigger release |
t=(t+1)*(t<2) |
Next wheels turn |
if not j st=stick(0) |
Not jumping? |
if st&1=0 |
Jump? |
j=20 else |
Triggers the jumping |
x=x-(st&4=0)&(x>50)+(st&8=0)&(x<120) |
Move left or right? |
bb=x/40 endif |
Sets bonus by position |
if not f+strig(0) and nf |
Shoot? |
poke $A196,x+4 |
Sets horizontal position of vertical missile (M2) |
nf=0 |
Disables automatic next shoot |
f=7 |
Starts shooting sequence |
u=0 |
Resets hit-a-rock flag |
z=x+8+g endif |
Sets the position of the horizontal shoot |
else |
Jumping... |
n=n+(peek(p+x/4-10)>67)*bb*(g>0) endif |
Adds points while jumping over an obstacle |
poke $A190,x poke $A191,x |
Updates horizontal position of the buggy |
if j |
Jumping? |
y=yy(j) |
Next height |
sound 1,100-3*y,10,3*(y>0) |
Jumping sound |
dec j |
Decrease jumping step |
elif n s=s+n n=0 |
Points to add to score? |
nt$=str$(s) |
Converts number to digits |
position 25-len(nt$),0 print nt$ endif |
Align score right (besides a static "0") |
pause 0 move d,$A000,50 move $A180,hh,4 move $A190,$D000,8 |
Refresh the screen |
move $B348,$B338,112 |
Scrolls the vertical missile up |
mset $B64C,3,u2*u1 |
Draws a missile if it was just dropped |
u2=0 |
Resets dropping flag |
if f |
Firing? |
dec f |
Decreases firing counter |
a=u=0 |
Still not hitting a rock? |
mset $B3A4,4,32*(f=6) |
Puts a vertical shoot in the first step |
sound 2,9+f,10,f*a |
Firing sound FX, shuts up automatically |
z=z+6 |
Next step of shoots |
poke $A197,z*a*(f>0) endif |
Sets position of the horizontal shoot |
move w+31,$B5B3-y,15 move w+44+13*t,$B4B3-y,15 |
Updates the buggy |
-move $B648,$B64C,117 |
Scrolls down the missile |
if peek($D004)>1 or peek($D00E) |
Crashed? |
move w+93,$B4B8-y,8 |
Displays crash bitmap |
k=1 |
Sets killed flag |
n=0 endif |
Discards new points for score |
a=p+(z+g)/4-10 |
Shot hit a rock? Computes the absolute position of the shoot on the ground |
b=peek(a)=73 c=peek(a+1)=73 |
Is there a big rock? |
if f*(b+c) and not u |
Found a rock? |
dpoke a+c,65 |
Removes the rock |
u=1 |
Sets hit-a-rock flag |
o=9 |
Enables explosion sound |
n=n+3*bb endif |
Scores for the hit |
c2(2)=i |
Changes color of mines |
if o |
Explosion sound effect |
dec o sound 3,80-o*5,0,o endif |
Plays a step of the FX on each loop |
until p>$A950+u1*30 or k |
End of stage? |
sound |
Shuts up! |
v=v-k |
Lost a life? |
b=k=0 if b |
Survived the stage? |
inc r |
Increase stage counter |
poke $BC65,r+$A0 |
Updates the POINT stage |
v=v+(v<5) endif |
Recovers a life (5 max) |
poke $BC5E,v+$D0 |
Prints current lives |
for a=0 to 31 step 1+k sound 1,200-a*b*6,8+2*b,7-a/4 pause 0 next |
Plays a tune (buggy explosion or end of stage) |
until r=26 or not v |
Game is over when there are no more lives or all stages were completed. |
if v |
Displays end of game message |
print "{binary data}" else |
Completed game |
print "{binary data}" endif |
Game over |
while strig(0) wend |
Wait for trigger to restart |
loop |
Return to my 10-liners page.
© 2020 by Víctor Parada - 2020-04-09 (updated: 2021-04-10)