6
Advanced Techniques
Laser Gunner II
Gary R. Lecompte

Version for the Atari by Charles Brannon with revisions by Thomas A. Marshall.

This revised version of "Laser Gunner" mixes machine language and BASIC to make a very exciting game. The enhancements include having two missiles on the screen simultaneously and smooth animation even as the missiles are fired.

In your corner of the universe, a zone of high-pressure radioactive plasma is contained by a platinum-iridium wall. Your ship, immersed in the red zone, is charged with a vital duty: defend the wall. The vengeful enemies of your civilization send wave after wave of attack ships in an effort to breach the wall. These semismart robot ships will concentrate their firepower on your weakest spot and mercilessly try to fire their way into the wall.

Your only defense is your powerful particle beam which you use to fend off the attacking drones. The enemy ships are wary of your power, so if you move too close to an attack point, you can spook the enemy ship into picking another target. Move to shoot at the new position, and it will just cruise back to another vulnerable spot. You must not let the enemy blast a hole in the wall since, like a balloon stuck with a pin, the radioactive plasma will explode, reducing your ship to an expanding shell of iridescent particles.

As the laser gunner, you try to react quickly to your enemy's shots. Follow the ship as well as you can, but do not stray too far from a weak spot. When you destroy one ship, another will appear at a random position, and will home in on a vulnerable spot in the wall.

A Novel Player/Missile Technique

For a game written in BASIC, "Laser Gunner" is reasonably fast and smooth. The smoothness of motion comes from player/missile graphics, but the speed comes from an unusual technique that lets you move player/missile graphics at machine language speed.

A special graphics technique is used here. Instead of storing the player/missile graphics at the top of memory, a large string is dimensioned to hold the player/missile data. When a string is dimensioned, a block of memory is reserved for it. The starting address of the string can be determined by using the ADR function. The problem is that player/missile graphics must start on an even 1K boundary (the address must be a multiple of 1024), or a 2K boundary (divisible by 2048) for single-resolution player/missile graphics. Strings are given the next available address when dimensioned, which would only be on an even kilobyte address by sheer coincidence.

So when the ADdRess of the string is determined, we must what offset to add to the address to reach the next boundary. It can be shown that in worst case conditions (i.e., the address is just one byte past a 1K or 2K boundary), we must allow for an offset of at least 1023 bytes for double-resolution, or 2048 bytes for single-resolution P/M graphics. So, although double-resolution P/M graphics require only 1024 bytes, we must dimension the holding string at least 2048 bytes. Then, a simple calculation (lines 150-160) will give us the starting address within the string of the base address, PMBASE. This value is then used to "set up" P/M as usual.

The advantage of using a string is twofold: one, we know that BASIC is covetously protecting the string from the "RAMTOP Dragon" (see COMPUTE!'s Second Book of Atari Graphics) and other nasties. Second, we can use BASIC's fast string manipulation commands to move segments of strings around, scroll a string, erase a string, copy one string to another, more. Since the memory being moved in the string is the P/M memory, these manipulations directly modify the players and missiles. And since these string operations internally proceed at language speed, we get fast P/M animation using BASIC. Although the code is not as straightforward as dedicated P/M commands such as PMMOVE or PMGRAPHICS, it sure beats cryptic USR statements. As a matter of fact, since BASIC permits such flexibility with strings, it may be the best solution to using P/M graphics from BASIC.

Using Vertical Blank for Smoother Motion

The original version of Laser Gunner required all other motion to stop when missiles were fired. By using a vertical blank interrupt routine, continuous and smooth motion can be achieved. The vertical blank (VB) is the time during which the television's electron beam is turned off while it returns from the lower-right comer of the screen to the top-left. Depending on the graphics mode and other interrupts, there are approximately 7980 machine cycles available during a single VB. (A machine cycle is the smallest measurement of time on your computer's internal clock.)

Bringing VB into the Picture

To utilize the VB, we first have to tell the operating system (OS) where to go. We do this by performing a Vertical Blank Interrupt (VBI) through the Set Vertical Blank Vector (SETVBV) routine. Before jumping to the SETVBV, we have to load the least significant byte (LSB) in the Y register and the most significant byte (MSB) in the X register of our VB machine language routine.

