A.N.A.L.O.G. ISSUE 36 / NOVEMBER 1985 / PAGE 33
You knew if had been too quiet. Nothing had shown on the scanner for the whole watch. That in and of itself wasn’t unusual, but intelligence had reported increased enemy activity. It seemed that a major move to capture and destroy the gunbases that protected the Interior was being planned.
Further, the enemy had developed a new type of intelligent robot, which could stand the shock of being parachuted to Earth and, once there, could team up with other robots to destroy the gunbases. Intelligence reports indicated that each robot could carry one-quarter of the explosives necessary to pierce the armor of the gunbase you manned.
The anticipated plan is that the enemy choppers will drop robots, which, if they land successfully, will wait until three more robots have also landed, then team up to destroy your base. Since radio silence must be maintained, the robots only “know” about other robots in their direct line of sight, so four robots must land successfully on one side of your base.
These robots are not invulnerable, however. If one parachuting robot lands on another, the one underneath will be crushed and immobilized.
Your gunbase is a pretty awesome weapon. The gun is mounted on a concrete pedestal and is aimed by your joystick. The missiles unleashed by your fire button are steerable — they will travel left and right if you press the joystick control in the appropriate direction, and rise toward the top of your scanner screen if you center the stick.
The missiles are powerful, capable of obliterating the enemy’s helicopters, as well as the robots. One strategic trick, learned in advanced gunnery class, is to use a missile to destroy a parachute by careful aiming, thus causing the robot to plummet to Earth, destroying any robots that happen to be beneath it.
This is really the only method of destroying robots that are already on the ground. The enemy has split the attack into levels, and each level is faster and fiercer than the previous one. Duty calls, so plug your joystick into port 1 and prepare to defend your home as the attack commences! Good luck.
Each procedure is commented with a brief description of what it does. Some of the procedures illustrate interesting programming tricks, however, and I want to expand on them.
The first is the procedure Title(). As stated, it prints the title screen. Notice that it checks the location of the vertical scan VCOUNT and puts color information directly into the hardware registers COLPF0-COLPF3. This causes each scan line on the TV screen to be drawn in a different color. Action! is so fast that you can do this without resorting to machine language display list interrupts.
By using the built-in jiffy timer (RTCLOCK), which advances by one each time a new screen is drawn, in the equation to compute what color is actually used, the colors are made to “scroll” up the screen, providing a rather neat effect. The speed of the scroll is determined by the RSH portion of the color term. RSH essentially does a divide, so the more times you RSH the RTCLOCK, the slower the scroll will be.
The other interesting procedure is MoveTroopers(), which moves the robots down the screen. As you can see by looking at the program listing, Sneak Attack is written in graphics 0, with a redefined character set. Yet the robots scroll smoothly down.
The way it works is this: each robot is two characters high (chute and robot) and is initially put on the screen by simply printing three characters one above the other — the two characters which make up the shape and a third character which is initially blank. These three characters appear one after the other in the character set.
To move the robot in what looks like a smooth scroll down the screen, the 16 bytes which make up the shape (two characters at 8 bytes per character) are shifted 1 byte further into the 24 bytes of the three characters which were printed on the screen.
This “dynamic character redefinition” goes on until the figure has been shifted 8 bytes down, at which time the top character of the three is blank, and the 16-byte figure resides in the lower two characters. Then, you move the 16 bytes back into the top two characters, and then print the three characters one position lower on the screen.
The shifting of 16 byte blocks is done using MOVEBLOCK. The location of the character set and the location of the 16 bytes which make up the shape are passed to MOVEBLOCK by using the names of the arrays which contain the data. Used in this manner, array names are treated as the memory addresses of the data in the array.
I’ve been programming Atari home computers for four years. The very first video game I ever saw running on a home computer was a little something from Sierra (then On-Line), called Sabotage. It was only available on the Apple and was never translated.
I’ve always enjoyed Sabotage and several times tried to program something similar myself. I was never very successful, mostly because BASIC just isn’t up to the job. But Action! is, and I think you’ll enjoy this version of a venerable game.
One more thing. The end is worth waiting for.
David Plotkin is a Project Engineer for Chevron U.S.A., with a Master’s in Chemical Engineering. He bought his Atari in 1980 and is interested in programming and design of games, as well as word processing. His work has been seen in ANALOG Computing, Compute! and other computer magazines.
; Sneak Attack by David Plotkin
MODULE
BYTE
ChrBase=756,Max,Bkgrnd=710,
Fate=53770,Level=[1],CursIn=752,
Stick=632,Ps,Loud=[0],Indx=[0],
DownL=[0],DownR=[0],Loud1=[0],
Snd1=$D208,Snd2=$D20F,Freq=[169],
Wsync=$D40A,Colbk=$D018,
Nmien=$D40E,Hard=[15],
Consol=53279
CARD
Scrn=88,RamSet,HiMem=$2E5,
Score=[0],Comp=[300],Sdlst=560,
Vdslst=512
CARD ARRAY Linept(24)
BYTE ARRAY
Charset,Chopperstatus(30),
Chopperx(30),Choppery(30),
Expx(60),Expy(60),ExpStatus(60),
TrStatus(30),Trx(30),Try(30),
MisStatus(30),Misx(30),Misy(30),
Ll(20),Rr(20),Dlist,
ShapeTable(0)=
[254 16 124 71 127 12 62 0
127 8 62 226 254 24 126 0
96 96 48 48 24 60 231 255
24 24 24 24 24 60 231 255
6 6 12 12 24 60 231 255
128 85 17 66 24 170 91 131
60 126 255 255 195 66 36 24
60 36 24 255 60 24 36 102
0 0 0 0 0 0 0 0
60 36 24 255 60 24 36 102
60 36 219 255 60 24 36 102
60 60 24 60 60 24 24 28
60 60 24 60 60 60 102 195]
PROC Download()
;Step back HiMem and move the
;character set into RAM
CARD Index
BYTE Val
RamSet=(HiMem-$400)&$FC00
ChrBase=RamSet RSH 8
HiMem=RamSet
FOR Index=0 TO 1023 DO
Val=Peek(57344+Index)
Poke(RamSet+Index,Val)
OD
Charset=RamSet
RETURN
PROC Dlint()
;the display list interrupt routine
[$48 $8A $48 $98 $48]
Wsync=1
Colbk=50
[$68 $A8 $68 $AA $68 $40]
PROC ScoreLine()
;set up the dli
Dlist=Sdlst
Vdslst=Dlint
Dlist(27)=130
Nmien=$C0
RETURN
PROC Update()
;print score and level
Position(1,23)
Print("Score: ")
Position(8,23)
PrintC(Score)
Position(18,23)
Print("Level: ")
Position(25,23)
PrintB(Level)
RETURN
PROC Title()
BYTE colpf0=53270,colpf1=53271,
colpf2=53273,colpf3=53273,
rtclock=20,vcount=54283
Graphics(18)
Position(3,4)
PrintD(6,"SNEAK ATTACK")
Position(8,5)
PrintD(6,"BY")
Position(3,7)
PrintD(6,"david plotkin")
Position(3,9)
PrintD(6,"PRESS start")
WHILE Consol<>6 DO
colpf3=Fate
Wsync=0
colpf0=128-vcount+rtclock RSH 2
colpf1=vcount+rtclock RSH 2
OD
RETURN
PROC Gr0Init()
;Set up the address of each screen
;line and initialize
CARD xx
Graphics(0)
CursIn=1
Print(" ")
FOR xx=0 TO 23 DO
Linept(xx)=Scrn+(40*xx)
OD
FOR xx=0 TO 29 DO
Chopperstatus(xx)=0
Chopperx(xx)=0
Choppery(xx)=0
Misx(xx)=0
Misy(xx)=0
MisStatus(xx)=0
TrStatus(xx)=0
OD
FOR xx=0 TO 59 DO
ExpStatus(xx)=0
OD
FOR xx=0 TO 19 DO
Ll(xx)=0
Rr(xx)=0
OD
Bkgrnd=0
Update()
RETURN
PROC Plot0(BYTE x,y,ch)
;Plot a char at location x,y
BYTE ARRAY line
line=Linept(y)
line(x)=ch
RETURN
BYTE FUNC Locate0(BYTE x,y)
;Returns the value of the char at x,y
BYTE ARRAY line
line=Linept(y)
RETURN(line(x))
PROC Noise()
;the explosion noises
IF Loud=0 AND Loud1=0
AND Freq=169 THEN
RETURN
FI
IF Loud THEN
Loud==-2
Sound(0,90,8,Loud)
FI
IF Loud1 THEN
Loud1==-2
Sound(1,150,8,Loud1)
FI
IF Freq<168 THEN
Freq==+8
Sound(2,Freq,10,4)
ELSE
Freq=169
Sound(2,0,0,0)
FI
RETURN
PROC HitChute(BYTE wh)
;see which chute was hit by missile wh
BYTE lp
FOR lp=0 TO 29 DO
IF Misx(wh)=Trx(lp) AND
(Misy(wh)=Try(lp) OR
Misy(wh)=Try(lp)+1) THEN
TrStatus(lp)=2
Plot0(Trx(lp),Try(lp),0)
Plot0(Trx(lp),Try(lp)+1,10)
Plot0(Trx(lp),Try(lp)+2,0)
EXIT
FI
OD
IF Try(lp) LSH 3 < Freq THEN
Freq=Try(lp) LSH 3
FI
RETURN
PROC HitMan(BYTE wh)
;see which man was hit by missile wh
BYTE lp
FOR lp=0 TO 29 DO
IF Misx(wh)=Trx(lp) AND
(Misy(wh)=Try(lp)+1 OR
Misy(wh)=Try(lp)+2) THEN
TrStatus(lp)=3
Plot0(Trx(lp),Try(lp)+1,6)
Plot0(Trx(lp),Try(lp),0)
Plot0(Trx(lp),Try(lp)+2,0)
FI
OD
Loud1=12
RETURN
PROC ExplodeChopper(BYTE lp)
;explosions in place of Chopper lp
BYTE lq
FOR lq=0 TO 59 STEP 2 DO ;find empty
IF ExpStatus(lq)=0 THEN
ExpStatus(lq)=1
ExpStatus(lq+1)=1
Expx(lq)=Chopperx(lp)
Expx(lq+1)=Chopperx(lp)+1
Expy(lq)=Choppery(lp)
Expy(lq+1)=Choppery(lp)
Chopperstatus(lp)=0
Plot0(Expx(lq),Expy(lq),6)
Plot0(Expx(lq+1),Expy(lq+1),6)
EXIT
FI
OD
RETURN
PROC HitChopper(BYTE wh)
;which chopper was hit by missile wh
BYTE lp
FOR lp=0 TO 29 DO
IF Misy(wh)=Choppery(lp) AND
(Misx(wh)=Chopperx(lp) OR
Misx(wh)=Chopperx(lp)+1) THEN
ExplodeChopper(lp)
EXIT
FI
OD
Loud=12
RETURN
PROC MissileHit(BYTE wh)
;see if missile wh hit anything
BYTE dum
dum=Locate0(Misx(wh),Misy(wh))
IF dum=0 THEN
Plot0(Misx(wh),Misy(wh),84)
RETURN
FI
MisStatus(wh)=0
IF dum=1 OR dum=2 THEN
HitChopper(wh)
Score==+1
ELSEIF (dum=7 AND Indx<6 OR
dum=8 AND Indx>3) THEN
HitChute(wh)
Score==+2
ELSEIF (dum=8 AND Indx<4 OR
dum=9 AND Indx>1) THEN
HitMan(wh)
Score==+1
FI
RETURN
PROC Modify()
;Modify the RAM character set
CARD xx
FOR xx=0 TO 103 DO
Charset(xx+8)=ShapeTable(xx)
OD
RETURN
PROC LaunchTrooper(BYTE wh)
;drop a paratrooper from chopper wh
BYTE lp
IF Fate>240-(Level LSH 1) THEN
FOR lp=0 TO 29 DO ;find MT trooper
IF TrStatus(lp)=0 THEN ;got one
TrStatus(lp)=1
Trx(lp)=Chopperx(wh)
IF Trx(lp)=0 THEN
Trx(lp)=1
FI
Try(lp)=Choppery(wh)+1
Plot0(Trx(lp),Try(lp),7)
Plot0(Trx(lp),Try(lp)+1,8)
Plot0(Trx(lp),Try(lp)+2,9)
EXIT
FI
OD
FI
RETURN
PROC EraseChopper(BYTE wh)
;erase chopper number wh
Plot0(Chopperx(wh),Choppery(wh),0)
Plot0(Chopperx(wh)+1,Choppery(wh),0)
Chopperstatus(wh)=0
Chopperx(wh)=0
Choppery(wh)=0
RETURN
PROC DrawChopper(BYTE wh)
;draw chopper number wh
Plot0(Chopperx(wh),Choppery(wh),1)
Plot0(Chopperx(wh)+1,Choppery(wh),2)
RETURN
PROC ClearScreen()
;clear the screen
BYTE lp
FOR lp=0 TO 29
DO
IF Chopperstatus(lp) THEN
EraseChopper(lp)
FI
IF TrStatus(lp) THEN
TrStatus(lp)=0
Plot0(Trx(lp),Try(lp),0)
Plot0(Trx(lp),Try(lp)+1,0)
Plot0(Trx(lp),Try(lp)+2,0)
FI
IF MisStatus(lp)=1 THEN
MisStatus(lp)=0
Plot0(Misx(lp),Misy(lp),0)
FI
OD
FOR lp=0 TO 59 STEP 2 DO
IF ExpStatus(lp)=1 THEN
ExpStatus(lp)=0
ExpStatus(lp+1)=0
Plot0(Expx(lp),Expy(lp),0)
Plot0(Expx(lp+1),Expy(lp+1),0)
FI
OD
RETURN
PROC MoveChopper()
;move the choppers
BYTE lp,ps=[0]
FOR lp=0 TO 29 DO
IF Chopperstatus(lp)=1 THEN ;right
IF Chopperx(lp)=38 THEN
EraseChopper(lp)
ELSE
Plot0(Chopperx(lp),
Choppery(lp),0)
Chopperx(lp)==+1
DrawChopper(lp)
LaunchTrooper(lp)
FI
FI
IF Chopperstatus(lp)=2 THEN ;left
IF Chopperx(lp)=0 THEN
EraseChopper(lp)
ELSE
Plot0(Chopperx(lp)+1,
Choppery(lp),0)
Chopperx(lp)==-1
DrawChopper(lp)
LaunchTrooper(lp)
FI
FI
OD
IF ps=0 THEN
Charset(8)=56
Charset(16)=28
ps=1
ELSE
ps=0
Charset(8)=254
Charset(16)=127
FI
RETURN
PROC LaunchChopper()
;Decide whether to send off a new
;chopper, which side, how high up
BYTE lp
IF Fate>230-(Level LSH 1) THEN
FOR lp=0 TO 29 DO ;find MT chopper
IF Chopperstatus(lp)=0 THEN
Choppery(lp)=Rand(Hard)
IF Fate>128 THEN
Chopperx(lp)=38 ;right side
Chopperstatus(lp)=2
ELSE
Chopperx(lp)=0 ;left side
Chopperstatus(lp)=1
FI
DrawChopper(lp)
EXIT
FI
OD
FI
RETURN
PROC DrawBase()
;draw the base
BYTE lp
FOR lp=19 TO 21 DO
Plot0(lp,22,128)
OD
Plot0(20,21,4)
RETURN
PROC AimGun()
;read the joystick and move the base
IF Stick=11 THEN
Ps=3
ELSEIF Stick=7 THEN
Ps=5
ELSE
Ps=4
FI
Plot0(20,21,Ps)
RETURN
PROC Shoot()
;send off a bullet
BYTE trig=644,lp,flg=[0]
IF trig=1 OR flg=0 THEN
flg=1
RETURN
FI
FOR lp=0 TO 29 DO ;find empty shot
IF MisStatus(lp)=0 THEN ;got one
MisStatus(lp)=1
Misy(lp)=20
IF Ps=3 THEN
Misx(lp)=19
ELSEIF Ps=5 THEN
Misx(lp)=21
ELSE
Misx(lp)=20
FI
MissileHit(lp)
EXIT
FI
OD
flg=0
RETURN
PROC MoveShots()
;move the fired bullets
BYTE lp
FOR lp=0 TO 29 DO ;for each shot
IF MisStatus(lp)=1 THEN
Plot0(Misx(lp),Misy(lp),0)
IF Stick=11 THEN
Misx(lp)==-1
ELSEIF Stick=7 THEN
Misx(lp)==+1
ELSE
Misy(lp)==-1
FI
IF (Misx(lp)<>39 AND
Misy(lp)<>255 AND
Misx(lp)<>0) THEN
MissileHit(lp)
ELSE
MisStatus(lp)=0
FI
FI
OD
RETURN
PROC MoveExplosions()
;move the explosions
BYTE lp
FOR lp=0 TO 59 STEP 2 DO
IF ExpStatus(lp)=1 THEN
Plot0(Expx(lp),Expy(lp),0)
Plot0(Expx(lp+1),Expy(lp+1),0)
Expy(lp)==+1
Expy(lp+1)==+1
Expx(lp)==-1
Expx(lp+1)==+1
IF Expy(lp)<>22 AND Expx(lp)<>0
AND Expx(lp+1)<>39 THEN
Plot0(Expx(lp),Expy(lp),6)
Plot0(Expx(lp+1),Expy(lp+1),6)
ELSE
ExpStatus(lp)=0
ExpStatus(lp+1)=0
FI
FI
OD
RETURN
PROC BaseExplode()
;explode the base
BYTE ARRAY endx(0)=[16 24 17 23 20],
endy(0)=[22 22 19 19 17]
BYTE lp,time=20
color=38
FOR lp=0 TO 4 DO
Plot(20,22)
DrawTo(endx(lp),endy(lp))
OD
FOR lp=0 TO 16 DO
Sound(0,Fate,8,16-lp)
Sound(1,Fate,8,16-lp)
time=0
DO
UNTIL time=15
OD
OD
SndRst()
color=32
FOR lp=0 TO 4 DO
Plot(20,22)
DrawTo(endx(lp),endy(lp))
OD
RETURN
PROC EndRight()
;move the troopers from the right
;to the base
BYTE lp,lq,nn,time=20
FOR lp=0 TO 19 DO
IF Rr(lp)=1 THEN
lq=21+lp
WHILE lq>20 DO
IF nn=12 THEN
nn=13
ELSE
nn=12
FI
Plot0(lq,22,nn)
time=0
DO
UNTIL time=10
OD
Plot0(lq,22,0)
lq==-1
OD
Plot0(21,22,11)
FI
OD
FOR lp=0 TO 3 DO
Plot0(21,22-lp,11)
time=0
DO
UNTIL time=10
OD
OD
BaseExplode()
RETURN
PROC EndLeft()
;Move the troopers from the left to
;the base
BYTE lp,lq,lc,nn,time=20
FOR lp=0 TO 19 DO
lq=19-lp
IF Ll(lq)=1 THEN
FOR lc=lq TO 19 DO
IF nn=12 THEN
nn=13
ELSE
nn=12
FI
Plot0(lc,22,nn)
time=0
DO
UNTIL time=10
OD
Plot0(lc,22,0)
OD
Plot0(19,22,11)
FI
OD
FOR lp=0 TO 3 DO
Plot0(19,22-lp,11)
time=0
DO
UNTIL time=10
OD
OD
BaseExplode()
RETURN
PROC EndPrint()
;print the end of game message and
;test for new game
BYTE trig=644,lp
Position(10,7)
Print("Game Over...Final Score:")
Position(15,8)
PrintC(Score)
Position(15,9)
Print("FINAL LEVEL :")
PrintB(Level)
Position(10,20)
Print("Press FIRE to play again")
DO
UNTIL trig=0
OD
DownL=0
DownR=0
Put(125)
FOR lp=0 TO 19 DO
Ll(lp)=0
Rr(lp)=0
OD
Score=0
Level=1
DrawBase()
Update()
Hard=15
RETURN
PROC GameOverTwo()
;game over when four troopers down
BYTE lp
SndRst()
ClearScreen()
Loud=0
Loud1=0
Freq=169
FOR lp=0 TO 19 DO
IF Ll(lp)=1 THEN
Plot0(lp,22,11)
FI
IF Rr(lp)=1 THEN
Plot0(lp+21,22,11)
FI
OD
IF DownL=4 THEN
EndLeft()
ELSE
EndRight()
FI
EndPrint()
RETURN
PROC GameOverOne()
;game over when trooper lands on base
BYTE lp
SndRst()
ClearScreen()
Loud=0
Loud1=0
Freq=169
FOR lp=0 TO 19 DO
IF Ll(lp)=1 THEN
Plot0(lp,22,11)
FI
IF Rr(lp)=1 THEN
Plot0(lp+21,22,11)
FI
OD
BaseExplode()
EndPrint()
RETURN
PROC TrooperDown(BYTE wh)
;redraw trooper wh at bottom of screen
BYTE cc
TrStatus(wh)=0
cc=Trx(wh)
Plot0(Trx(wh),Try(wh),0) ;erase chute
Plot0(Trx(wh),Try(wh)+1,11) ;replace
IF Trx(wh)<20 AND Ll(cc)=0 THEN
Ll(cc)=1
DownL==+1
ELSEIF Trx(wh)>20 AND
Rr(cc-21)=0 THEN
Rr(cc-21)=1
DownR==+1
ELSEIF Trx(wh)=20 THEN
GameOverOne()
FI
IF DownL=4 OR DownR=4 THEN
GameOverTwo()
FI
RETURN
PROC TrooperFall()
;make trooper fall when chute hit
BYTE lp,qq,cc
FOR lp=0 TO 29 DO
IF TrStatus(lp)=2 THEN
Plot0(Trx(lp),Try(lp)+1,0)
Try(lp)==+1
IF Try(lp)=21 THEN
cc=Trx(lp)
IF Trx(lp)<20 AND Ll(cc)=1 THEN
DownL==-1
Ll(cc)=0
ELSEIF Trx(lp)>20 AND
Rr(cc-21)=1 THEN
Rr(cc-21)=0
DownR==-1
FI
FI
IF (Try(lp)<22 AND Trx(lp)<>20)
OR (Try(lp)<20 AND
Trx(lp)=20) THEN
Plot0(Trx(lp),Try(lp)+1,10)
ELSE
TrStatus(lp)=0
FI
FI
OD
RETURN
PROC MoveTroopers()
;move paratroopers down screen
BYTE lp,qq
BYTE ARRAY Trooper(0)=
[60 126 255 255 195 66 36 24
60 36 24 255 60 24 36 102
0 0 0 0 0 0 0 0]
FOR lp=0 TO Indx DO
Charset(56+lp)=0
OD
MoveBlock(Charset+56+Indx+1,
Trooper,16)
Indx==+1
IF Indx<8 THEN
RETURN
FI
Indx=0
FOR lp=0 TO 29 DO
IF TrStatus(lp)=1 THEN
Plot0(Trx(lp),Try(lp),0)
Try(lp)==+1
IF Try(lp)=21 THEN
TrooperDown(lp)
FI
FI
IF TrStatus(lp)=3 THEN
TrStatus(lp)=0
Plot0(Trx(lp),Try(lp)+1,0)
FI
OD
MoveBlock(Charset+56,Trooper,24)
FOR lp=0 TO 29 DO
IF TrStatus(lp)=1 THEN
Plot0(Trx(lp),Try(lp),7)
Plot0(Trx(lp),Try(lp)+1,8)
Plot0(Trx(lp),Try(lp)+2,9)
FI
OD
RETURN
PROC NewLevel()
;go to higher level
BYTE lp,time=20
Level==+1
IF Level>100 THEN
Level=100
FI
SndRst()
Loud=0
Loud1=0
Freq=169
Comp==+300
FOR lp=10 TO 150 STEP 10 DO
Sound(0,lp,10,4)
Sound(1,lp+10,10,4)
time=0
DO
UNTIL time=2
OD
OD
Position(25,23)
PrintB(Level)
IF Level>8 THEN
Hard=19
FI
SndRst()
RETURN
PROC Main()
BYTE time=20,lp,ch=764
Title()
Gr0Init()
Snd1=0
Snd2=3
Download()
Modify()
DrawBase()
ScoreLine()
DO
LaunchChopper()
MoveChopper()
MoveExplosions()
Noise()
TrooperFall()
MoveTroopers()
Position(8,23)
PrintC(Score)
IF Score>Comp THEN
NewLevel()
FI
time=0
FOR lp=2 TO 6 STEP 2 DO
AimGun()
Shoot()
MoveShots()
DO
UNTIL time=lp
OD
OD
OD
RETURN