Copyright © 2023 by Víctor Parada
This is a little game for the 2023 NOMAM's BASIC 10-liners Contest. This program fits in the PUR-120 category, and it was written using FastBasic 4.6 for the 8-bits ATARI XL/XE computers line. Development started on 2022-01-28, and it took 2+6 days. The final version's date is 2023-02-25.
UPDATE: It obtained the 1st place of 9 entries in the category.
Proud of your white Beetle, participate in the Beetles Great Parade. How far can you go?
When the title scren appears, press the button to join the Beetles Great Parade. | |
You can move to the left and to the right using the joystick. Upwards you can accelerate and downwards you can reduce your speed. You earn a point for every car you pass. | |
You have to overtake 20 cars in less than 20 seconds. If you do it, the time is extended to overtake 20 more cars, and so on. Every time you get extra time, you also get bonus points (the square of the remaining time), but the path is narrowed a bit. | |
When you crash against another beetle or when you get out of the way, your the hood of your beetle will open and you must close it to see again. Precious time will be lost as you have to join the Parade again. | |
When you feel that a crash is inminent, press the button to honk. One of the other cars might change to the other side. If you are lucky, you could find a path to be safe. Using the honk too much might lead to a major crash. Use it carefully! | |
When there is no more time, your beetle will stop and the game is over. Honk to join the Parade again. |
Big and chunky sprites have a some kind of mystique, and I've written some games with such kind of sprites. While reviewing for the Atari Homebrew Awards lots of games released in 2022, a small game with huge sprites surprized me. It made recall the old Speed Race arcade, and I thought that someting similar could be done in Atari BASIC for the tenliners contest in the PUR-80 category (up to 800 bytes), using a string manipulation trick I developed to manage P/M graphics. I tried a proof of concept, using P0 for the players car, and P1 to P3 as the enemy cars, I could make them scroll down showing at most three of them at any time. The result was too slow for an action game. Instead of discarding this prototype or trying it in Turbo Basic XL, I turned it into Fastbasic and tried it again. The result was nice enough to continue the development.
Initial prototype in Atari BASIC.
Second prototype using Fastbasic and wide P/M graphics.
Not being an Atari BASIC game, the max size restriction increased to 1200 bytes for the PUR-120 category. That is a lot of space for a simple mini game, so I thought on how many features could be added and which would the game rules be. In the features, I thought about a narrowing or zig-zagging path, cars that move with different horizontal speeds and, obviously vertical speed control relative to our own car, which it should be fixed to a given height in the screen. About the rules, I thought about a challenge game counting how many cars you could pass in a given amount of time, or how many cars could you pass until you crash.
Adding car bitmaps and shoulders.
First playable version with collision detection.
After I coded some of the game action, I realized a couple of issues: the road was too wide with many open space to go through and sometimes the enemy cars were randomly aligned to force you into a crash. The solution for the first issue was simple: to reduce the width of the highway and use the sides to show the score and timer in a column. For the second one, it was fun to add a horn: you can press the button to beep-beep and make them change the direction, but not every one, just one of the three, and that one was randomly selected, so you have to horn many times to move them all, but sometimes against yourself!
Adding a left pannel, new bitmaps and crash animation.
New scoring system is displayed.
With enough space available, I added extra animations and sound effects. The bitmap for the car turned into a VW Beetle after some iterations, making it bigger. The rules of the game changed to a stage based ones: you have to avoid a given number of cars in a given amount of times to get into the next stage in a continuous run. At the end of every stage, the paths is narrowed a couple of pixels, so at some time it will be impossible to continue without a crash. Every crash just makes you waste time trying to get the rush. I fine tuned the timer during my tests, so you have to speed up your run if you crashed to complete the stage. Three crashes in the same stage are enough to be disqualified.
Simplifying the animation to save bytes.
During the last development iterations, the code was about 1200 bytes, but hard enough to make it fit 10 lines of at most 120 chars, and I had to decide which features to remove. The first feature to be removed was the title screen, turning it into a very simple text screen. I also replaced the original crash animation using zig-zagging cars with a simpler one that move them straight to the top. This gave me more control over the score and the speed of the game. I also changed the use of each P/M, making player's car be P3 and enemy cars P0 to P2, simplifying some formulas and memory management.
In this process I found that I was not using Fastbasic statements and functions for P/M management but standard POKE instructions for Atari BASIC or TurboBasic XL. I changed the syntax and I found that I had freed about half of a line of code, and that no speed up or slow down of the gameplay was notorious. That was enough space to restore the original intro screen and to improve the crash animation. Also, a small "tick" sound FX was added to signal when a car gets passed.
While writing this doc, I decided to change the crash, and turned it into a small "incident" in order to make the game flow: the hood of the car will open and prevent seeing ahead. That is a simple reason to make you stop and loose time.
Final version with an open front hood.
Some weeks later, I was thinking that the game did not reward completing the stage quick and with the least amount or no crashes. So I take a look at the packaged listing to find some room for 6 bytes in the proper place to add the remaining time to the score. After making some room by changing the order of some statements, I could make room and add the statement. Verifying that the game still have 10 lines, I found that the modified line still had 2 extra bytes available, so I could add more bonus points by adding the remaining time twice or my multiplying it by 2 or 3, but at the end, I multiplied the remining time by itself, so you could get 1, 4, 9, 16 or might be 25 extra points at the end of each stage.
Get the BEETLES.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.
NOTE: This game is for PAL computers. If it is played on NTSC computers, the text colors won't be the same, but the game should have the same difficulty.
The abbreviated BASIC code is the following:
The full and expanded BASIC listing is:
|
BEETLES (c) 2023 Víctor Parada |
dim x(3) byte,y(3),d(3),c(3) |
X(): Horizontal position of each car Y(): Vertical position of each car D(): Moving direction of each car C(): Start delay for each car |
data f() byte=0,202,180,160 |
Engine's sound depending on speed |
g=adr("{binary data}")+1 |
Bitmaps of car and opened hood |
graphics 18 |
Set screen to graphics mode 2 (ANTIC 7) without text window |
pmgraphics 2 |
P/M graphics in double line resolution |
poke 707,15 |
P3 player's car is white |
mset $D008,4,1 |
Double width players |
poke 623,16 |
GPRIOR=16: Missiles with 5th player color |
mset pmadr(0),512,0 |
Clears P0-P3 P/M area |
mset pmadr(-1),128,$F0 |
M2 & M3 delimits the street |
r=adr(x) |
Pointer to the array (buffer) of P/M's horizontal positions. To avoid graphic glitches, all cars are moved at once after a VSYNC |
u=dpeek(88)+140 |
Screen position of the timer |
position 6,5 print #6,"{binary data}" x(3)=116 y(3)=83 exec p 3 |
Displays title screen |
do |
GAME LOOP |
while strig(0) wend |
Waits for trigger |
a=92 b=188 |
Street side limits |
exec l |
Displays street limits |
o=1 |
General counter |
s=0 |
Score |
n=20 |
Cars needed to pass |
t=9 |
Time |
print #6,"{binary data}",, "{binary data}",,,,"{binary data}",,n,,, ,"{binary data}",,"{binary data}" |
Displays left panel |
repeat |
RACE LOOP |
for i=0 to 2 y(i)=55 c(i)=i*33 next i |
Initializes traffic |
x(3)=140 exec p 3 |
Initializes car position |
e=0 |
Delay to count cars that gone away (passed cars) |
h=0 |
No honk |
pause |
Syncs with VBLANK |
poke $D01E,0 |
Clears collisions |
repeat |
DRIVE LOOP |
t=t-(o=0) |
Decreases the round timer |
j=stick(0) |
Checks joystic position |
f=(j&8=0)-(j&4=0) |
Right or left? |
x(3)=x(3)+f+f |
Move 2 pixels to the selected side (if any) |
v=2+(j&1=0)-(j&2=0) |
Up or down? Selects current speed |
sound 0,f(v),12,3 |
Engine's sound depents on speed |
m=h |
Last state of the button |
h=strig(0) |
Current state of the button |
if h-m |
Did the state of the button change? |
if h |
Changed, and is it now released? |
sound 1 |
Yes... shut off the horn |
else |
No, it was pressed |
sound 1,75,14,8 |
Makes a beep |
f=rand(3) |
Selects a car |
d(f)=-d(f) endif endif |
makes it move to the other side |
o=(o+1)*(o<53) |
Increases general counter |
for i=0 to 2 |
Computes the position for each of the enemy cars |
z=x(i) |
Gets current horizontal position of a car |
w=(z<a)-(z>b) |
Checks if it has to change the direction because it is reaching a border |
if w d(i)=w endif |
Assigns new direction if it reached to a border |
x(i)=z+d(i) |
Computes the new horizontal position of the car |
if c(i)>0 |
Checks for a delay for the selected car |
c(i)=c(i)-v |
delay is based on current speed |
else |
No delay |
k=y(i) |
Gets current vertical position of the selected car |
if k<104 |
Still on screen? |
y(i)=k+v |
Moves it down |
else |
Reached the end |
mset pmadr(i)+y(i),23,0 |
Removes the car from the screen |
setcolor i-4,rand(15)+1, rand(8)+4 |
Selects the color for the next car |
inc e |
Counter for incomming cars |
d(i)=2*rand(2)-1 |
Selects a direction for the car |
x(i)=a+rand(b-a) |
Selects an horizontal starting position for the car |
y(i)=k-104+v |
Selects a vertical position near the top, aligned by current speed |
if e>3 |
Are incomming cars reaching the bottom of screen? |
inc s |
Yes, increase score |
dec n |
Decreases the pending cars for the round |
sound 2,8,10,6 |
Makes a "tick" sound |
if n=0 |
Was the last car of the round? |
s=s+t*t |
Yes, add time bonus |
t=9 n=20 |
Resets the timer and the cars count for the round |
o=1 |
Resets the general counter |
inc a dec b exec l endif |
Decreases the width of the road |
position 0,4 print #6,n, |
Updates the number of pending cars |
position 0,1 print #6,color(32) s endif |
Prints the new score |
exec p i endif |
Displays the selected car in its new position at the top |
poke 77,0 endif |
Avoid ATRACT mode |
sound 2 |
Turns off the "tick" sound |
next i |
Next car, please! |
poke u,$90+t |
Updates the timer |
pause |
Syncs with VBLANK to avoid glitches |
move r,$D000,4 -move pmadr(0),pmadr(0)+v,384 |
Move cars (horizontally and vertically) |
until peek($D00F) or dpeek($D00A) or t=0 |
Exit loop if crashed or no more time |
sound |
Turns off FX |
v=6 |
Select a sound for the game over FX |
if t |
If there is enough time, it was a crash |
move g+20,pmadr(3)+$53,9 |
Opens the car hood |
v=0 endif |
Selects a crash sound FX |
for f=0 to 48 |
Move forward all the traffic |
sound 0,72+f,8+v,8-f/6 |
Plays the sound FX |
for i=1 to 3 mset pmadr(i),3,0 next i |
Cleans the top of each enemy P/M area |
pause |
Sync with VBLANK; |
move pmadr(0)+3,pmadr(0),384 |
Scrolls up the P0-P2 area |
if t -move pmadr(3),pmadr(3)+1,127 endif |
Scrolls down crashed P3 car |
next f |
Repeat... |
until t=0 |
No more time? |
position 10,4 print #6,"{binary data}",, "{binary data}" loop |
Prints the "GAME OVER" message |
proc p i pmhpos i,x(i) move g,pmadr(i)+y(i),20 endproc |
Displays the given car |
proc l pmhpos 6,a-4 pmhpos 7,b+18 endproc |
Adjusts street borders |
Return to my 10-liners page.
© 2023 by Víctor Parada - 2023-02-04 (updated: 2023-04-03)