Spacewar 3060

This project recreates and embellishes the first truly interactive computer game: Spacewar!

Spacewar 3060 A Little History
The legendary game of Spacewar was created in 1961, 10 years before Pong, and led to the first-ever interactive use of the computer CRT display (it was, in fact, first run on an oscilloscope).
Spacewar was created on a PDP-1 (not 11), one of the enormous room-sized computers created by DEC and supported by massive government and university funding, that ran at approximately 200 Hz (effectively half that fast when memory was accessed). Even the simple gravity calculations on two ships caused the game to flicker, until math lookup routines were implemented.
Spacewar involves two dueling ships attempting to outlast each other as they rotate and apply thrusters to avoid the strong gravitational grip of a star in the middle of the screen. Players fire single-pixel "torpedoes" at each other as they fly around, and can fall back on a one-shot hyperspace "panic" button that warps to a random safety location.
The original game machine was retired in 1975, and now serves as a source of spare parts for another PDP-1 on display at the DEC museum in Marboro, MA.
Source: Creative Computing, August 1981 issue, republished here.

Our Project / Problem Description
Spacewar 3060 will depart from the simple two-ships-and-a-star configuration of the original game, and will implement a number of features to modernize the gameplay:

Team Members
Note: Side-by-side function assignments listed in 'Procedures' below
Member Jobs
Eric Tyson Team leader; game states, MAIN; gameplay, game timer interrupt; graphics design, graphics drawing of rotated game objects, animations, and sprites; math routines (trig. lookup)
Andrew Scally Interrupt (un)installation; game input, key interrupt; sound design, asynchronous sound implementation (play sound, timer-based sound updates)
Alex Mihov Data initialization, lookup (bitmap, trig. functions); data organization; menu screen coolness (design, input, and timer interrupt)

Implementation
Our implementation of Spacewar 3060 will involve a number of interesting techniques and algorithm considerations that add to the complexity and overall technical accomplishment of the project:

Segments / Data / Variables

StkSeg		SEGMENT stack			; stack segment
			db	64 dup ('STACKSTA')
StkSeg		ENDS

BufferSeg	SEGMENT 'DATA'			; screen buffer to reduce flicker
			db	320*200 dup ('X')
BufferSeg	ENDS

ImageSeg		SEGMENT 'DATA'			; All animation frames and source images
			; Various game and menu element images - organization by Alex
ImageSeg		ENDS

