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 PUR-120 category, and it was written using FastBasic 4.3 for the 8-bits ATARI XL/XE. Development started on 2020-03-08, and it took 5 days. The final version's date is 2018-03-12.
UPDATE: It obtained the 1st place of 30 entries in the category.
You are a monster wazer and you have to go to the destination pin, avoiding all those wazers on the way.
You are a monster wazer, and you have to go to the pin at the other side of the road. | |
Find the path through all those wazers on your way. | |
When you arrive to the destination, a new pin will appear, and you must go there. | |
When you crash into a wazer or to the trees, you will loose a try. | |
You can recover a try each time you reach the destination. | |
Sometimes, you will have to go to places that are hard to reach because you won't know if wazers are approaching in that zone. | |
Traffic will increase as time passes. | |
When you loose all the tries, the game is over. How many trips could you do? | |
Press the button of your joystick to start a new game. |
This game is based on Waze characters ("moods") and the development was not supervised by Waze.
For some years, I was thinking about a game based in Waze app and it's moods. While thinking in a game concept, I included the Waze logo as a puzzle in my Pixelated Puzzle game, but I wanted something different, probably related to driving instructions that must be followed to find a destination or something like that.
Pixelated Puzzle's Waze puzzle
One of the 2020 new features of FastBasic was DLI statement, which enables a program to include multiple Display List Interrupts. This means that few system registers could be altered automatically during the refresh of the screen, giving the posibility to change element's colors or to manage P/M by screen line. Thinking about a game to use this feature, the first one was a Frogger-like game, and the idea about using Waze moods came back.
About the dynamics, at first I thought about moving some wazers from the bottom to the top of the playfield filling empty spaces (like in Frogger), but having to select the space with a pin to complete the trip. On the way there will be moving obstacles that should be avoided. After some rework of the concept, the obstacles became a lots of other wazers moving as in a rush hour, and a single wazer doing his commute from home to work and backwards.
I wrote the first prototype using ANTIC 5 text mode, because I could get a background color and 4 other colors for the game elements. With DLI, I could get 4 different colors per line, and that could be 40 diferent colors for 10 lanes with wazers. Hum... only 31 different colors, because 1 color should be fixed in black for the wheels and face details of the wazers. That also restricted which color should be used for the background, because it could not be black. Almost all my previous games have a black background because game elements look better, so I selected grey for the background and a list of random colors and brights for the three selected playfield registers (one of them was reserved for black).
I was used to manage P/M graphics using only POKE and MOVE, but this time I tried FastBasic's built-in PMGRAPHICS and PMHPOS instead of a lot of POKEs, and see how that work in a tenliner. For the playfield, I decided to use a custom display list, in order to enable fine scrolling, so each of them got assigned 64 bytes. Doing this way, I could randomly add wazers out of the screen and make them appear softly in its lane. Wazers should be displayed by 2 ANTIC 5 chars, giving a 8x8 bitmap, a very extreme simplification of Waze moods.
The playground would consist on 1 scoreboard line, 1 origin/destination line, 9 wazer lines and 1 origin/destination line, for a total of 12 double scanlines text lines, like a full graphics mode 2 screen. Both origin/destination lines should have a blocking obstacle except for home&work spots, and I decided to add a wall.
Proof of concept
Adding colors with DLI
It was time to design the wazers. I opened a blank spreadsheet, adjusted the width of 8 columns and started painting. I tried to replicate some of the most common wazer moods, but in a 8x8 grid, there was no space for the details of most of them, so I kept only a few. Instead of drawing the face in the center of the sprite, I decided to make them as if they were looking forward. With some formulas in the same sheet, I could get the bytes to include the bitmaps into the code. I also decided that I won't use the default pink mood or the standard white wazer from the logo, but the monster mood, so I had to simplify that icon for the P/M single-color bitmap.
Bitmap designs
To be able to display three diferent colors on each row, I need to define two sprites: one using PF0 and one using PF2, which could be also displayed in inverse video o get the PF3 color instead. This required to redefine four chars (two per sprite) and a 5th char for the wall. With the sprites included in the code, I could improve the game, including some other elements and adjusting the game behavior. I changed the background color to white in order to get a better view of the monster eye. After some fine tunning, I changed the bitmap for the "happy" mood that uses PF0 and turned it into a female "happy" mood, trying to assign soft colors to that register. Also, I could use two players (P0 and P1) for the monster (moving the pin to P3), obtaining a more interesting view.
Sprites in the game
Female wazers on the rush!
Aditional bitmaps
By this time, the code was just around 1200 bytes, so I decided to keep the game for PUR-120 category. This meant that no new features would be added. As no fine scrolling was used and it won't be, I removed some of the code from the DL, but enabled the wide playfield to let the wazers appear from out of the visible area on the TV. This had a drawback on DLI: as there is a small amount of CPU cycles that could be used between one line and the next one, only few system registers can be altered before being noticed on the screen, and in normal playfield only three color registers can be safely altered, but on wide playfield mode, there are less CPU cycles available, and the third color register is updated while the scanline is being drawn on screen. Oops... I had to change something.
I decided to insert a blank line between the lanes. This was good because the wazers were touching between them and a separation was a visual improvement, and it also gave more space to the monster wazer for going between wazers without crashing in the traffic, but forced me to remove two lanes (7x2 blank scan lines instead of 2x8 scan lines for lanes) or just one lane if the screen is moved upwards and extended a bit downwards. I decided to remove just one lane, and the playfield became of a very big size: 352x214 hi-res pixels or 176 color clocks x 107 double scan lines.
The changes in the code left me a little of extra coding space, so I could add a third bitmap for a wazer, and selected the "cool" mood instead of the "T-rex". This way, I changed the female "happy" from PF0 to PF2/PF3, and assigned "cool" to PF0. Now, with three sprites, I can have five different wazers per row: "cool" with its own color, male "happy" in two colors and female "happy" in the same two colors. At the same time, I found that a wall was an ugly element, and turned it into a row of trees. I had to fine tune colors and made a traffic jam to see them all at the same time!!!
5 sprites per lane (row)
Traffic jam!!!
In order to speed up things, I omitted the PAUSE 0 statement to be in syncro with the screen refresh, but I got an unexpected behavior: sometimes, the eye of the monster (P0) is refreshed before its body (P1), so it seem that the monster is looking at to where it is moving. I did not try to fix that because I felt it was fun.
Get the WAZERS.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:
graphics 8 |
Cleans upper 8K RAM area to be used by P/M |
graphics 0 |
Graphics mode 0 (standard), but only 1st line is used for PRINTing the scores. |
s=adr("{binary data}")+1 |
Binary data (some bytes used twice) 0-9 (10 bytes): 5 wazers 10-65 (56 bytes): 7 bitmaps for 3 wazers and the wall 65-80 (16 bytes): 2 bitmaps for monster (P0 & P1) 80-88 (9 bytes): bitmap for pin (with extra top blank boundary) 89-97 (9 bytes): Color palette for PAL |
data u() byte = "{binary data}" data v() byte = "{binary data}" data w() byte = "{binary data}" |
Lists of PAL colors used by Multi-DLI U(): 708 ($D016) V(): 710 ($D018) W(): 711 ($D019) Each list contains 1 byte for title, 1 byte for upper trees, 8 bytes for lanes, and 1 byte for lower trees. Hidden extra initial byte of each data that has the constant value of 11 (string length in FastBasic) is used in a blank line at the top. |
dli set c=u into $D016,v into $D018,w into $D019 |
Defines a multi-DLI using the color lists |
dim n(7),z(4) |
Arrays N(0-7): delay for each lane Z(0-4): wazer sprites (2 bytes = 1 word) |
move s,adr(z),10 |
Inits wazers array |
poke 82,3 |
Sets left margin |
poke 559,35 |
Enables wide playfield |
pmgraphics 2 |
Enables P/M with defaults for double scan line |
p=pmadr(0) |
Get address for P0 data |
q=$8300 |
Source data for player (P0+P1) |
r=s+80 |
Source data for pin (P2) |
mset $8000,$400,0 |
Cleans buffers for lanes and walls |
move s+67,q+3,6 move s+73,q+129,8 |
Puts player bitmaps into source data |
move $E000,$9000,$400 move s+10,$9200,56 |
Copies charset from ROM to RAM and modifies it to add wazers |
poke 756,$90 |
Enables new charset |
move adr("{binary data}")+1,$7000,35 |
Sets new Display List |
dpoke 560,$7000 |
Enables the new DL with multiple DLI marks |
dli c |
Enables the Multi-DLI |
move s+89,704,9 |
Sets color palete |
do |
MAIN LOOP |
mset $8000,768,0 |
Cleans playfield |
mset $8240,128,70 mset $829C,3,0 |
Draws the walls and opens a space for the starting point of the player |
print "{CLS}wazers TRIPS:" |
Cleans the text screen (1st line) and displays title and score label |
i=0 |
Iterator index, from 0 to 7 (eight lanes with wazers) |
x=126 |
Current horizontal position |
y=103 |
Current vertical position |
e=14 |
Current pin horizontal position (virtual at start) |
d=y |
Pin vertical position (virtual at start) |
o=64 |
Current offset in P/M area for pin |
l=5 |
Lives |
a=x |
Horizontal position at trip start |
b=y |
Vertical position at trip start |
g=0 |
Delay before a trip. During this delay, a tone is played. |
t=-1 |
Trips (score) |
f=50 |
Probability to release a wazer in a lane, starts with 1/50, and increases up to 1/25 |
poke $D01E,1 |
Resets collisions |
repeat |
GAME LOOP |
i=(i+1)&7 |
Next iteration index |
if dpeek($D004) |
Collision? |
poke $D01E,1 |
Resets collisions again |
l=l-(l>0) |
Decreases lives number |
x=a y=b |
Restores player to starting point |
mset p,256,0 |
Removes the player from playfield |
g=60 h=0 |
Sets a crash tune |
else |
No crash... |
if y=d |
Reached the destination? |
o=64-o |
Toggles pointer between walls |
d=125-d |
Toggles destination |
x=e*4+70 |
Adjusts horizontal position of player, because it could crash an finish the trip at the same time |
a=x b=y |
Saves the new starting location |
l=l+(l<5) |
Recovers a life (max 5) |
t=t+(t<9999) |
Increase the score (max 9999) |
position 18,0 print t |
Prints current score |
e=rand(32) mset $8240+o,64,70 mset $824E+o+e,3,0 |
Selects a new destination on the opposite side |
mset p+256,128,0 |
Removes the pin from the old location |
move r,p+256+d,9 pmhpos 2,e*4+70 |
Places the pin in the new one |
f=f-(f>25)*(t&1) |
Increases the frecuency of wazers release |
g=60 h=2 |
Sets a pin tune |
endif endif |
|
if g |
Pause to play a tone? |
poke $BC4A,l+$D0 |
Updates lives in scoreboard |
poke 77,0 |
Disables attract mode |
sound 0,98+g*h,8+h,g/8 |
Plays the tone based on parameters |
g=g-(g>0) |
Decreases counter for the pause |
else |
Not in pause mode |
if i&1 |
Move the player? Sprite of the monster is moved every 2 cycles |
j=stick(0) x=x+(j&8=0)*(x<200)-(j&4=0)*(x>48) y=y+(j&2=0)*(y<103)-(j&1=0)*(y>22) |
Reads the joystick and update player coordinates within the bounds |
pmhpos 0,x pmhpos 1,x move q,p+y,138 |
Update player position |
endif endif |
|
m=$8000+48*i |
Select next lane to move |
if n(i)>4 and rand(f)=0 |
Time to add a wazer? Wazers are randomly added some time after the latest of that lane, based on a decreasing delay |
dpoke m,z(rand(5)) n(i)=0 |
Adds a random wazer and reset the lane's timer |
endif |
|
-move m,m+1,47 poke m,0 |
Scrolls the lane right half a sprite |
inc n(i) |
Increases lane's timer |
until l+g=0 |
Game ends when there are no more lives (and crash tone has finished) |
while strig(0) wend |
Wait for trigger to restart |
loop |
Return to my 10-liners page.
© 2020 by Víctor Parada - 2020-03-10 (updated: 2020-08-15)