Retrochallenge 2016/10:
Ironic Computer Space Simulator (ICSS)

Episode 7: Skeet in Space!

Meanwhile, we've scored a few milestones at once, like collision detection and torpedos, even some visual effects. In fact, we've everything in place required for a little game of skeet shooting in outer space. Best of all, removing most of the artificial throttling, we're now running at twice the frame rate, nurturing the best hopes regarding the outcome of this venture.
So it's high time for another writeup to catch up with the progress of our little project.

Ironic Computer Space Simulator: Shooting saucer

Saucer skeet in outer space.

Collisions

So far, we had a maneuverable ship and randomly moving saucers on the screen. The saucers were quite a bit of work, most of it dedicated to the few animated dots moving in the central "rotating" section of the saucers, and, quite perversely, to the idle phase of the saucers. (Just as a reminder, there are in total 4 phases out of two different idle behaviors, and the central animation is to be kept for any consecutive leg of movement.)

Now, we may actually do some with these objcts, maybe have them collide with each other?
I may be wrong, but I believe the original game to do some kind of pixel-wise hit detection, similar to what we see with sprites/MOBs in early game consoles and home computers. *) The next best option, in case we're lacking the hardware for this, is to detect collisions by comparing radii. But this involves time consuming operations like multiplications, even, if we opted for comparing distances to squared radii and thus saving the runtime required for costly square roots.

*) Update: I stand corrected. The original game features a rather generous hit detection and ships collide even at still a distance.

Moreover, having a closer look at our saucers, they are, while ideally disk-shaped in our imagination, far from disk-shaped on the screen. Actually, some kind of vertically compressed hit box would do the job. Maybe something that would be more like an octagon instead of a circular area?

Enter Spacewar! and the solution Steve Russell came up with:

Hitboxes in Spacewar!

Hitboxes in Spacewar!
( ε1 ≥ |dx| )  &&  ( ε1 ≥ |dy| )  &&  ( ε2 ≥ |dy| - ε1 + |dx| )

This is next to ideal for our purpose. Instead of using the same epsilon for both dx and dy, we're going to use distinctive thresholds, thus achieving our vertical compression. We move the code into a subroutine and set up the values of the various epsilons with regard to the objects we're gong to compare. As we're checking for any overlap, the sums of the vertical and horizontal raddi (half the side-lengths), respectively, will do. And of course, we've to set up the respective center cooordinates of the objects, we're going to compare, as well.

Here, we've to remind ourselves that the rotational center of the rocket ship is not its geometrical center. In fact, it's somewhere below, in the "umbilical" region of the ship. Therefor, we've to subtract an offset based on the current sine and cosine of the ships rotational angle. As this happens to be just half the offset we already used to get the tip of the ship for the drawing routine, we're just storing this when we're inside the rockte ship's routine to be reused here.