DataSeg		SEGMENT 'DATA'			; Runtime data vars
	; SineTable = array of 256 bytes; SineTable[i] = sin((2*Pi)i/256)*128
	; CosTable = 64 byte (Pi/2) offset into SineTable
	include 'sine.inc'
	; NormSineTable = 256 byte array; NormSineTable[i] = sin((2*Pi)i/256)*(256/sqrt(2))
	; NormCosTable = 64 byte (Pi/2) offset into NormSineTable
	include 'nsine.inc'

	; Various filenames for Initialize procedure to use for loading
	include 'titles.inc'

	; Sound - set of sound frequency arrays, each terminated with zero
	; (design by Andrew)
	include 'sound_data.inc'

	currentSound	dw	?	; offset of current tune's sound array

	frame				dw	?	; current frame of gameplay - AND'd for use with animation

	ship1Move		db	?	; ship 1 moving state: b0 = move left, b1=right, b2=thrust
	ship1X     		dw	?	; x position of ship 1 (pixels)
 	ship1Y      	dw	?	; y position of ship 1 (pixels)
	ship1Angle		db	?	; angle of ship 1 in (2*Pi/256) units
	ship1VX			db	?	; x velocity (signed, pixels per tick)
	ship1VY			db	?	; y velocity (signed, pixels per tick)
	ship1Shield		db	?	; shield left on ship 1

	ship2Move		db	?	; ship 2 moving state: b0 = move left, b1=right, b2=thrust
	ship2X     		dw	?	; x position of ship 2 (pixels)
 	ship2Y      	dw	?	; y position of ship 2 (pixels)
	ship2Angle		db	?	; angle of ship 2 in (2*Pi/256) units
	ship2VX			db	?	; x velocity (signed, pixels per tick)
	ship2VY			db	?	; y velocity (signed, pixels per tick)
	ship2Shield		db	?	; shield left on ship 2

	torp1X	dw	16 dup (?)	; array of torpedos' x locations for player1
	torp1Y	dw	16 dup (?)	; y locations (pixel)
	torp1VX	db	16 dup (?)	; x velocities (signed, pixels per tick)
	torp1VY	db	16 dup (?)	; y velocities (signed, pixels per tick)
	torp1T	db	16 dup (?)	; time before torpedo self-destruct in clock ticks

	torp2X	dw	16 dup (?)	; array of torpedos' x locations for player2
	torp2Y	dw	16 dup (?)	; y locations (pixel)
	torp2VX	db	16 dup (?)	; x velocities (signed, pixels per tick)
	torp2VY	db	16 dup (?)	; y velocities (signed, pixels per tick)
	torp2T	db	16 dup (?)	; time before torpedo self-destruct in clock ticks

	db astAngle	8 dup (?)	; array of asteroid angles (2*Pi/256 units)
	dw astX		8 dup (?)	; array of asteroid x locations (pixels)
	dw	astY		8 dup (?)	; array of asteroid y locations (pixels)
	db	astVX		8 dup (?)	; array of asteroid x velocities (pix per tick)
	db	astVY		8 dup (?)	; array of asteroid y velocities (pix per tick)

	;	Game state:	0 = Game in progress, 1 = Esc pressed,
	;					2 = Player 1 died, 3 = Player 2 died, 4 = computer(s) dead,
	;					5 = Next level, 6 = Game paused
	game_state	db	?	

	dw stars		128 dup (?)	; array of 2*64 words holding 64 star positions in [x,y]

	; Rotated images - drawn by DrawRotated in GameKeyInt, drawn to buffer in GameTimeInt
	db ship1rot		48*48 dup (?)	; rotated ship prebuffer
	db ship1rott	48*48 dup (?)	; rotated ship with thrust prebuffer
	db ship2rot		48*48 dup (?)	; rotated ship prebuffer
	db ship2rott	48*48 dup (?)	; rotated ship with thrust prebuffer
	db roid1rot		48*48 dup (?)	; asteroid prebuffer

	conf_mode	db	18		; game mode: PvP (0), PvC (1), CvP (2), P1 only (3),
								; set bit 3 (add 4) for asteroids on,
								; set bit 4 (add 8) for sun on,
								; set bit 5 (add 16) for sound on,
								; set bit 6 (add 32) for stars on


	; array of 5*2 scancodes for each function:
	; {p1left, p1right, p1thrust, p1fire, p1hyper; p2left, ...}
	; (initialized to some nice, easy default values)
	db conf_keymap 10		75, 77, 72, 29, 82,   30, 32, 17, 31, 16

	dd oldKeyInt	; original key interrupt before loading
	dd oldTimeInt	; original time interrupt before loading
DataSeg ENDS

Procedures

Math formulas
Sine tables were calculated using simple C++ programs: sine.cpp and normsine.cpp. These calculate SineTable[i] = sin(i*2*Pi/256)*128, which results in a signed byte from -128 to 127. The normalized table is similar, but divided by sqrt(2) for optimal scaling. Cosine tables are simply offsets of 64 bytes into their respective sine arrays, which is equivalent to an extra Pi/2.

Image rotation was done by calculating the rotation of the upper-left corner of the source image, in the destination rotated buffer. This meant using the formula: Rot(<x,y>) = <cos(theta) * x - sin(theta) * y, sin(theta) * x + cos(theta) * y>. DX and DY are calculated as the step size of each successive point in the horizontal direction. To draw a rotated horizontal line of image, the plotting point <x,y> is incremented by <dx, dy> for each step. At the end of each line, the starting point is restored, and <dy, -dx> is added to get the next point in the orthogonal direction, which is equivalent to the beginning of the next line. This allowed an entire 32x32 square to be drawn on a 48x48 buffer, properly rotated and scaled. Additionally, using normalized cos/sin, the image exhibits no "gap" flaws while maintaining the largest image size.
Some extra details of the image rotation scheme involve decentering and recentering the image (as the rotational center of the source is (0,0), and the real center is (32/2, 32/2)). Also, to avoid using floating points, the output of sine/cosine functions return a number in 1/256ths. This is added to word-storage point variables which are also in 1/256ths scale, so that to get the "real" x and y coordinates, one must simply look at only the high word to ignore the sub-pixel details.

External Procedures
There was absolutely no external code used in this project. Every technology we used was learned from primary sources and implemented entirely independently. We feel that this gave us a great learning experience, and gave us confidence in our ability to write projects entirely from scratch.

Play the Game
This project is not yet complete. It may be finished up during the summer of 2003. Until then, you can play the original Spacewar here (on a PDP-11 emulator, no less!) or get more history here.