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

Episode 11: It Lives!

So, Retrochallenge is over, and it's only appropriate that its best date, Halloween, should also be the official birthday of our Frankenmonster of a game, this Lovecraftian distortion in game history, this harrowing encounter of ancient gods, for eons now resided in those untold shadows beyond the reach and wisdom of modern-day's bloggers, who happily dwell, while unbeknownst by them these creatures of dark and untold powers are still lurking in the gloom of decaying paper tapes …

Ironic Computer Space Simulator for the PDP-1 (v. 0.9 / final)

Ironic Computer Space Simulator for the PDP-1 (v. 0.9 / final).

Where to begin with? A lot has happened since the last episode of this log. — Why not do it chronologically, shall we?

Same Scores, A Different Size, Some Automations

First, we resolved to rescale the score and time display. Fortunately we first implemented an algorithm for deriving the display location of the various digits from the grid units of the character display. Thus, when we eventually tried some different units and ratios, our previous efforts payed off and we hadn't much else to do.

Just the same, we put some constants near the top of the source code defining some timing factors to be summed up at the start of the program to get the instruction budget for a frame. As we were at it, we also reorganized the source code a bit, according to the established practice of old. While these days there really isn't any relevance to it, it was a great idea in the days of punched paper tape: Two parts, a first part with the static stuff (constants, independent subroutines and coroutines, which are not expected to change) and a second part, where the main code lives. This way, you haven't to repunch the entire code, when you're adding some minor changes to your program.

The overall structure now looks like this:

But we were speaking of the timing constants. BTW, this is what it's all about:

/adjust these two parameters for timing

ith, 27,	74		/ throttle (idle cycles / 3)
ifs, 30, 	73		/ frames per second for time keeping


/timing constants (avr. cycles / 3)

irk,	310		/ rocket routine
isc,	360		/ saucer routine
isx,	350		/ saucer explosion
idp,	 60		/ score display

Earlier, we announced that it would be rather easy to figure these out, as we were going to draw them empirically from the cycle count of the emulator. Just devide the microseconds by 5 to get the instruction count, and the result once again by 3, and we'll get the appropriate number of iterations for the compensation count-up loop that is the keeper of the pace and frame rate of the game.

Once again, just the same as last time something looked pretty easy, we summoned the code devil — or was it, appropriately for Halloween, the Grim Code Pumpkin?

coding nightmares

Choose your own nightmare.

So someone, who was to be supposed busy on Halloween anyway, had another sleepless hour. However, as the saying goes, they are to supposed to never sleep …

This time, we didn't get consistent results out of the emulator, and our creepy guest star had a great show. We chose to implement an "iot" instruction of our own, passing options for a timer label, start and stop micro code and meant to extract the time passed between these two code points. Somehow, our measurements didn't look as they were supposed to. In between, we even lost the trust in our trusty emulator and its cycle counts and reviewed the code line by line. While, in the end the, as in any bedtime story, all was good, — there were other reasons to doubt the cycle counts, even when the results were consitent, reasonable and correct.

Real-Time Capability of the PDP-1

In the summer of 1962 Martin Graetz did a paper under the above title for the first DECUS (DEC User Group) conference on the unheard fact of a video game (what's this?) being implemented and run on a digital general purpose computer. — A fullfledged simulation of space flight and orbital battle including visual representations of ships and stars and, even more, realtime feedback to human control input. (J. M. Graetz, "Spacewar! Real-Time Capability of the PDP-1", DECUS Proceedings 1962: Papers and Presentations of the Digital Equipment Computer Users Society, Maynard, Massachusetts, 1962, p. 37-39.)

With our cycle counts in place, we were finally able to a) implement our attract mode (the game running idle before and between plays) and b) adjust a counter to match our now stable frame rate to seconds for the benefit of timer that would eventually trigger this attract mode as it runs around 99.

And, lo and behold, our frame rate is at 58 fps!

(Update: And as of v. 1.0 even at 60 fps!)

Even, with a bit of throttle left in the program, and by this frame-to-seconds ratio the program is still running a bit too fast to match standard seconds exactly (which is ok, since Computer Space did it, too). So, the correct frame rate would be rather at around 60 fps. And this is the humble pace of a punny 1960s computer when running a video game. Even a modern browser has a bit of load to keep up with this pace, since 60 fps is still considered the max, if not the hols grail, for HTML repaint (i.e., refresh of certain regions in the display), and that is only, if you know to do it right. — A testament to the capabilities of a machine, which's prototype was presented in 1959!

