Copyright © 2016 by Víctor Parada
This is a little game for the 2016 NOMAM's BASIC 10-liners Contest. This program fits in the PUR-120 category, and it was written using TurboBASIC XL 1.5 for the 8-bits ATARI XL/XE. Development started on 2015-08-10, and it took 8 days. The final version's date is 2015-08-30.
UPDATE: It obtained the 1st place of 22 entries in the category.
Kill all the invaders before they reach the earth. Just one chance... win or die!
Game starts immediately. Invaders don't wait!!! Aliens move across the screen as they advance towards the bottom of the screen. | |
Use the joystick to move the cannon to the left or to the right across the screen, shooting aliens and avoiding to be hit by their ammo. | |
Aliens will move faster as more of them are defeated. | |
You win the game if you destroy all the aliens before they reach bottom of the screen. | |
You loose the game if your cannon is destroyed or the invaders reach the earth. |
After the first version was ready, I thought about another game that could be simplified in a 10-liner, but this time I wanted to use Player/Missiles, as I never used them before in BASIC (without a M/L USR routine I got from A.N.A.L.O.G. Magazine). I knew that some BASIC dialects had instructions to deal with P/M graphics, but TurboBASIC XL was not one of them. Instead, TBXL provides MOVE and -MOVE statements that could be used to manage chunks of memory, and that was all we need to set P/M data to be displayed. Memory registers should be used to set horizontal position, colors and check for collisions.
Then it came to me the idea of the use of MOVE instruction to also move screen data as a block, and that should be used in a game like "Space Invaders", but with ASCII characters (another "Alpha/Numeric" approach). I did a small proof of concept: it worked and needed very few instructions.
Proof of concept
But it felt so hard that I tried to add fine scrolling: glitches appeared. It seemed that a method to synchronize data movement, update of registers and the refresh of the screen. Fortunately there is a BASIC instruction that could be used to synchronize: PAUSE. This instruction is used to wait for a moment, and it checks internal timer changes to count for the number of "jiffies" (1/50 or 1/60 of a second in PAL or NTSC respectively). The special statement PAUSE 0 waits only for the first change of the timer, and that happens just during the screen refresh. Doing all POKEs and MOVES just after that statement reduced the glitches. Instead of moving blocks of memory, I decided to change the display list's memory pointer. Glithches had gone. But this time used screen memory of graphics mode 0 with a modified DL look like mode 1 instead of real mode 1. This way, I'll have the double of screen memory to scroll the invaders down without moving memory data. I decided to leave 1 line of graphics mode 0 in the bottom, because it already has a color without any screen data, so it would represent the earth.
Proof of concept using fine scrolling
Glitches in fine scrolling
During these tests, I decided to change the ASCII chars to an alien char, and used 2 bytes wide aliens, with no bytes between them. The space would be visually added in the fonts definition. That would allow missiles go through them. So I started the programming to include P/M graphics. First, it was the cannon, then the missile. As "missile graphics" require some binary algebra to handle many of them at a time, because they use a pair of bits in the same range of memory bytes, it would be easier to handle only "player graphics", so I assigned P0 for the cannon and P1 for it's missile.
P/M: Adding a cannon
P/M: Adding a missile
To detect if the missile hit an alien, it was only needed to check the collision register for P1 against the playfield, and then calculate which alien was hit based on the current coordinates of the player and the current position of the block of aliens. The horizontal position of the missile is fixed and set when it was fired. On each iteration, the vertical position is moved up 4 scan lines, so the missile needed to be redrawn. If it reached the top, it was turned off and available to be fired again.
P/M: Detecting collisions
After some adjustments, I noticed that there was enought room for more code, so I started to think how to manage the missiles launched by the aliens. Initially, I selected P2, which could be fired once and moved down until it reached the bottom of the screen, but only one missile would be fired at a time. A FOR loop could be used to iterate between P2 and P3 to manage 2 missiles at a time... but I wanted to keep it simple, and realized that there were 4 missiles available, each with it's own horizontal position, but as them share the same memory locations, a single -MOVE instruction would scroll down 4 missiles at the same time wherever they were on the screen. Just needed to find the number of cycles to wait before releasing one and being sure that it has already reached the earth and removed from the playfield, or it would "jump" to a new position. The drawback is that I had to check all missile's collision register against P0, but that could be done in a very simple way: if one of them is not clear, the game is over.
P/M: Aliens's missiles
I used the original "Space Invaders" bitmaps of the aliens, but I wanted to include both positions for each of them, and swap on every step like the game. Unfortunately I had room for one of the steps for the three types of aliens, or try two steps and keep only one type of alien for all the block. I decided to keep the three types of aliens, because each of them had it's own width, being harder to hit one from the top row than from the bottom one. And because the game looks like the original one... but I selected the most known step of each type. Also, I decided to add an extra row on top (one more than original game) and more columns to fill the screen and to add a bit of extra difficulty.
Final version of Invaders
This game was programed very similar to "Fleas!": I wrote BASIC code in a text editor in abbreviated form, then copy the code and paste it in Altirra to test. I began to write some tools to add special characters to the text file in order to include ATASCII codes in constant strings for the new charset with aliens's bitmaps.
Get the INVADERS.ATR file and set it as drive 1 in a real Atari or emulator. Turn on the computer and the game should start after loading. A joystick in port 1 is required.
The abbreviated BASIC code is the following:
The full and expanded BASIC listing is:
graphics 24 |
Cleans the upper 8K of RAM memory. 1K is used for double line resolution P/M data. Note that this program uses static memory addressing, not the usual vectors like DL=DPEEK(560), assuming you are running TurboBASIC XL in a standard XL/XE computer (or emulator) without a cartridge. |
graphics 0 |
Sets the playfield area: 1K in the TOP of RAM. The display list is modified in the following way: a graphics mode 2 with a pointer to the top of the screen, another graphics mode 2 line as a filler to center the playfield in the screen, a graphics mode 1 line pointing to the block of aliens' memory and fine scrolling enabled, and 18 graphics mode 1 lines with fine scrolling enabled. The last graphics mode 0 line was kept to represent the earth. As graphics mode 1 uses half of the memory than graphics 0's, the block of aliens is placed in the middle of the screen memory, so the remaining memory is displayed as blank area. When the invaders move to the right, we are actually moving the playfild memory pointer to the left. This way, the upper screen memory is used to display blank area above the invaders, and they can go down without having to move memory blocks. |
k=adr("binary-data") |
Lots of data:
1-4 (4 bytes): Bitmap for the cannon. Note that the last byte is also used in the next group. 4-11 (8 bytes): Color for each playfield and player/missile. Note that the last 3 bytes are also used in the next group. 9-21 (13 bytes): Initial horizontal position and width for each player and missile. 22-29 (8 bytes): Beginning of the new display list. 30-77 (48 bytes): Bitmaps of the invaders (6 chars = 2 chars * 3 aliens). |
q=$BC28 move k+21,q-5,8 |
Replaces the beginning of the display list with new data, setting up the first 4 lines of it. |
move q+2,q+3,17 |
Repeats the second line of the playfield in the remaining ones, except for the last one, which remains in graphics mode 0 to represent the earth. |
p=$D000 |
Sets p as the memory address where we have to write the horizontal position of player 0 (P0) and to check for missile 0 (M0) collisions. The following 3 bytes are for P1 to P3 positions and M1 to M3 collisions. After that, the following 4 bytes are for M0 to M3 positions and P0 to P3 collisions. |
m=$D404 poke m,0 |
Sets the horizontal fine scroll to none. |
poke m+3,184 |
Sets the P/M base address. |
poke 559,46 |
Enables P/M graphics on standard width playfield. |
poke $D01D,3 |
Turns on players and missiles. |
move $E000,$B000,512 move k+29,$B008,48 |
Copies the first half of the charset into RAM, and replaces some chars with alien's bitmap. |
z=1 |
Sets the initial index value for the arrays. Z is 1 when the invaders are moving to the right, and 0 if going to the left. |
d=1 |
Sets the initial horizontal moving direction of the invaders to the right (1=right, -1=left). |
t=54 |
Number of aliens in the block. |
move k,$BA68,4 |
Puts the cannon bitmap in P0 data. |
dim x(1),m(1),f(1),i(8),j(5) |
Defines the arrays used to control the block of invaders:
x(0-1): Index for left-most (0) and the right-most (1) column of aliens. m(0-1): Margin for the horizontal scroll depending on the current movement direction. f(0-1): Fine scrolling step that requires a whole horizontal scroll, depends on the direction. i(0-8): Number of aliens in each column. j(0-5): Number of aliens in each row. |
move k+8,p,13 |
Set the initial horizontal position and width for all players and missiles. |
u=1 |
U is the delay counter. It starts from 1. When it reaches the current number of aliens, the invaders must move one step. |
l=5 |
L is the current index of the lower row of aliens. |
poke 756,$B0 |
Enables the modified charset. |
w=$B99C |
Memory address where begins the aliens' ammo and it will scroll down two vertical positions on each loop. This is the M0 to M3 missiles' area of P/M graphics. |
e=$BAEC |
Memory address of P1 for the laser (missile). |
s=$BE0E |
S is the static memory position of the first alien in the block of invaders. |
r=s-2 |
R is dynamic memory position of the first displayed byte of the playfield. Will be decreased to move the block to the right. |
move k+3,704,8 |
Sets the playfield and P/M graphics colors. |
x(1)=8 |
Index of the right-most column of aliens. The left-most defaults to 0. |
m(1)=8 |
Number of the maximum number of chars to scroll to the right before to move them down and start moving to the left. The minimum number of chars when going to the left defaults to 0. |
f(1)=14 |
Number of the maximum number of fine scrolling steps before moving a whole char to the right. The minimum number of steps when going to the left defaults to 0. |
a=72 |
Initial horizontal position of the cannon. |
for j=0 to 5 |
Loop for each row of aliens: |
for i=0 to 8 |
Loop for the aliens in that row: |
dpoke s+j*48+i*2,$4242*((j+2) div 2)-1 |
Puts an alien in the block of invaders. The type of alien and its color is given by a single formula on the row number. |
i(i)=6 j(j)=9 |
Sets the initial number of aliens on each row and column. These are assigned many times, but this way saves bytes in the listing and also introduces a delay, so the aliens appears slowly, similar to the "Space Invaders" game. |
next i next j |
End of the set-up loops. |
while y+l*2<18 and c&257=0 and t |
Main loop: while the bottom-most invader has not reached the earth, the missiles collision registers are empty, and there are aliens in the playfield. |
poke $D01E,1 |
Clears the collision registers. |
u=u+2 |
Increase the delay counter to signal that invaders must be moved. |
h=stick(0) g=(h=7)-(h=11) |
Capture the movement of the joystick: -1 to the left, 1 to the right and 0 in other case. |
if a+g>46 and a+g<192 |
If the cannon is not in the borders of the playfield: |
a=a+g*2 poke p,a endif |
Move the cannon in the selected direction. |
if v=strig(0) |
Checks two things at the same time: that the missile is not moving and the joystick button has been pressed. |
b=a+8 v=4 poke p+1,b endif |
Sets the horizontal position of the fired missile, it's vertical position and puts in P1 memory area. |
-move w,w+2,78 |
Scroll down on screen the memory for all the missiles in the P/M area by two bytes (4 scan lines) |
o=(o+1) mod 36 |
Increments the counter for the alien's missiles. They will launch a missile every 9 steps, so 36 steps are required to have all 4 missiles on screen at the same time. |
if o mod 9=0 |
If it's time to launch a missile: |
h=o div 9 |
Selects the missile number (M0 to M3). |
i=rand(x(1)-x(0)+1)+x(0) |
Randomly select the alien's column where a missile would be fired. Only columns between the left-most and right-most aliens are a valid selection. |
if i(i) |
If there is an alien in the selected column: |
poke p+4+h,(x+i)*16+f+55 |
Set the horizontal position of the selected missile. |
_=w+i(i)*8+y*4 dpoke _,$202*4^h |
Put the missile in the P/M area for missiles. The vertical position of the missile is based on the number of aliens in that column and the number of rows already advanced by the invaders. All missiles share the same bytes, 2 bits each, so it must be calculated which pair of bits should be set for the given missile. I used the underscore variable as a helper to split the sentence between 2 physical lines of code. Note that there is a known bug here: if you killed an alien that is not the one at the bottom of the column, missiles will appear a little over that alien. |
endif endif |
End of the missile launch routine. |
pause 0 |
Synchonizes with vertical blank to ensure that a collision could be detected. |
c=dpeek(p+8)!dpeek(p+10) |
Captures the collision registers for all the missiles at the same time. |
if v |
If the laser (P1) was fired... |
if peek(p+5) |
If P1 (the fired laser) hits a playfield: |
i=(b-f) div 16-x-3 j=((76-v) div 4-y) div 2 |
Finds the alien that was hit based on the coordinates of the missile and the position of the aliens block. |
sound 1,8,8,9 |
Starts an explosion sound. |
dpoke s+j*48+i*2,0 |
Remove the alien from the block (two chars). |
i(i)=i(i)-1 j(j)=j(j)-1 |
Decrease the number of aliens on the row and column where it was. |
t=t-1 |
Decrease the number of total aliens. |
c=c!2 endif |
Sets the collision register as if the missile hit invaders' ammo, so it is processed that way in the following lines. |
dpoke e-v,0 |
Remove the missile (P1) from screen. |
if v<84 and c&514=0 |
If the missile was shooted and it haven't reached the top of the screen and didn't hit the invaders' ammo: |
v=v+2 dpoke e-v,$8080 |
Sets the new vertical position of the missile. |
else v=0 |
Else, disables the missile (already removed from the screen). |
endif endif |
End of laser (missile) validation routine. |
if u>t |
Begin of the invader's move routine. This happens every time the delay was completed. |
sound 0,255,10,8 |
Start the invaders' moving sound. |
u=1 |
Reset the delay counter. |
if f=f(z) |
If we have done all the fine scrolling: |
x(z)=x(z)-d*(i(x(z))=0) |
Adjust the invaders block parameters for current direction, removing 1 column if that one has no more invaders. |
if x(z)+x=m(z) |
If invaders have reached the border: |
d=-d |
Changes the movement direction. |
z=1-z |
Switches to the "other side" parameters. |
l=l-(j(l)=0) |
Adjust the number of rows of aliens if current bottom line has no more invaders. |
r=r-24 |
Move the invaders one line down by moving the memory pointer backwards. |
y=y+1 |
Increases the number of lines that invaders have advanced. |
else |
If aliens haven't reached the border: |
r=r-d-d |
Adjust the memory ponter of screen data by 2 bytes. Remember that aliens were built using 2 chars. |
x=x+d |
Increase or decrease the full horizontal scroll counter based on current moving direction. |
f=f(1-z) |
Set current fine scrolling index to the "other direction" limit. |
endif pause 0 |
Waits for a Vertical Blank to avoid glitches. |
dpoke q,r |
Change the DL vector to point to the new memory address. |
else |
Next fine scrolling step. |
f=f+d+d |
Calculates the next fine scrolling value. |
pause 0 endif |
Wait for next VB to avoid glitches. |
poke m,f |
Performs the fine scroll step. |
endif |
End of the invaders move routine. |
sound |
Turns off all sounds. |
wend |
End of game loop. |
if t dpoke $BA69,$2A55 |
If there are aliens in the playfield, then they won. |
else ? "WIN" endif |
If not, we won. |
get a run |
Waits for a key to start again. |
Return to my 10-liners page.
© 2016 by Víctor Parada - 2016-03-10 (updated: 2020-08-15)