Into the accumulator we can place either a 6 or a 7. Six is for deferred mode; the OS does its housekeeping operations before it executes our code. Seven is for immediate mode; the OS executes our code first during the VB. Since we will be checking the collision registers, we will be loading a 6 into the accumulator. BASIC program initializes the SETVBV through the USR statement on line 1460. To return control to the OS, we jump back through $E45F.

The BASIC and the machine language (ML) programs interact through several PEEKs and POKEs. The ML program checks the STRIG(0), location $0284, for the press of a button, and moves both missiles horizontally. Since the player/missile graphics are defined in strings, it is easier to have BASIC draw and erase the missiles by PEEKing the flags that the ML program sets.

In the enhanced version, both missiles appear on the screen at the same time. This requires the additional coding located at $0607. The missiles are defined as:

  BIT   7 6 5 4 3 2 1 0
 
  M3 M2 M1 M0

Since it is difficult for Atari BASIC to selectively tum bits off and on, we will use ML to change the bits. The AND instruction is used to set bits to zero (off). ANDing a bit with zero sets the bit to zero. The ORA instruction is used to set bits to one (on). By ORAing a bit with one, we set the bit to one. The flipping of the missile bits is done in the subroutines at lines 1300-1330.

Further Enhancements

The programming technique of performing graphics movement during the vertical blank enhances Laser Gunner almost to the level of difficulty of professional arcade games. Further program execution speed can be achieved by removing the REMs and moving the part of the program that does most of the action to the beginning. This shortens the memory that BASIC has to search to find line number references. An additional enhancement would be to add a sound routine during the VB each time the trigger is pressed.