Hasting Towards the Finishing Line

And, there were still other things to be done. In order to implement the attract mode and a restart of the game, we had to move most of the setup code into a subroutine. And we wanted to address the problem of the appropriately inaccurate targeting (see last episode), or loose aiming for the saucers. Here, we tried both of the tricks we had suggested and also tested a few parameters and configurations. In the end, we had lost a bit of momentum on the intermezzo with the cycle counts and, since this requires some extensive testing, decided to go with a simple alternate epsilon, a minimum distance, and a rather small propability for this to trigger. (An approach based on scaling distances to an increased error by widening the epsilon by this amount seemed promissing, but requires some testing and adjustments. This must be left to a future revision of the program. Thus we're now featuring just version 0.9 with the prospect of some improved loose aiming being eventually included in the 1.0 release.)

Otherwise, we're quite happy with the state of affairs and a bit au bout de souffle.

Thus, we close for now, and promise to return for a wrap-up episode, where we will conclude on our experiences. After all, this was my first complete project with PDP-1 code, and it may be interesting what can be learned from this.

ICSS (phew!).

Extra terrestrial Medusa raising its head for a farewell, Ironic Computer Space Simulator for the PDP-1.

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

Finally, here's the Computer Space "money shot", a yellow Nutting Associates machine (while commonly considered to be the most attractive of the color variations, only about 130 of these machines are said to were built) and its cover girl from the 1971 flyer:

Computer Space, edited image (2016) recreating the 1971 flyer.

Computer Space and its cover girl (edited image, N.L. 2016).
(Sources: girl from original flyer: pinrepair.com, yellow Computer Space cabinet: Dennis Scimeca via The Daily Dot.)

And here's our code, as it has grown over the last  19   20  22 days:

Update, Nov. 1st: I added a final spinning rocket animation at the end of a game, displayed in case the player didn't win and earn a free extra play. The bare end (the ship just fading out) was just a bit too dry…   So, here is version 0.9.1, updated section in blue (at labels ff0at0, coming late for RC2016/10)…

Update, Nov. 3rd: As a final addition I added a tiny animation at the bottom of the screen to indicate "Hyperspace Mode" (extra play) — the best we can do in the absence of reverse video. By this, we have now version 0.9.2, upades in green (for most parts, aside a simple reset at a3, in the main loop).

And we've another (small) update (in purple), regarding parameter "efp", a new parameter "rto" to make the restart timeout configureable, and a few heading comment lines. That's makes our little program version 0.9.3 …

(A playable version 0.9 as of Oct 31, end of RetroChallenge, is preserved here.)

ironic computer space simulator  0.9.3 nl 2016-11-03

	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.'

/ Ironic Computer Space Simulator for PDP-1, Norbert Landsteiner, 2016

/ sense switch options
/ SSW 1 ...  parallax effect
/              off - stars roll continuously to the left.
/              on  - background stars move relatively to rocket ship.
/ SSW 2 ...  parallax effect strength (together with SSW 1)
/              off - subtle effect.
/              on  - stronger effect.
/ SSW 3 ...  torpedoes, single shots (like spacewar!)
/              off - 'continuous fire' action.
/              on  - trigger blocks and waits for new press.
/ SSW 4 ...  torpedoes, steering
/              off - steering applies only, while the ship is turning.
/              on  - torpedoes eventually follow the turn of the ship.
/ SSW 5 ...  saucer motion
/              off - diagonals are horizontally stretched.
/                   (conforms more to the overall impression of CS)
/              on  - geometrical diagonals.
/ SSW 6 ...  saucer piloting, which saucer is shooting?
/              off - always the same one (as in original CS).
/              on  - random select.

/ input / control word in io as follows (like spacewar),
/ 'high order 4 bits, rotate ccw, rotate cw, ire rocket, and fire torpedo.
/  Low order 4 bits, same for other ship.'


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,	60		/ rocket reset delay (frames)
etl, 15,	law i 240	/ enemy torpedo life
etd, 16,	law i 30	/ enemy torpedo cooling (frames)
etf, 17,	law i 100	/ enemy first torpedo cooling (after score)
ela, 20, 	sal 6s		/ scaling enemy torp look-ahead
ete, 21,	14000		/ epsilon for enemy aiming
etn, 22,	sal 3s		/ scaling noise for enemy torpedo aim
efd, 23,	160000		/ min distance for loose aiming
efp, 24,	7		/ probability of loose aiming
efe, 25,	140000	/ epsilon for loose aiming
rto, 26,	law i 340	/ game restart delay (frames blocking input)
ran, 27,	0		/ random number


/adjust these two parameters for timing

ith, 30,	74		/ throttle (idle cycles / 3)
ifs, 31, 	73		/ frames per second for time keeping


/timing constants (avr. cycles / 3)

irk,	310		/ rocket routine
isc,	360		/ saucer routine
isx,	350		/ saucer explosion
idp,	 60		/ score display


/saucer movement table (dy, dx)

/ horizontally streched diagonals, default (dy, dx)
ut0,	 600		   0
	-600		   0
	   0		 600
	   0		-600
	 400		 600
	-400		 600
	 400		-600
	-400		-600

/ diagonals proper
ut1,	 600		   0
	-600		   0
	   0		 600
	   0		-600
	 400		 400
	-400		 400
	 400		-400
	-400		-400

/saucer torpedo movement table (dy, dx)
emt,	 2400		    0
	-2400		    0
	    0		 2400
	    0		-2400
	 1600		 1600
	-1600		 1600
	 1600		-1600
	-1600		-1600


/character display grid units (for character outline compiler)
/ (display positions are derived automatically at start up)

cgy,	 7000			/unit for character grid y
cgx,	 5000			/unit for character grid x
	. 3/			/reserve space for x-unit offsets 2..4


/routine to flush sequence breaks, if they occur.

sbf,	tyi
	lio 2
	lac 0
	lsm
	jmp i 1


/sine-cosine subroutine - Adams associates 1961
/calling sequence= number in AC, jda sin or jda cos.
/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


/character outline compiler

/character outlines
/ 7 rows at 5 dots, left to right, top down,
/ 2 words per character, aligned left.
cot,
	350614	306134	/0
	106041	020410	/1
	350411	442076	/2
	350411	406134	/3
	451227	610204	/4
	770207	406134	/5
	350607	506134	/6
	760421	041020	/7
	350613	506134	/8
	350613	604230	/9
	105214	376142	/A
	750617	506174	/B
	350604	102134	/C
	750614	306174	/D
	770207	502076	/E
	770207	502040	/F

coa,	. 21/			/space for addresses of character object code

/setup and compile digits 0-9, chars A-F
ci,	dap cix
	lac cgx			/set up grid units (1..4)
	add cgx
	dac cgx 1
	add cgx
	dac cgx 2
	add cgx
	dac cgx 3
ci2,	dzm \t1			/compile outlines
	law ctb
	dap ci4
	law coa
	dap ci5
	cla
ci3,	dac . 4			/fix up character index
ci4,	law .			/start address of compiled code (fixed up)
ci5,	dac .			/store start address (fixed up)
	jda cc
	0			/character index (fixed up)
	dap ci4
	idx ci5
	idx \t1
	sas (21
	jmp ci3
cix,	jmp .

/character outline compiler
/ call  law addr, jda cc, char-index (see cot)
/ returns next address in ac, compiled code will exit at cdx

define comp A			/push instr. A to object code
	lac A
	dac i cc
	idx cc
	term

define cadd A			/add instr. A to ac and push to object code
	add A
	dac i cc
	idx cc
	term

cc,	0
	dap ccx
	lac i ccx		/get char-index
	sal 1s
	add (cot
	dac \cwd		/addr. of first code word
	idx ccx			/fix up return address
	stf 6			/flag 6 for first dot
	law i 22
	dac \cbi		/bit counter
	lio i \cwd		/get first code word to parse
	comp (lio cy		/- reset y
	dzm \cyi
	comp (lac cx		/- reset x
	dzm \cxp		/current x
ccr,	dzm \cxi		/new row
	spi i			/is there a dot?
	jmp ccs			/no
	lac \cxi		/get offset to last x
	sub \cxp
	sza i
	jmp ccd
	spa
	jmp ccm
	cadd (add cgx-1
	jmp cca
ccm,	cma
	cadd (sub cgx-1
cca,	lac \cxi
	dac \cxp
ccd,	szf 6
	jmp cd1
	comp (ioh		/- wait for display
cd1,	comp (dpy -4000 100	/- display a dot
	clf 6
ccs,	ril 1s			/advance to next bit
	isp \cbi
	jmp cci
	law i 22		/fetch next code word
	dac \cbi
	idx \cwd
	lio i \cwd
cci,	idx \cxi
	sas (5			/end of a row?
	jmp ccr 1		/no
	idx \cyi		/next row
	sad (7			/was it the last one?
	jmp ccz			/trail and exit
	comp (rcr 9s		/- swap
	comp (rcr 9s
	comp (sub cgy		/- increment pos y
	comp (rcr 9s		/- swap
	comp (rcr 9s
	jmp ccr
ccz,	szf 6
	jmp cz1
	comp (ioh
cz1,	comp (jmp cdx
ccx,	jmp .			/return

cx,	0
cy,	0

/display a character
/ call  law char-index, jda cd (coors in cx, cy)
cdp,	0
	dap cdx
	law coa
	add cdp
	dap . 1
	jmp i .
cdx,	jmp .


/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
	dzm bgh				/ reset bg offsets
	dzm bgv
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
	lac \gms			/ force slow movement in attract mode
	spq
	jmp bgd
	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

/	start				/ cut here for two source tapes

/ironic computer space simulator  0.9.3   pt. 2   nl 2016-11-03

/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

/initial setup

a2,	jsp ci			/ run character outline compiler

	lac cgx			/ configure positions for score display
	sal 3s
	sub cgx
	cma
	dac \t1			/ -7 x
	add (377777		/ right margin
	dac \d0x		/ right pos = margin - 7 x (2 x offset)
	add \t1
	dac \d1x		/ left pos = margin - 14 x (2 x spacing)
	lac cgy
	sal 3s
	sub cgy
	sar 1s
	dac \d1y		/ vertical middle pos = 7/2 y (centered)
	sal 2s
	sub cgy
	dac \d0y		/ top pos = 13 y (3 y spacing)
	cma
	add \d1y
	add \d1y
	dac \d2y		/ bottom pos = 6 (3 y spacing)

	lac irk			/ configure cycle count budget
	add isc
	add idp
	add ith
	cma
	dac \ifr
	jsp a3			/ setup rest of the game
	dzm \gms		/ game status to attract mode


/main loop

fr0,	lac \ifr 		/ initial instruction budget (delay)
	dac \ict
	idx \frc		/ increment frame counter
	clf 1			/ flag 1 indicates active player's ship
	clf 2
	clf 3

	jsp bg			/ display background

	lac \gms
	spq
	jmp at0

	lac \hpm		/ check hyperspace mode (extended play)
	sma			/ activated?
	jmp ffg			/ no
	law 2000		/ display moving dots at the bottom
	add \hpc		/  as an indicator (in stead of reverse video)
	dac \hpc
	lio (-377000
	dpy -4000		/ display a dot
	cma			/ mirror x
	ioh			/ wait for the crt
	dpy -i			/ and display another one

ffg,	jsp rkt			/ rocket routine
	jsp ufo
	lac \trs
	sza
	jsp trp			/ torpedo routine
	lac \ets
	sza
	jsp etm			/ enemy torpedo routine
	jsp df			/ check collisions

	idx \fdc		/ manage time
	sas ifs
	jmp fr1
	dzm \fdc
	idx \fd0
	sas (12
	jmp fr1
	dzm \fd0
	idx \fd1
	sas (12			/ overflow of second timer digit?
	jmp cwu			/ no

ff0,	dzm \fd1		/ game over, check scores
	lac \sc1
	sub \sc2
	szm			/ winner?
	jmp fng			/ yes, continue
	xct rto		/ change to attract mode (get restart delay)
	dac \gms
	lac \rks
	sza			/ rocket active?
	jmp ff1			/ yes, start a final spinning animation
	add rsd			/ check, if spinning
	sma			/ animation done?
	dzm \rks		/ yes, disable rocket
	jmp cwu

ff1,	law ff2			/ fix up return address for df-routine
	dap dfx			/  to setup a final spin of the rocket
	stf 5			/ set flag 5, do not respawn saucers
	jmp dxr			/ reset rocket to spin
ff2,	lac \sc2		/ undo score increment done in dxr
	sub (1
	dac \sc2
	jmp cwu

fng,	dzm \sc1		/ continue for a new game (extra play)
	dzm \sc2		/ reset scores
	lac \hpm		/ flip 'hyperspace mode'
	cma
	dac \hpm
	dzm \hpc		/ reset counter for 'hyperspace' animation
	jmp cwu

at0,				/ attract mode
	lac \rks		/ check rocket state
	sma			/ spinning?
	jmp . 5			/ no
	jsp rkt
	lac \rks		/ check again for setup delay
	add rsd
	sma			/ spinning done?
	dzm \rks		/ yes, disable rocket
	xct rcw			/ new game on any input
	dio \cw
	random
	isp \gms		/ countup to prevent accidental restart
	jmp at1
	dzm \gms
	lac \cw
	xor \cwo
	sza i
	jmp at1
	jsp a3			/ restart
	law i 20
	dac \rks
	law 1
	dac \gms
	jmp cwu
at1,	jsp ufo

cwu,	lac \cw
	dac \cwo

fr1,	jsp scd			/display scores

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


/score display

scd,	dap scx
	lac \d0x
	dac cx
	lac \d0y
	dac cy
	lac \sc1
	and (17
	jda cdp
	lac \d1y
	dac cy
	lac \sc2
	and (17
	jda cdp
	lac \d2y
	dac cy
	lac \fd0
	jda cdp
	lac \d1x
	dac cx
	lac \fd1
	jda cdp
	lac \ict
	add idp
	dac \ict
scx,	jmp .


/start a new game
a3,	dap ax
	jsp bsi			/ setup bg-stars
	dzm \fd0		/ reset counters for time display
	dzm \fd1
	dzm \fdc
	dzm \rks		/rocket status to inactive
	dzm \hpm		/ reset 'hyperspace mode'

/rocket setup
ar,	lac (670000
	dac \rth		/ rotational angle
	dac \th0
	lac (-200000
	dac \rpx		/ pos x
	cma
	dac \rpy		/ pos y
	law 200
	dac \rdx
	law i 600
	dac \rdy
	dzm \rac		/ acceleration skip counter
	dzm \rxc		/ reset center offset
	dzm \ryc
	dzm \sc1		/score
	dzm \trs		/torpedo status counter
	dzm \cwo		/old control word

/saucer setup
au,	random
	sar 6s
	add (200000
	dac \upy		/ pos x
	random
	sar 4s
	add (140000
	dac \upx		/ pos y
	dzm \udc		/ animation skip counter
	law 1
	dac \udd		/ animation direction
	dzm \umo		/ direction code
	dzm \udy		/ delta y
	dzm \udx		/ delta x
	law i 10
	dac \ufc		/ duration of movement
	law 1
	dac \uft		/ speed of center animation (1,3)
	dac \ufs		/ state counter
	law i 1
	dac \umo
	dzm \sc2
	dzm \ets		/ torpedo state
	jsp etr			/ get torpedo cooling (\etc)

ax,	jmp .


/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
	clf 5

	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 dfe
	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 first saucer
	sza
	jmp dxu			/hit, respawn

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

dfe,	lac \ets		/enemy torpedo active?
	sza i
	jmp dfx
	lac (11000
	dac \dxe
	dac \dye
	law 6400
	dac \de2
	law \px
	dap mx1
	law \py
	dap my1
	law \epx
	dap mx2
	law \epy
	dap my2
	jsp dmf
	sza i
	jmp dfx
	dzm \ets		/reset enemy torpedo
	xct etf			/get initial topredo cooling
	dac \etc		/and store it
	lac
	stf 5

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
	idx \sc2
	szf 5			/was it a saucer-rocket collision?
	jmp dfx			/no

dxu,	dzm \trs		/set up saucer explosion, reset torpedoes
	dzm \ets
	lac i my2		/set up pos y
	dac \upy
	idx \sc1
	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 \t1
	sub \dxe		/ < epsilon x ?
	sma
	jmp dm0			/no
my1,	lac .
my2,	sub .
	spa
	cma
	sub \dye		/ < epsilon y ?
	sma
	jmp dm0			/no
	add \t1
	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 eta			/continue for torpedo setup

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 eta
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 movement table
	szs i 50		/sense switch 5 to select table (ut1 or ut0)
	jmp . 3
	add (ut1		/setup reference location for dy in \t1
	jmp . 2
	add (ut0		/select other table
	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
	lac \umo
	sma
	jmp . 4
	lac \ufc		/shorter delay on stops
	sar 1s
	dac \ufc

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

ufi,	lac \ict		/update instruction count
	add isc
	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
	jsp etr			/reset torpedo, get cooling
	law 1			/set state to active
	dac \ufs
urx,	jmp .


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

uxe,	law uxs			/set up instr at uxs to be xct-ed in ux1
	dap ux1
	isp \ufs
	jmp . 3
	jsp urs			/last iteration, respawn saucers
	jmp ufx
	add (30			/done with explosion, 030 frames setup delay
	sma
	jmp ufx
	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
	lac \ict
	add isx
	dac \ict
	jmp ufx

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


/saucer torpedo ai (setup)
/ here from inside ufo

eta,	lac \ets		/torpedo already active?
	spa
	jmp ufi
	szf i 1			/rocket active?
	jmp ufi
	lac \ufs		/saucers active?
	spq
	jmp ufi
	isp \etc		/count up cooling
	jmp ufi
	dzm \etc

	lac \upx		/position of tubes
	add (10400
	dac \epx
	lac \upy
	sub (3400
	dac \epy

	random			/get random offset for targeting
	cli
	scr 9s			/split it into lower parts of ac and io
	sir 9s
	xct etn			/scale it
	dac \exn		/store noise x
	swap			/same for y
	xct etn
	dac \eyn


	szs i 60		/sense switch 6 to select saucers at random
	jmp et1			/ original shoots always from same saucer
	random
	sma			/which saucer
	jmp et1
	lac \epy
	add (400000
	dac \epy
et1,
	lac ete
	dac \eps
	lac \rdx
	xct ela			/look-ahead
	add \rpx
	sub \rxc
	add \exn
	sub \epx
	dac \dx			/delta x
	spa
	cma
	dac \adx		/abs delta x

	lac \rdy		/same for y
	xct ela
	add \rpy
	add \ryc
	add \eyn
	sub \epy
	dac \dy
	spa
	cma
	dac \ady

	lac \umo		/check loose aiming conditions
	and (7
	spq			/moving?
	jmp et2			/no
	lac \ady		/check min distance
	add \adx
	sub efd
	spa			/big enough?
	jmp et2			/no
	random			/handle probabilty
	spa
	cma
	sub efp
	sma			/aim loosely?
	jmp et2			/no
	lac efe
	dac \eps

et2,	lac \ady
	sub \eps
	spa
	jmp eah
	lac \adx
	sub \eps
	spa
	jmp eav

	lac \adx
	sub \ady
	spa
	cma
	sub \eps
	sma
	jmp ufi
ead,
	law 4
	lio \dy
	spi
	law 5
	lio \dx
	spi
	add (2
	jmp et3
eah,
	law 2
	lio \dx
	spi
	law 3
	jmp et3
eav,
	cla
	lio \dy
	spi
	law 1
et3,
	sal 1s			/read corresponding dx/dy form movement table
	add (emt		/setup reference location for dy in \t1
	dac \t1
	lac i \t1		/load dy indirectly from addr. in \t1
	dac \edy
	idx \t1			/increment addr. in \t1
	lac i \t1		/load dx indirectly from addr. in \t1
	dac \edx
	xct etl
	dac \ets

eti,	jmp ufi			/return to ufo


/saucer torpedo movement routine

etm,	dap etx
	isp \ets		/count up life
	jmp . 2
	jmp etr 1		/expired
	lac \epy
	add \edy
	dac \epy
	swap
	lac \epx
	add \edx
	dac \epx
	dpy -i 100
etx,	jmp .			/return
etr,	dap etx			/reset
	dzm \ets
	xct etd
	dac \etc
	jmp etx


/player rocket routine

rkt,	dap rkx
	lac \rks		/check for collision state
	sza i
	jmp rkx
	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
	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 i			/check next bit for fire
	jmp . 7
	szs i 30		/sense switch 3 for single shot
	jmp . 4
	lio \cwo
	ril 3s
	spi i			/block if already set in old control word
	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
	lac \ufs
	spq			/saucers active?
	jmp rkq			/no
	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 irk
	dac \ict

rkx,	jmp .


/torpedo (rocket)

trp,	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 40			/sense switch 4 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 .


/sprite routines

/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

ctb,				/start address for code of outline compiler

	start 4

(~ 1.87 K 18-bit words, 03605 locations incl. variables, + JIT compiled code ~ 1.25 K, 02476 locations.)

 

That's all for this post.

 

Next:   Wrap-Up

Previous:   Episode 10: AI and the Problem of Missing Accurately

Back to the index.

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