Here's a quick look at the actual code for comparing the player's ship with the first of the two saucers:


	lac \rpx		/get rotational center of rocket
	sub \rxc
	dac \px
	lac \rpy
	add \ryc
	dac \py

	law \px			/set up rocket for comparison
	dap mx1
	law \py
	dap my1

	lac (21000		/set up collision radii (x 34, y 24) in screen locs
	dac \dxe
	sar 1s
	dac \de2
	lac (14000
	dac \dye

	law \upx		/first saucer
	dap mx2
	law \upy
	dap my2
	jsp dmf			/compare them
	sza			/hit?
	jmp dxr			/yes

(...)

/subroutine to compare object positions (from spacewar, mod n.l.)
dmf,	dap dmx
mx1,	lac .			/calc if collision
mx2,	sub .			/delta x
	spa			/take abs val
	cma
	dac \t
	sub \dxe		/ < epsilon x ?
	sma
	jmp dm0			/no
my1,	lac .
my2,	sub .
	spa
	cma
	sub \dye		/ < epsilon y ?
	sma
	jmp dm0			/no
	add \t
	sub \de2		/ < epsilon 2 ?
	sma
	jmp dm0
	law 1			/return 1 (in ac) for hit
	jmp dmx
dm0,	cla			/return 0 for miss
dmx,	jmp .

But, aren't there two of the saucers on the screen? — Yes and no: It's more like a single saucer appearing twice at a constant vertical offset. Therefor, all we have to do is to set up a temporary value with the offset added for a new vertical position (my2) and go for another round, using otherwise the parameters, we've set up earlier.

By this, we should be able to detect any ship-to-saucer collisions. So, what's going to happen in case, we were actually detecting one?

Symbolic Death (© Stewart Brand)

As indicated by the subtitle, the jest of death is in the notice, even more so for a video game. Therefor, it's of uttermost importance that the event should be symbolized to the player in a proper and orderly manner.

Computer Space doesn't disappoint here, informing us of the tragic event by a spinning animation of the ship. (The saucers are respawned quickly after a short flashing of the screen, making this a proper twitch game.) And, as this is just a symbolic death, live goes on and the player's rocket is repositioned on the screen, just as it was, in order to commence with the short-lived but epic quest of shooting saucers.

This is rather easy to implement: We just trick the rocket routine into performing a clockwise rotation, but at twice the speed. At second thought, we make this a progressively accelerating spinning motion, starting rather slowly, at twice the speed of a normal turn, becoming quicker and quicker, until it just stencils a nice dotted pattern into the long sustain of the Type 30 CRT. For this, we've to add some counters, but are paid off by a rather nice, while ephimeral effect.

Symbolic death in ICSS.

Symbolic death in Ironic Computer Space Simulator.
(Hi-res screenshot of the emulation.)

Space Pigeons, or, Gosh, What I'm Supposed to Do with a Torpedo?

In the Apollo era, NASA dubbed any unidentified lunar emanations Moon pigeons, but, in spite of this code name, astronauts restrained their lunar sports activities to golf only, that is, as far as we know it. :-)
But we're here for some serious sport and are facing the by far fiercer space pigeon — and there's no room for error. So we'd better do something about it…

With hit detection already in place, it would be nice to implement a torpedo (that is, a photon torpedo, of course), so that we may have something to shoot at, while testing the program. This is rather straight forward, again, as there is just a single torpedo at a time and it's about just a single dot on the screen, advancing by the constant delta of its velocity. That is, if it wouldn't be for a peculiar property of Computer Space, namely guided missiles.

In Computer Space, if you've fired a torpedo (or missile) and happen to turn your ship, the torpedo is altering its direction, too. Steerable torpedos! Just think of it and the possibilities in gameplay! But, judging from footage of the game, the steering is rather quirky and not too easy to master — otherwise, the game might be to easy.

We've to come up with a similar solution. Additionally, the trail of the torpedo will be pretty visible in the afterglow of the Type 30 CRT, so it should be an aesthetical pleasing solution, as well. We implement this, quite like we did it for the acceleration of the ship, by a combination of frame skips (delayed response) and repetitive averaging with the old movement delta (for damping), where we derive the new factor from the scaled unit vector of the ship's current rotational angle.

In fact, we even implement two solutions to the problem: A default one, where torpedo steering only applies when the ship is actually rotated (similar to what is seen in footage of the game), and another one, to be activated by sense switch 3, where the torpedo eventually follows the rotation of the ship, but in a delayed manner at a greater turning radius. (In fact, this is just skipping the test for the turn controls currently being employed, but effects in an aesthetical pleasing curve and better control.)

Another peculiarity of Computer Space to be mentioned here, torpdoes are actually fired from the belly of the ship, its rotational center, and not from its bow — this is not a mistake.

Computer Aesthetics

Speaking of computer aesthetics with respect to the platform, the screen isn't the only thing to be considered. We are using some of the program flags, either to control the various passes and states of the drawing routines, or to trace the state of the player controls, and, as we've to repeatedly check for the player's rocket active state, we use another flag for this. Now, the program flags are implemented in hardware on the PDP-1, and there are console lights for them on the operator's console. And not only is the state of the flag register clearly visible to everyone near the machine, so is our use of it, as well.

DEC PDP-1 running Spacewar! at the CHM.

PDP-1 running Spacewar! at the CHM during restoration work in 2005. (Image: The PDP-1 Restoration Team, 2005.)
Console lights for the flag register on the lower right of the operator's console, just above the lights for the instruction register.

Therefor, in order to deliver a proper program, we should pay attention to this part of the runtime experience, too. In our program, the indicators for flags 5 and 6 (at the right) will flash rapidly as we make heavy use of them in the drawing routines. With flag 4 being practically taboo most of the time, we've to consider a pleasing use of flags 1 to 3. We'll use flag 1 (at the left) for the active state of the player's rocket (it will flash each frame of an ongoing game, being on most of the time), flag 2 for thrust, and, finally, flag 3 for the torpedo trigger. Thus, the lights will give a somewhat coordinated feedback of the game's state and the player action as read by the machine.

Speaking of the experience and platform specific considerations, another thing to consider is the tactical behavior of the game. The saucers are meant to act randomly, but, as it is, with our random number generator implemented in software and nothing but our own program on the machine to be used as an additional source of randomness, the game is rather showing a deterministic, patterned behavior. So we really should look for some randomness and variety that even a single additional run of the random generator could provide. Looking around, the only additional source there is, is the player and the interaction via the control input. Since this input is that fine in granularity that we've, in fact, to skip most of it in order to model the behavior of Computer Space, there's some randomness in it: It's near to impossible that a player should be able to press any of the buttons exactly as long in two separate runs of the game. So, for the sake of randomness, we run the random number generator each time, we register the signal for the rocket to thrust. Therefor, even when starting fresh from a previously cleared memory (core memory is nonvolatile!), there should always be different games.

Pyrotechnics

Returning to the torpedo and its course across the screen, we'll probably more often miss than hit — otherwise it may be quite a boring game. But, in case we score a hit, we should be notified properly by an attractive experience, since shooting at and eventually hitting saucers is the core purpose of this game, isn't it? The visual feedback of Computer Space is rather harsh, just a short flashing of the screen in reverse video. But there's sound — and did we mention already? —, it's a sound of rather peculiar nature.

However, we can't do any of this on the PDP-1. Reverse video is impossible with a X/Y random access display and — while Peter Samson famously did impressive playback of Bach on the PDP-1 by hoooking up special circuitry to the flag register (see this video of Lyle Bickley explaining the details) —, there's no sound at all on a default PDP-1. Thus, we've to come up with a solution of our own. Maybe a visual animation of an explosion?

Visual explosions by rendering some particles at a random offset should be rather simple, even more on a painted display. But, alas, what's supposed to be simple on paper rarely is so in real-life implementation. So it is in the case of our particle explosion. Just using some scaled random numbers as an offset to the central screen location of our effect won't do, since it shows pretty much the confining box of the scaled random number.

Obviously, we are in nedd of some additional random scaling for any of the offsets of our distinctive particles. Playing around with it, I'm always close to the solution found in Spacewar! (maybe, I've just seen too much of it). But, this isn't only the first particle explosion in history for a computer game, it's one of the most beautiful, ever — and it is already custom tailored to the specific display and hardware. A bespoke animation, to say the least. Therefor, we may have a closer look at this one, in order to recklessly steal it for our own purpose.