Laser Gunner II
0 REM  LASER GUNNER.V2{17 SPACES}
1 REM  An enchancement of Laser Gunner 
2 REM  in Issue 30 of COMPUTE!{9 SPACES}
3 REM  The program allow simutaneous{3 SPACES}
4 REM  motion of the missiles using the
5 REM  verticle blank period.{10 SPACES}
6 REM  Developed by Thomas A. Marshall 
7 REM  Albuquerque, NM 87123{11 SPACES}
10 GOSUB 1400
20 RESTORE 
100 DIM PM$(2048):GRAPHICS 2+16
110 DIM ALIEN$(11),PLAYER$(11),NULL$(11),EXPLODE$(12*9),TARGET(20)
120 FOR I=1 TO 11:NULL$(I)=CHR$(0):NEXT I
130 LEVEL=15:CNT=15:REM DECREASE LEVEL FOR A HARDER GAME
140 A=ADR(PM$):REM RAW ADDRESS
150 PMBASE=INT(A/1024)*1024:REM NEAREST 1 K BOUNDARY
160 IF PMBASE<A THEN PMBASE=PMBASE+1024:REM IF BELOW STRING, GO TO NEXT 1K BOUNDARY
170 S=PMBASE-A:REM START OF PMBASE IN STRING (OFFSET)
180 POKE 559,46:REM SET DOUBLE-LINE RES.
190 POKE 54279,PMBASE/256:REM TELL ANTIC WHERE PMBASE IS
200 POKE 53277,3:REM TURN ON PLAYER/MISSILE DIRECT MEMORY ACCESS(DMA)
210 PM$=CHR$(0):PM$(2048)=CHR$(0):PM$(2)=PM$:REM CLEAR OUT ALL P/M MEMORY
220 POSITION 4,0:? #6;"laser gunner"
230 ? #6:FOR I=1 TO 10:? #6;"-":NEXT I:POSITION 0,0
240 REM STRING POS OF PLAYER 0-3, AND MISSILES IN STRING:
250 P0=S+512:P1=P0+128:P2=P1+128:P3=P2+128:MS=S+384
260 PM$(P2+32)=CHR$(255):PM$(P2+127)=CHR$(255):PM$(P2+33,P2+127)=PM$(P2+32):REM CREATE WALL
270 PM$(P3,P3+127)=PM$(P2,P2+127):REM CREATE "ZONE"
280 POKE 53250,92:REM POSITION PLAYER 2, THE WALL
290 POKE 53251,60:REM POSITION PLAYER 3, THE ZONE
300 POKE 53258,0:POKE 53259,3:REM REM MAXIMUM WIDTH
310 POKE 706,14:POKE 707,66:REM SET COLOR OF PLAYERS 2 AND 3
320 DATA 0,8,28,62,255,62,255,62,28,8,0
330 FOR I=1 TO 11:READ A:ALIEN$(I)=CHR$(A):NEXT I:REM PLACE INTO STRING, HENCE INTO P/M MEMORY
340 AY=32:REM ALIEN VERTICAL LOCATION
350 PM$(P1+AY,P1+AY+11)=ALIEN$:REM PLACE INTO STRING INTO P/M MEMORY
360 POKE 705,6*16+10:REM SET COLOR OF ALIEN TO PURPLE
370 POKE 53249,180:REM SET HORIZNONTAL POSITIN
380 POKE 53257,1:REM SET ALIEN TO DOUBLE-WIDTH
390 REM SET UP EXPLODE$, USE FOR EXPLOSION OF ALIEN
400 FOR I=1 TO 108:READ A:EXPLODE$(I)=CHR$(A):NEXT I:REM EXPLODE DATA
410 DATA 8,28,62,255,54,255,62,28,8,8,28,62,235,54,235,62,28,8,8,28,54,227,34,227,54,28,8
420 DATA 8,24,34,227,34,227,18,24,8,8,24,34,194,32,163,18,8,8
430 DATA 0,0,0,0,24,24,0,0,0,0,0,0,32,8,24,0,4,0,0,0,0,36,0,16,0,36,0,0,128,10,128,0,16,0,16,65
440 DATA 0,9,0,0,32,0,32,0,8,0,0,0,64,0,0,64,0,4,0,0,0,0,0,0,0,128,0
450 RY=INT(78*RND(0)+32):MH=190+RY*2:REM ATTRACT MODE:
455 POSITION 9,5:? #6;"PRESS":POSITION 9,6:? #6;"START"
460 FOR I=32 TO 110:PM$(P1+I,P1+I+11)=ALIEN$:IF I=RY THEN PM$(MS+RY+10,MS+RY+10)=CHR$(12)
470 IF I>RY THEN POKE 53253,MH-I*2
480 IF PEEK(53279)>6 THEN NEXT I
490 PM$(MS+RY+10,MS+RY+10)=CHR$(0)
500 FOR I=110 TO 32 STEP -1:PM$(P1+I,P1+I+11)=ALIEN$:IF PEEK(53279)>6 THEN NEXT I
510 IF PEEK(53279)>=7 THEN 450
515 POSITION 9,5:? #6;"{5 SPACES}":POSITION 9,6:? #6;"{5 SPACES}"
520 IF PEEK(53279)=3 THEN FOR I=0 TO 4:POKE 53248+I,0:NEXT I:GRAPHICS 0:END 
530 DATA 0,0,224,48,120,63,120,48,224,0,0
540 FOR I=1 TO 11:READ A:PLAYER$(I)=CHR$(A):NEXT I
550 PY=60:REM SET PLAYER'S VERITCAL LOCATION
560 PM$(P0+PY,P0+PY+11)=PLAYER$
570 PM$(P1,P1)=CHR$(0):PM$(P1+127,P1+127)=CHR$(0):PM$(P1+2,P1+127)=PM$(P1)
580 AY=INT(78*RND(0)+32):PM$(P1+AY,P1+AY+11)=ALIEN$:REM RESET ALIEN
590 POKE 53256,1:REM PLAYER 0 DOUBLE-WIDTH
600 POKE 53248,64:REM HORIZONTAL POSITION OF PLAYER 0
610 POKE 704,26:REM COLOR OF PLAYER 0
620 POKE 53260,1:REM MISSILE 0 DOUBLE-WIDTH
630 ST=STICK(0):IF ST<>15 THEN DIR=ST:F=2:SOUND 0,100,0,8
635 IF PEEK(CMPFLG)=1 THEN PM$(TMS,TMS)=CHR$(0):POKE CMPFLG,0:REM THE MISSILES HIT EACH OTHER
636 IF PEEK(COLFLG)=1 THEN POKE COLFLG,0:GOTO 900:REM THE ALIEN MISSILE HIT THE WALL OR ZONE
640 PY=PY-(DIR=14)*(PY>32)*F+(DIR=13)*(PY<110)*F:F=1:REM UPDATE PLAYER
650 PM$(P0+PY,P0+PY+11)=PLAYER$:SOUND 0,0,0,0
660 IF PEEK(M0FLG)=1 THEN GOSUB 1310:REM ERASE THE PLAYER'S MISSILE
670 IF PEEK(TRIGFLG)=0 THEN GOSUB 1310:POKE M0FLG,0:TMS=MS+PY+5:GOSUB 1300:POKE TRIGFLG,1:REM THE TRIGGER WAS PRESSED
720 IF PEEK(HITFLG)<>0 THEN 790:REM NO COLLISION
725 REM THE PLAYER'S MISSILE HIT THE ALIEN
730 SCR=SCR+10:POSITION 11-LEN(STR$(SCR))/2,5:? #6;SCR
735 PM$(TMS,TMS)=CHR$(0):POKE M0FLG,1:POKE HITFLG,1:POKE 53278,0
740 AY=AY+1:P=PEEK(705):REM PRESERVE COLOR OF ALIEN
750 FOR I=0 TO 11:Z=I*9:PM$(P1+AY,P1+AY+9)=EXPLODE$(Z+1,Z+9)
760 POKE 705,PEEK(53770):POKE 53279,0:SOUND 0,I*2,0,15-I:FOR W=1 TO 2:NEXT W:NEXT I
770 POSITION 5,5:? #6;"{10 SPACES}":REM ERASE SCORE
780 SOUND 0,0,0,0:POKE 705,P:GOTO 570
790 IF AY=PY THEN 870:REM TOO CLOSE FOR COMFORT
800 IF TARGET=0 THEN GOSUB 950:TARGET=TARGET(INDEX):REM SELECT A TARGET
810 IF AY<>TARGET THEN 840
820 CNT=CNT-1:IF CNT THEN 630
830 CNT=LEVEL:GOTO 870
840 AY=AY+SGN(TARGET-AY):REM MOVE TOWARDS TARGET
850 PM$(P1+AY,P1+AY+11)=ALIEN$
860 GOTO 630
870 IF ABS(AY-PY)<10 THEN GOSUB 970
875 IF PEEK(ALIEFLG)=0 THEN 630
880 POKE ALIEFLG,0:TM1S=MS+AY+5:GOSUB 1320:TTAY=AY:GOTO 630
900 P=ASC(PM$(P2+TTAY+5))*2-256:GOSUB 1330:POKE 53278,0:REM CUT HOLE IN WALL
910 IF P<0 THEN 1000:REM WALL DESTROYED
920 PM$(P2+TTAY+5,P2+TTAY+5)=CHR$(P)
930 GOTO 630
940 REM PICK A TARGET
950 INDEX=INDEX+1:TARGET(INDEX)=INT(78*RND(0)+32):RETURN 
970 IF INDEX=1 THEN 950
980 TARGET=TARGET(INT(INDEX*RND(0)+1)):RETURN 
990 REM DESTRUCTION OF PLAYER
1000 FOR I=1 TO 100:Z1=TTAY+5+I:Z2=TTAY+5-I
1005 PM$(TMS,TMS)=CHR$(0):POKE M0FLG,1:POKE M0PFLG,72
1010 IF Z1<126 THEN PM$(P2+Z1,P2+Z1)=CHR$(0)
1020 IF Z2>30 THEN PM$(P2+Z2,P2+Z2)=CHR$(0)
1030 IF Z1<126 OR Z2>30 THEN NEXT I
1040 FOR I=30 TO 1 STEP -1:FOR J=0 TO 20 STEP 3:SOUND 0,J+1,10,8:POKE 707,PEEK(53770):NEXT J:NEXT I
1050 SOUND 0,0,0,0:SOUND 1,0,0,0:POKE 707,14:FOR W=1 TO 50:NEXT W:POKE 707,0
1060 FOR I=0 TO 15 STEP 0.2:SOUND 0,I,8,I:POKE 704,16+I:NEXT I
1070 SOUND 0,0,0,0
1080 Z1=PY:Z2=PY:INCR=0
1090 Z1=Z1+INCR*(Z1<128):Z2=Z2-INCR*(Z2>=0):POKE 704,PEEK(53770)
1100 PM$(P0+Z1,P0+Z1)=CHR$(255):PM$(P0+Z2,P0+Z2)=CHR$(255):POKE 53279,0
1110 INCR=INCR+0.5:IF Z1<127 OR Z2>0 THEN 1090
1120 FOR I=1 TO 100:POKE 704,PEEK(53770):NEXT I
1130 FOR I=0 TO 7:POKE 53248+I,0:NEXT I:GRAPHICS 18
1140 POSITION 4,0:? #6;"laser gunner":POSITION 3,5:? #6;"your score was:";
1150 POSITION 10-LEN(STR$(SCR))/2,7:? #6;SCR
1160 FOR I=15 TO 0 STEP -0.2:SOUND 0,10+10*RND(0),0,I:SOUND 1,100+10*RND(0),16,I
1170 SETCOLOR 4,3,14*RND(0):NEXT I
1280 RUN 
1299 REM  M0 SET 
1300 Q=USR(ANORA,ASC(PM$(TMS,TMS)),3,2):PM$(TMS,TMS)=CHR$(Q):RETURN 
1309 REM  M0 CLEAR 
1310 Q=USR(ANORA,ASC(PM$(TMS,TMS)),12,1):PM$(TMS,TMS)=CHR$(Q):RETURN 
1319 REM  M1 SET 
1320 Q=USR(ANORA,ASC(PM$(TM1S,TM1S)),12,2):PM$(TM1S,TM1S)=CHR$(Q):RETURN 
1329 REM  M1 CLEAR 
1330 Q=USR(ANORA,ASC(PM$(TM1S,TM1S)),3,1):PM$(TM1S,TM1S)=CHR$(Q):RETURN 
1400 TRIGFLG=1546:HITFLG=1547:M0FLG=1548:TMS=1:TM1S=1
1410 ALIEFLG=1550:COLFLG=1551
1420 ANORA=1753:CMPFLG=1553
1430 IF PEEK(1753)=104 THEN RETURN 
1440 GRAPHICS 18:? #6;"INITIALIZING"
1450 RESTORE 1500:GOSUB 1500
1460 A=USR(1536):RETURN 
1500 FOR I=1536 TO 1552:READ A:POKE I,A:NEXT I
1509 REM  INIT 1536 TO 1552 
1510 DATA 104,169,6,170,160,22,32,92,228,96,1,1,1,72,1,0,180
1520 FOR I=1558 TO 1709:READ A:POKE I,A:NEXT I
1530 REM  MISSILE MOVING ROUTINE 
1540 DATA 173,132,2,201,0,240,2,208,12,205,12,6,240,12,169,0,141,10,6,240
1550 DATA 58,205,12,6,240,53,238,13,6,238,13,6,173,13,6,141,4,208,173,8
1560 DATA 208,41,2,208,9,173,13,6,201,190,144,27,176,15,173,13,6,201,170,144
1570 DATA 18,169,0,141,30,208,141,11,6,169,1,141,12,6,169,72,141,13,6,173
1580 DATA 14,6,201,0,208,63,173,9,208,41,1,208,21,173,9,208,41,12,208,29
1590 DATA 206,16,6,206,16,6,173,16,6,141,5,208,208,35,169,1,141,17,6,141
1600 DATA 12,6,169,72,141,13,6,208,5,169,1,141,15,6,169,0,141,30,208,169
1610 DATA 1,141,14,6,169,180,141,16,6,76,95,228
1620 FOR I=1753 TO 1791:READ A:POKE I,A:NEXT I
1630 REM   AND-OR ROUTINES 
1640 DATA 104,104,104,141,215,6,104,104,141,216,6,104,104,201,1,208,9,173,215,6
1650 DATA 45,216,6,76,249,6,173,215,6,13,216,6,133,212,169,0,133,213,96
1660 RETURN 

Listing. Laser Gunner II.
Download (Saved BASIC) / Download (Listed BASIC)


Return to Table of Contents | Previous Section | Next Section