As I understand it, the explosion routine was originally written by J. M. Graetz and was later refined by Steve Russell in version 3 and never to be changed thereafter. First, the explosion graphics suffered from the very disease already mentioned above, a more than obvious confining box of the effect,

"(…) appropriately-named Crock Explosion. Something dramatic obviously had to happen when a ship was destroyed, but we were dealing with a plain dot-matrix screen. The original control program produced a random-dot burst confined within a small square whose outlines were all too discernible."
(J. M. Graetz, The Origin of Spacewar, Creative Computing, August issue 1981.)

The cure applied by Steve Russell was essentially an additional random shift, assembled on the fly for each of the particles. Further, the final explosion came at two sizes, expanding at a wide scale at the beginning and ending in smaller spread, with a gradually diminishing number of particles.

For our purpose, we modify the explosion routine a bit (at least, to show that we have a decent understanding of the code, we steal). We're going with three phases, starting small, expanding wider in the middle, and ending small again. And we're going to tune the scaling parameters to fit our purpose. Moreover, our explosion will be rather short in duration, thus we really go for it and draw a lush octal 30 (decimal 24) particles each of the frames.

Here's the code:

/saucer explosion (similar to spacewar)
/ jump here from inside ufo

uxe,	law uxs			/set up instr at uxs to be xtc-ed in ux1
	dap ux1
	lac \ufs
	add (30			/done with explosion, 030 frames setup delay
	sma
	jmp ux3
	sal 1s
	dac \uxc		/repeat for 030 particles
	add (50			/first and last frames small explosion
	spa
	jmp ux0
	sub (30
	sma
	idx ux1			/increment addr in ux1 for bigger explosion
ux0,	random			/start of loop, get random number
	and (6s			/constrain to lowest 6 bits (as no of shifts)
	ior (scl		/assemble a shift instruction for these
	dac ux2			/insert it at ux2
	random			/new random number
	cli
	scr 9s			/split it into lower parts of ac and io
	sir 9s
ux1,	xct .			/apply scaling (size)
ux2,	opr			/apply shift for random spread
	add \upy		/add center pos x/y
	swap
	add \upx
	dpy -i 100		/display a  dot
	isp \uxc		/redo \uxc times
	jmp ux0
ux3,	isp \ufs		/increment status counter and skip next on positive
	jmp ufi			/exit, jump to end of ufo routine (saucers)
	jsp urs			/last iteration, respawn saucers
	jmp ufi			/exit

uxs,	scl 1s			/scaling for small explosion
	scl 2s			/ and bigger one, to be xct-ed at ux1

And this is what we get:

Explosion effect in Ironic Computer Space Simulator

Explosion effect in Ironic Computer Space Simulator.
Yet another particle effect.

Experience the code live:   www.masswerk.at/icss/.

*****

Update/Note: While we were completele happy with this effect — and it was a beautiful one —, we eventually replaced it by something other that was not as much a Spacewar! trademark visual and more something of our own.

*****

By this we've already arrived at a somewhat playable game. Still missing are the shots by the saucers and scoring, — and probably some fine-tuning, too.

However, we're that proud of our progress that we leave the shabby outskirts of pre-prerelease revisions behind and enter, by skipping version 0.09, the gleaming prerelease territory of 0.x-code. (At least, if Microsoft is allowed to skip Windows 9, why shouldn't we dare to skip version 0.09?)

And, last but not least, we remove most of the throttling applied to the code, thus running nearly at full speed, resulting in more than twice the frame rate we had before. (Thus, we're not too displeased with having to readjust most of the frame counts, skips, and scalings.) In fact, the game might be even a bit too fast — and we've still some buffer left. Therefor, we shouldn't face any severe timing issues, even, when we eventually are going to implement the score display.

And here's our growing code, so far:

ironic computer space  0.1  nl 2016-10-23

	mul=mus			/instruction code for multiplication
	div=dis			/instruction code for division
	ioh=iot i		/wait for completion pulse (CRT display)
	szm=sza sma-szf		/skip on ac zero or minus
	spq=szm i		/skip on ac plus and not zero

define initialize A,B
	law B
	dap A
	term

define index A,B,C
	idx A
	sas B
	jmp C
	term

define swap
	rcl 9s
	rcl 9s
	term

define load A,B
	lio (B
	dio A
	term

define setup A,B
	law i B
	dac A
	term

define count A,B
	isp A
	jmp B
	term

/macros specific to the program

define scale A,B,C
	lac A
	sar B
	dac C
	term

define random
	lac ran
	rar 1s
	xor (355671
	add (355671
	dac ran
	term


/ Computer Space
/ Original arcade game by Nolan Bushnell and Ted Dabney,
/ Syzygy Engineering / Nutting Associates, 1971.
/ 'A simulated space battle that pits
/  computer-guided saucers against
/  a rocket ship that you control.'


3/	jmp sbf			/ ignore seq. break
	jmp a0			/ start addr, use control boxes
	jmp a1			/ alt. start addr, use testword controls


/game parameters

raa,  6,	1200		/ rocket angular acceleration
rvl,  7,	sar 8s		/ scaling rocket velocity
ras, 10,	law i 6		/ rocket acceleration sensitivity (skip)
rad, 11,	law i 4		/ rocket acceleration damping (averaging)
trv, 12,	sar 7s		/ scaling torpedo velocity
tlf, 13,	law i 250		/ torpedo life
rsd, 14,	40		/ rocket reset delay (frames)
ran, 15,	0		/ random number


/routine to flush sequence breaks, if they occur.

sbf,	tyi
	lio 2
	lac 0
	lsm
	jmp i 1


/sine-cosine subroutine - Adams associates
/calling sequence= number in AC, jda sin or jdacos.
/argument is between +/- +2 pi, with binary point to right of bit 3.
/answer has binary point to right of bit 0.  Time = 2.35-? ms.
/changed for auto-multiply , ddp 1/19/63

cos,	0
	dap csx
	lac (62210
	add cos
	dac sin
	jmp .+4

sin,	0
	dap csx
	lac sin
	spa
si1,	add (311040
	sub (62210
	sma
	jmp si2
	add (62210

si3,	ral 2s
	mul (242763
	dac sin
	mul sin
	dac cos
	mul (756103
	add (121312
	mul cos
	add (532511
	mul cos
	add (144417
	mul sin
	scl 3s
	dac cos
	xor sin
	sma
	jmp csx-1
	lac (377777
	lio sin
	spi
	cma
	jmp csx

	lac cos
csx,	jmp .

si2,	cma
	add (62210
	sma
	jmp si3
	add (62210
	spa
	jmp .+3
	sub (62210
	jmp si3

	sub (62210
	jmp si1


/subroutines for background display

nos=77 	/number of stars

/table of stars coors (nos words, 9 bits x and 9 bits y)
bst,	. nos/

/setup (nos random coors starting at bst)

bsi,	dap bsx				/ deposit return address
	init bsc, bst			/ deposit first addr of bst in bsc
bsl,	random				/ get a new random number
bsc,	dac .				/ store it in current loc of st
	index bsc, (dac bst+nos, bsl	/ increment bsl, repeat at bgl for nos times
bsx,	jmp .				/ return


/display background stars (displays every 2nd frame only)

bg,	dap bgx				/ deposit return address
	lac \frc			/ check frame conter
	and (1
	sza				/ skip every second frame
	jmp bgi				/ jump to star display
	szs i 10			/ sense switch 1 for parallax effect
	jmp bgd
	lac \rdx
	szs i 20			/ sense switch 2 for stronger effect
	jmp . 3
	sar 1s
	add \rdx
	sar 3s
	cma
	add bgh
	dac bgh
	lac \rdy
	szs i 20
	jmp . 3
	sar 1s
	add \rdy
	sar 3s
	cma
	add bgv
	dac bgv
	jmp bgx				/ return
bgd,	lac \frc			/ advance x offset slowly
	and (177
	sza
	jmp bgx
	law i 400
	add bgh
	dac bgh
	jmp bgx				/ return
bgi,	init bgl, bst			/ init bgl to first addr of stars table
bgl,	lac .				/ get coors of next star (x/y)
	cli				/ clear io
	scr 9s				/ shift low 9 bits in high 9 bits of io (x)
	sal 9s				/ move remaining 9 bits in ac in high part (y)
	add bgv				/ add vertical offset
	swap				/ swap contents of ac and io
	add bgh				/ add horizontal offset
	dpy-i				/ display a dot at coors in ac (x) and io (y)
	index bgl, (lac bst+nos, bgl	/ repeat the loop at bgl nos times
bgx,	jmp .				/ return

bgh,	0
bgv,	0


/here from start

a0,	law rcb			/configure to read control boxes (sa 4)
	dap rcw
	jmp a2

a1,	law rtw			/configure to read testword (sa 5)
	dap rcw

/start a new run

a2,	jsp bsi			/ initial setup of bg-stars
	dzm bgh			/ reset offsets of stars to zero
	dzm bgv

/rocket setup

ar,	lac (240000
	dac \rth		/ rotational angle
	lac (-200000
	dac \rpx		/ pos x
	cma
	dac \rpy		/ pos y
	law 300
	dac \rdx
	law i 500
	dac \rdy
	dzm \rac		/ acceleration skip counter
	dzm \rxc		/ reset center offset
	dzm \ryc
	law 1
	dac \rks		/status
	dzm \trs		/torpedo status counter

/saucer setup

au,	lac (200000
	dac \upy		/ pos x
	lac (140000
	dac \upx		/ pos y
	dzm \udc		/ animation skip counter
	law 1
	dac \udd		/ animation direction
	dzm \umo		/ direction code
	law 600
	dac \udy		/ delta y
	dzm \udx		/ delta x
	law i 100
	dac \ufc		/ duration of movement
	law 1
	dac \uft		/ speed of center animation (1,3)
	law 1
	dac \ufs		/ state counter

/main loop

fr0,	load \ict, -2100	/ initial instruction budget (delay)
	idx \frc		/ increment frame counter
	clf 1				/flag ship active

	jsp bg			/ display background
	jsp rkt			/ rocket routine
	jsp ufo
	lac \trs
	sza
	jsp tr
	jsp df			/ check collisions

	count \ict, .		/ use up rest of time of main loop
	jmp fr0			/ next frame


/collision / hit detection

df,	dap dfx
	lac \ufs		/check saucer state
	spq			/skip on positive
	jmp dfx
	lac \rks		/check rocket state
	spq
	jmp dfx

	lac \rpx		/get rotational center of rocket
	sub \rxc
	dac \px
	lac \rpy
	add \ryc
	dac \py

	law \px			/set up rocket for comparison
	dap mx1
	law \py
	dap my1

	lac (21000		/set up collision radii (x 34, y 24) in screen locs
	dac \dxe
	sar 1s
	dac \de2
	lac (14000
	dac \dye

	law \upx		/first saucer
	dap mx2
	law \upy
	dap my2
	jsp dmf			/compare them
	sza			/hit?
	jmp dxr			/yes

	lac \upy		/second saucer
	sub (400000		/vertical offset
	dac \vpy
	law \vpy
	dap my2
	jsp dmf			/compare them
	sza			/hit?
	jmp dxr			/yes

dft,	lac \trs		/torpedo active
	sma
	jmp dfx
	lac (12200		/setup hit box (x 21, y 10)
	dac \dxe
	sar 1s
	dac \de2
	lac (5000
	dac \dfy
	law \tpx		/set up torp as first object
	dap mx1
	law \tpy
	dap my1
	jsp dmf			/compare
	sza
	jmp dxu			/hit, respawn

	law \upy
	dap my2
	jsp dmf			/compare them
	sza			/hit?
	jmp dxu
	jmp dfx


dxr,	law i 100		/set rocket state to collision
	sub rsd			/additional delay before reset
	dac \rks
	dzm \rdx
	dzm \rdy
	dzm \trs
	lac \rth
	dac \th0
	dzm \thc
	law i 10
	dac \thd
	jsp urs			/respawn saucers
	jmp dfx

dxu,	dzm \trs		/set up saucer explosion
	lac i my2		/set up pos y
	dac \upy
	law i 30 30
	dac \ufs

dfx,	jmp .			/return


/subroutine to compare object positions (from spacewar, mod n.l.)
dmf,	dap dmx
mx1,	lac .			/calc if collision
mx2,	sub .			/delta x
	spa			/take abs val
	cma
	dac \t
	sub \dxe		/ < epsilon x ?
	sma
	jmp dm0			/no
my1,	lac .
my2,	sub .
	spa
	cma
	sub \dye		/ < epsilon y ?
	sma
	jmp dm0			/no
	add \t
	sub \de2		/ < epsilon 2 ?
	sma
	jmp dm0
	law 1			/return 1 (in ac) for hit
	jmp dmx
dm0,	cla			/return 0 for miss
dmx,	jmp .


/saucers

ufo,	dap ufx
	lac \ufs
	spq
	jmp uxe
	clf 6

	lac \upx		/update position
	add \udx
	dac \upx
	dac \px
	lac \upy
	add \udy
	dac \upy
	dac \py
	jsp sd			/display first saucer
	lac \py
	sub (400000		/half-screen vertical offset
	dac \py
	jsp sd			/display second saucer

	isp \ufc		/increment leg counter
	jmp uf1			/continue
	lac \umo		/new direction
	spa
	jmp uza			/we're in a 3-steps stop (\umo: -3..-1)
	sar 3s			/ 010 set for single step stop
	sza
	jmp uz1
	random
	sma
	jmp uf2			/set up new leg

	dzm \udx		/stop
	dzm \udy
	lac ran			/what kind of stop will it be?
	ral 3s
	spa
	jmp uz0			/three-steps stop
	law 10			/single-step stop, keep center animation
	dac \umo
	jmp uzb			/get delay and continue
uz0,	law i 3			/first period of three-steps stop
	dac \umo		/reuse \umo as step counter (negative)
	law 3
	dac \uft		/set animation to slow (3)
uzb,	jsp utd			/get duration
	sar 1s
	dac \ufc
	jmp ufi			/continue

uza,	cma			/3-steps stop, dispatch on -\umo (-3..-1)
	add (. 3
	dap . 2
	idx \umo
	jmp .
	jmp uz1
	jmp uz2
uz3,	dzm \udd		/3 - stop animation
	jmp . 2
uz2,	jsp ucd			/2 - still stopped, new animation direction
	jsp utd
	sar 1s
	dac \ufc
	jmp ufi
uz1,	stf 6			/1 - start over, but keep animation
	random
	jmp . 2

uf2,	clf 6			/set up for a new leg (flag 6: keep anim. dir.)
	and (7
	dac \umo		/new motion code (0..7)
	sal 1s			/read corresponding dx/dy form table umt
	add (umt		/ setup reference location for dy in \t1
	dac \t1
	lac i \t1		/load dy indirectly from addr. in \t1
	dac \udy
	idx \t1			/increment addr. in \t1
	lac i \t1		/load dx indirectly from addr. in \t1
	dac \udx
	szf 6 i			/skip next on flag 6
	jsp ucd			/set new direction for animation
	jsp utd			/get delay
	dac \ufc

uf1,	lac \frc		/increment center animation
	and \uft
	sza
	jmp ufi
	lac \udc		/udc = 0..5
	sub \udd
	dac \udc
	sma
	jmp . 4
	law 5
	dac \udc
	jmp ufi
	sad (6
	dzm \udc

ufi,	lac \ict		/update instruction count
	add (1200
	dac \ict

ufx, jmp .


ucd,	dap ucx			/subroutine to set center animation
	lio ran
	ril 1s
	law 1
	spi
	cma
	dac \udd
	lac ran			/animation speed, favor faster
	rar 9s
	and (3
	sza
	jmp . 3
	law 1
	jmp . 2
	law 3
	dac \uft
ucx,	jmp .

utd,	dap utx			/subroutine to get random delay (leg length)
	random
	sar 9s
	sar 3s
	sub (270
utx,	jmp .

urs,	dap urx			/subroutine to respawn the saucers
	lac ran			/random offset x to player's rocket
	rar 9s
	sar 2s
	dac \upx
	sar 1s
	add \upx
	add (400000
	add \rpx
	dac \upx
	random			/random pos y
	dac \upy
	jsp ucd			/set up center animation
	dac \udd
	law i 40		/set up delay before next leg
	dac \ufc
	law i 1			/stopped (end of 3-steps stop)
	dac \umo
	law 1			/set state to active
	dac \ufs
urx,	jmp .

/saucer movement table (dy, dx)
umt,	 600		   0
	-600		   0
	   0		-600
	   0		 600
	 400		 400
	-400		-400
	-400		 400
	 400		-400

/saucer explosion (similar to spacewar)
/ jump here from inside ufo

uxe,	law uxs			/set up instr at uxs to be xtc-ed in ux1
	dap ux1
	lac \ufs
	add (30			/done with explosion, 030 frames setup delay
	sma
	jmp ux3
	sal 1s
	dac \uxc		/repeat for 030 particles
	add (50			/first and last frames small explosion
	spa
	jmp ux0
	sub (30
	sma
	idx ux1			/increment addr in ux1 for bigger explosion
ux0,	random			/start of loop, get random number
	and (6s			/constrain to lowest 6 bits (as no of shifts)
	ior (scl		/assemble a shift instruction for these
	dac ux2			/insert it at ux2
	random			/new random number
	cli
	scr 9s			/split it into lower parts of ac and io
	sir 9s
ux1,	xct .			/apply scaling (size)
ux2,	opr			/apply shift for random spread
	add \upy		/add center pos x/y
	swap
	add \upx
	dpy -i 100		/display a  dot
	isp \uxc		/redo \uxc times
	jmp ux0
ux3,	isp \ufs		/increment status counter
	jmp ufi
	jsp urs			/last iteration, respawn saucers
	jmp ufi

uxs,	scl 1s			/scaling for small explosion
	scl 2s			/ and bigger one, to be xct-ed at ux1


/player rocket routine

rkt,	dap rkx
	clf 2
	clf 3
	lac \rks		/check for collision state
	szm			/ < 0 ?
	jmp rka			/no
	isp \rks		/manage rocket hit state
	jmp rke
	idx \rks
	lac \th0
	dac \rth
	jmp rcw
rke,	add rsd
	sma
	jmp rkx

rt1,	lac \thc		/animate collision, spin clockwise progressively
	dac \thn
	lio raa			/angular acceleration
rt2,	sil 1s
	isp \thn		/shift left \thd times
	jmp rt2
	isp \thd
	jmp rt3
	law i 20		/add another shift every 20 frames
	dac \thd
	lac \thc
	sub (1
	dac \thc
rt3,	swap
	cma cli -opr		/make it a clockwise spin, clear io
	add \rth		/update rotational angle
	dzm \cw			/and continue with cleared control input
	jmp rkr

rka,	stf 1			/start active rocket
rcw,	jsp .			/read control word (ccw, cw, trust, fire)
	dio \cw			/merge (or) spacewar player inputs
	cla
	rcr 4s
	ior \cw
	dac \cw

	lio \cw			/parse and process rotation
	lac \rth		/load angle
	spi			/sign cleared in io?
	add raa			/no, add angular acceleration
	ril 1s			/next bit
	spi
	sub raa
rkr,	sma			/normalize 0 >= angle >= 2Pi (0311040)
	sub (311040
	spa
	add (311040
	dac \rth		/update angle

	ril 1s			/parse thrust input
	spi
	stf 2			/set flag 2 for thrust
	ril 1s
	spi
	stf 3			/set flag 3 for torp
	jda sin			/get sin, store it in \sn
	dac \sn
	lac \rth
	jda cos			/get cos, store it in \cs
	dac \cs

	szf i 2			/flag 5 set? update dx / dy
	jmp rk0
	lac \frc		/load frame counter
	isp \rac		/sensitivity, frame skip
	jmp rk1
	xct ras			/reset counter
	dac \rac
	lac \sn			/dx
	cma
	xct rvl			/apply scaling for acceleration
	swap			/swap result into io
	xct rad			/damping, get loop count
	dac \rdc
rx0,	swap			/get intermediate value from io
	add \rdx		/average with old dx
	sar 1s
	swap			/swap into io
	isp \rdc		/increment loop
	jmp rx0
	dio \rdx		/store updated dx
	lac \cs			/same for dy
	xct rvl
	swap
	xct rad
	dac \rdc
ry0,	swap
	add \rdy
	sar 1s
	swap
	isp \rdc
	jmp ry0
	dio \rdy
	jmp rk1

rk0,	dzm \rac

rk1,	scale \sn, 4s, \sn1	/get position of rocket tip
	sar 2s			/offset x = (sin >> 4) - (sin >> 8)
	cma
	add \sn1
	dac \sn1
	scale \cs, 4s, \cn1	/offset y = (cos >> 4) - (cos >> 8)
	sar 2s
	cma
	add \cn1
	dac \cn1
	lac \rpx		/update pos x (add dx)
	add \rdx
	dac \rpx
	sub \sn1		/subtract offset for tip, store it in \px
	dac \px
	lac \rpy		/same for y
	add \rdy
	dac \rpy
	add \cn1
	dac \py

	scale \sn1, 1s, \rxc	/store half-offset for hit detection
	scale \cn1, 1s, \ryc

	scale \sn, 6s, \sn8	/scaled sine, 8 steps
	sar 1s
	dac \sn4		/4 steps
	sar 1s
	dac \sn2		/2 steps
	sar 1s
	dac \sn1		/1 step
	dac \sm1
	add \sn2
	dac \sn3		/3 steps
	dac \sm3
	add \sn2
	dac \sn5		/5 steps
	add \sn1
	dac \sn6		/6 steps
	dac \sm6

	scale \cs, 6s, \cn8	/scaled cosine, 8 steps
	sar 1s
	dac \cn4		/4 steps
	sar 1s
	dac \cn2		/2 steps
	sar 1s
	dac \cn1		/1 step
	dac \cm1
	add \cn2
	dac \cn3		/3 steps
	dac \cm3
	add \cn2
	dac \cn5		/5 steps
	add \cn1
	dac \cn6		/6 steps
	dac \cm6

	jsp rod			/display it

	szf i 3			/fire torpedo?
	jmp rkq			/no
	szf i 1			/are we active?
	jmp rkq			/no
	lac \trs
	sza			/torpedo already active?
	jmp rkq			/yes
	xct tlf			/set up torpedo
	dac \trs
	lac \rpx		/copy position
	dac \tpx
	lac \rpy
	dac \tpy
	lac \sn			/dx
	cma
	xct trv			/apply scaling for velocity
	dac \tdx
	lac \cs			/dy
	xct trv
	dac \tdy

rkq,	szf i 2			/advance random on thrust
	jmp rki			/ to prevent patterned behavior of saucers
	random

rki,	lac \ict		/update instruction count
	add (600
	dac \ict

rkx,	jmp .


/torpedo (rocket)

tr,	dap trx
	isp \trs		/count up status counter (zero = inactive)
	jmp . 2
trx,	jmp .
	szf i 1
	jmp tr1
	lac \frc		/frame skip
	and (3
	sza
	jmp tr1
	szs 30			/sense switch 3 for high agility
	jmp tr2
	lac \cw			/check control input for rotation (ccw, cw)
	and (600000
	sza i			/steering?
	jmp tr1			/no
tr2,	lac \cs			/update with steering
	xct trv			/scale cos of rocket
	add \tdy		/average 3 times with current dy
	sar 1s
	add \tdy
	sar 1s
	add \tdy
	sar 1s
	dac \tdy		/store updated dy
	add \tpy		/update pos y
	dac \tpy
	swap
	lac \sn			/same for sine, dx and pos x
	cma
	xct trv
	add \tdx
	sar 1s
	add \tdx
	sar 1s
	add \tdx
	sar 1s
	dac \tdx
	add \tpx
	dac \tpx
	dpy -i			/display adot and jump to return
	jmp trx
tr1,	lac \tpy		/no steering, simple update
	add \tdy
	dac \tpy
	swap
	lac \tpx
	add \tdx
	dac \tpx
	dpy -i
	jmp trx

/control word get routines

/read control boxes
rcb,	dap rcx
	cli
	iot 11
rcx,	jmp .

/read testword
rtw,	dap rtx
	lat
	swap
rtx,	jmp .


/rocket display
/ step rearwards -  x add \sn1, y sub \cn1
/ step outwards  -  x add \cm1, y add \sm1
/ step inwards   -  x sub \cm1, y sub \sm1

define disp
	dpy-i 100		/display a dot at brightness level +1
	term

rod, 	dap rox
	stf 6			/set flag 6
	lac \px
	lio \py
	disp

rop,	swap			/y +4, x +6
	sub \cn4
	add \sm6
	swap
	add \sn4
	add \cm6
	disp

	swap			/y +5, x +3
	sub \cn5
	add \sm3
	swap
	add \sn5
	add \cm3
	disp

	swap			/y +6, x +1
	sub \cn6
	add \sm1
	swap
	add \sn6
	add \cm1
	disp

	swap			/y +6, x -1
	sub \cn6
	sub \sm1
	swap
	add \sn6
	sub \cm1
	disp

	swap			/y +8, x +6
	sub \cn8
	add \sm6
	swap
	add \sn8
	add \cm6
	disp

	swap			/y 3, x -10
	sub \cn3
	sub \sm3
	sub \sm6
	sub \sm1
	swap
	add \sn3
	sub \cm3
	sub \cm6
	sub \cm1
	disp

	swap			/y +7, x +6
	sub \cn8
	add \cn1
	add \sm6
	swap
	add \sn8
	sub \sn1
	add \cm6
	disp

	szf i 6			/flag 6 set? (skip on not zero)
	jmp rot
	clf 6			/clear flag 6
	lac \cm1		/invert unit vector components
	cma
	dac \cm1
	lac \sm1
	cma
	dac \sm1
	lac \cm3
	cma
	dac \cm3
	lac \sm3
	cma
	dac \sm3
	lac \cm6
	cma
	dac \cm6
	lac \sm6
	cma
	dac \sm6
	lac \px			/load first pos
	lio \py
	jmp rop			/second pass for other side

rot,	szf i 2			/no, flag 2 set?
rox,	jmp .			/no, return
	swap			/draw exhaust flame
	sub \sm6		/x -11
	sub \sm6
	add \sm1
	swap
	sub \cm6
	sub \cm6
	add \cm1
	dac \px			/store position
	dio \py
	lac \frc		/load frame counter
	and (4
	sza			/is it zero? (state switch)
	jmp ro2
ro1,	lac \py			/state 1, display at y-1
	add \cn1
	swap
	lac \px
	sub \sn1
	disp
	jmp rox
ro2,	lac \py			/state 2, display at y+5
	sub \cn5
	swap
	lac \px
	add \sn5
	disp
	jmp rox			/jump to return


/saucer display

define sdisp B			/display a dot (opt. brightness parameter)
	dpy -4000 B		/ request completion pulse
	term

sd,	dap sdx
	clf 5
	clf 6
	law i 4
	dac \sdc

sdl,	law 5000		/y +/- 10, x +/- 4
	szf 6
	cma
	add \py
	swap
	law 2000
	szf 5
	cma
	add \px
	disp

	law 3400		/y +/- 7, x +/- 7
	szf 6
	cma
	add \py
	swap
	law 3400
	szf 5
	cma
	add \px
	disp

	law 3400		/y +/- 7, x x+/- 15
	szf 6
	cma
	add \py
	swap
	law 7400
	szf 5
	cma
	add \px
	disp

	lac \sdc		/dispatch on \sdc (-4..-1) for passes (set flags)
	cma
	add (sdd
	dap sdd
	idx \sdc		/increment counter and jump
sdd,	jmp .
	jmp sd1
	jmp sd2
	jmp sd3
	stf 6			/2nd pass
	jmp sdl
sd3,	stf 5			/3rd pass
	jmp sdl
sd2,	clf 6			/4th pass
	jmp sdl
sd1,	lio \py			/done, display outer dots
	lac (12400		/y 0, x + 21 (right side)
	add \px
	sdisp 100
	lac (-12400		/y 0, x - 21 (left side)
	add \px
	ioh
	sdisp 100

	add (1000
	swap

	lac \udc		/draw first group of dots at the left
	sal 1s			/ dispatch on \udc x 3 (udc = 0..5) for clipping
	add \udc
	add (sd4 1
	dap \sd4
	swap
	lio \py
sd4, 	jmp .
	add (1000		/0
	nop
	nop
	add (1000		/1
	ioh
	sdisp
	add (1000		/2
	ioh
	sdisp
	add (1000		/3
	ioh
	sdisp
	add (1000		/4
	ioh
	sdisp

	add (3000		/5, display 4 dots
	ioh
	sdisp
	add (1000
	ioh
	sdisp
	add (1000
	ioh
	sdisp
	add (1000
	ioh
	sdisp

	add (3000		/display 4 dots
	sdisp
	add (1000
	ioh
	sdisp
	add (1000
	ioh
	sdisp
	add (1000
	ioh
	sdisp


	add (3000		/draw group of remaining dots at the right
	swap
	lac \udc		/dispatch on \udc x 3 (udc = 0..5) for clipping
	add (sd5 1
	dap sd5
	swap
	lio \py
sd5,	jmp .
	jmp sd0
	jmp sd0
	jmp sd9
	jmp sd8
	jmp sd7
	jmp sd6
sd6,	ioh			/4 dots
	sdisp
	add (1000
sd7,	ioh			/3 dots
	sdisp
	add (1000
sd8,	ioh			/2 dots
	sdisp
	add (1000
sd9,	ioh			/last dot
	sdisp
sd0,	ioh			/fetch and clear last completion pulse

sdx,	jmp .			/return


	constants
	variables

	start 4

(~ 1.24 K 18-bit words, 02354 locations incl. variables.)

 

That's all for this post.

 

Next:   Episode 8: The Saucers Strike Back

Previous:   Episode 6: Saucers!

Back to the index.

— This series is part of Retrochallenge 2016/10. —