You can duke it out with a friend, but be careful not to fall into the star in this exclusive Spacewar clone written in Apple Logo.

Seymour Papert’s Logo programming language is perfect for a game like Spacewar – Logo uses vectors to draw graphics, unlike BASIC which typically plots pixels based on precise screen coordinates.

Rather than saying PLOT 50,100; PLOT 50,101 etc., Logo’s turtle occupies a certain position in Logo “space”, and can be moved relative to that position by using commands such as fd (forward), rt (right turn) and so forth.

By specifying whether the turtle’s “pen” (held in its tail) is up or down, it can draw a trail behind it as it moves, and this is how Spacewar renders its graphic elements.

In Logo Spacewar there are two ships, each of which can rotate left or right, thrust forward or in reverse, fire a missle, or jump to hyperspace (move to a random location on the screen – although sometimes it causes your ship to explode!) There’s also a star at the centre of the playfield that has a gravitational influence on the players and their missiles – crashing into it is fatal!

Once all of the code on these four pages is entered (don’t forget to save “spacewar) you start the game by calling the start procedure along with a game length, for example start 1000. The game will begin and a timer will start counting down from the number you specified, in this case 1000. The player with the most amount of points when the timer runs out wins!

Player one controls their ship with the up, down, left and right arrows, and the comma and period keys for hyperpsace and fire respectively. Player two uses D, X, Z and C, and Q and W.

Logo Notes

Unlike BASIC, which uses line numbers to direct program flow, Logo uses named procedures instead. Procedures contain a series of commands – such as ones to move the turtle, manipulate variables or make sounds – which fulfil the set aim of the procedure.

Procedures are declared with the command to followed by a unique name, and are finished with the command end. For example, the resetpositions procedure to the left, which defines the locations of the Spacewar elements at the start of the game, begins with to resetpositions. It is then followed by a series of commands which then do the actual work.

make is similar to the BASIC command LET – it assigns a value to a named variable, for example setting p1tx to 0. Variables can be copied to other variables, or used to calculate new values, such as in p1h :a1 + 90 (the colon denotes that a1 is another variable, since Logo allows you to string multiple commands on the same line, as in local “a2 make “a2 :a1 – 180 which both declares the variable a2 is local or only valid inside the resetpositions procedure, and sets a2 to a1-180).

Like BASIC, Logo uses the if command to make decisions regarding program flow. For example, in the last line within checkjs1 if is used to validate the two expressions :b > 0 and :p1 > 245. If both are found to be true, the program jumps to the procedure hyperspacep1, where player one, well, jumps to hyperspace.

When the hyperspace procedure ends, it will return back to the procedure that called it. But the hyperspace procedure could jump someplace else before that happens, and the program flow may never return back to hyperspace nor checkjs1! (There’s no requirement that it should.) So, while the lack of line numbers or GOTO may seem restrictive on the surface, Logo’s program flow can really be quite complex.

The program:

to resetlevel

make “hypersuccess 70

make “missilelife 50

make “gravitystrength 2

make “thrust 0.3

make “turn 10

make “p1x -30

make “p1y 30

make “p1h 45

make “p1size 2

make “p1tx 0

make “p1ty 0

make “p1tc 0

make “p1gx 0

make “p1gy 0

make “p2x 30

make “p2y -30

make “p2h 275

make “p2size 2

make “p2tx 0

make “p2ty 0

make “p2tc 0

make “p2gx 0

make “p2gy 0

make “m1x 0

make “m1y 0

make “m1h 0

make “m1tx 0

make “m1ty 0

make “m1life 0

make “m2x 0

make “m2y 0

make “m2h 0

make “m2tx 0

make “m2ty 0

make “m2life 0

make “update 1

make “px2km 20

make “gconst 2.53932807e+21

make “pixelm 1200000

make “starx 0

make “stary 0

make “stars 3

make “scoreupdate 1

resetpositions

end

to addscorep1 :amt

make “p1score :p1score + :amt

make “scoreupdate 1

end

to addscorep2 :amt

make “p2score :p2score + :amt

make “scoreupdate 1

end

to drawscores

if :scoreupdate = 0 [stop]

cleartext

setcursor list 10 20

pr “p1

setcursor list 30 20

pr “p2

setcursor list 10 21

pr :p1score

setcursor list 30 21

pr :p2score

make “scoreupdate 0

end

to drawtimer

setcursor list 17 22

local “sp make “sp char 32

pr se “TIME :cyclecount :sp

end

to resetp1

local “r make “r 60 + random 20

local “a1 make “a1 random 360

make “p1x :r * sin :a1

make “p1y :r * cos :a1

make “p1h :a1 + 90

make “p1tx 0

make “p1ty 0

make “p1tc 0

end

to resetp2

local “r make “r 60 + random 20

local “a1 make “a1 random 360

make “p2x :r * sin :a1

make “p2y :r * cos :a1

make “p2h :a1 + 90

make “p2tx 0

make “p2ty 0

make “p2tc 0

end

to resetpositions

local “r make “r 60 + random 20

local “a1 make “a1 random 360

local “a2 make “a2 :a1 – 180

make “p1x :r * sin :a1

make “p1y :r * cos :a1

make “p2x :r * sin :a2

make “p2y :r * cos :a2

make “p1h :a1 + 90

make “p2h :a2 + 90

make “p1tx 0

make “p1ty 0

make “p2tx 0

make “p2ty 0

end

to star :x :y :size :pc

ht

setx :x

sety :y

setheading random 360

setpc :pc

pu

fd :size

pd

bk 2 * :size

pu

fd :size

lt 90

pu

fd :size

pd

bk 2 * :size

pu

end

to ship :x :y :h :size :tailsize :pc

ht

setpc :pc

setheading :h

setx :x

sety :y

make “a :size

make “b :size * 2

make “t :a * :a + :b * :b

make “c sqrt :t

pu

fd :a

pd

lt difference 180 26.6

fd :c

lt difference 180 63.4

fd :b

lt difference 180 63.4

fd :c

pu

lt difference 180 26.6

fd :b

if :tailsize = 0 [lt 180 stop]

setpc 13

pd

fd :tailsize

pu

lt 180

end

to missile :x :y :h :size :pc :life

if :life = 0 [stop]

ht

setpc :pc

setheading :h

setx :x

sety :y

pd

fd :size

pu

end

to frame

ht

if :update = 0 [stop]

make “update 0

clean

star :starx :stary :stars 13

ship :p1x :p1y :p1h :p1size :p1tc 3

ship :p2x :p2y :p2h :p2size :p2tc 5

missile :m1x :m1y :m1h 3 14 :m1life

missile :m2x :m2y :m2h 3 14 :m2life

drawscores

drawtimer

end

to firep1

if :m1life > 0 [stop]

make “m1life :missilelife

make “m1tx :p1tx + 1 * sin :p1h

make “m1ty :p1ty + 1 * cos :p1h

make “m1x :p1x + :m1tx

make “m1y :p1y + :m1ty

make “m1h :p1h

end

to firep2

if :m2life > 0 [stop]

make “m2life :missilelife

make “m2tx :p2tx + 1 * sin :p2h

make “m2ty :p2ty + 1 * cos :p2h

make “m2x :p2x + :m2tx

make “m2y :p2y + :m2ty

make “m2h :p2h

end

to boosterp1 :power1 :heading1

make “p1tx :p1tx + :power1 * sin :heading1

make “p1ty :p1ty + :power1 * cos :heading1

if :power1 > 0 [ make “p1tc :p1tc + 5 ]

if :power1 < 0 [ make “p1tc :p1tc – 5 ]

if :p1tc > 20 [ make “p1tc 20 ]

if :p1tc < -20 [ make “p1tc -20 ]

end

to boosterp2 :power2 :heading2

make “p2tx :p2tx + :power2 * sin :heading2

make “p2ty :p2ty + :power2 * cos :heading2

if :power2 > 0 [ make “p2tc :p2tc + 5 ]

if :power2 < 0 [ make “p2tc :p2tc – 5 ]

if :p2tc > 20 [ make “p2tc 20 ]

if :p2tc < -20 [ make “p2tc -20 ]

end

to gravitycalc :sx :sy :x1 :y1

local “dx

make “dx difference :sx :x1

local “dy

make “dy difference :sy :y1

local “s

make “s :dx * :dx + :dy * :dy

local “d

make “d sqrt :s * :pixelm

local “r3

make “r3 :d * :d * :d

local “f

make “f :gconst / :r3

local “t

make “t abs :dx + abs :dy

local “gx

make “gx :dx / :t * :f

local “gy

make “gy :dy / :t * :f

output :gx * :gravitystrength :gy * :gravitystrength

end

to checkjs1

local “p0

make “p0 paddle 0

local “p1

make “p1 paddle 1

local “b

make “b buttonp 0

if :b = 0 and :p0 < 20 [make “p1h :p1h – :turn]

if :b = 0 and :p0 > 245 [make “p1h :p1h + :turn]

if :b = 0 and :p1 < 20 [ boosterp1 :thrust :p1h ]

if :b = 0 and :p1 > 245 [ boosterp1 0 – :thrust :p1h ]

if :b > 0 and :p1 < 245 [ firep1 ]

if :b > 0 and :p1 > 245 [ hyperspacep1 ]

end

to checkjs2

local “p2

make “p2 paddle 2

local “p3

make “p3 paddle 3

local “b

make “b buttonp 1

if :b = 0 and :p2 < 20 [make “p2h :p2h – :turn]

if :b = 0 and :p2 > 245 [make “p2h :p2h + :turn]

if :b = 0 and :p3 < 20 [ boosterp2 :thrust :p2h ]

if :b = 0 and :p3 > 245 [ boosterp2 0 – :thrust :p2h ]

if :b > 0 and :p3 < 245 [ firep2 ]

if :b > 0 and :p3 > 245 [ hyperspacep2 ]

end

to checkkeys

if not keyp [ stop ]

make “code ascii readchar

if :code = 8 [ make “p1h :p1h – :turn ]

if :code = 21 [ make “p1h :p1h + :turn ]

if :code = 11 [ boosterp1 :thrust :p1h ]

if :code = 10 [ boosterp1 0 – :thrust :p1h ]

if :code = 122 [ make “p2h :p2h – :turn ]

if :code = 99 [ make “p2h :p2h + :turn ]

if :code = 100 [ boosterp2 :thrust :p2h ]

if :code = 120 [ boosterp2 0 – :thrust :p2h ]

if :code = 44 [ hyperspacep1 ]

if :code = 46 [ firep1 ]

if :code = 113 [ hyperspacep2 ]

if :code = 119 [ firep2 ]

make “update 1

end

to checkboundsp1

if :p1x < -140 [ make “p1x 140 stop ]

if :p1x > 140 [ make “p1x -140 stop ]

if :p1y < -80 [ make “p1y 80 stop ]

if :p1y > 80 [ make “p1y -80 stop ]

end

to checkboundsp2

if :p2x < -140 [ make “p2x 140 stop ]

if :p2x > 140 [ make “p2x -140 stop ]

if :p2y < -80 [ make “p2y 80 stop ]

if :p2y > 80 [ make “p2y -80 stop ]

end

to checkboundsm1

if :m1x < -140 [ make “m1x 140 stop ]

if :m1x > 140 [ make “m1x -140 stop ]

if :m1y < -80 [ make “m1y 80 stop ]

if :m1y > 80 [ make “m1y -80 stop ]

end

to checkboundsm2

if :m2x < -140 [ make “m2x 140 stop ]

if :m2x > 140 [ make “m2x -140 stop ]

if :m2y < -80 [ make “m2y 80 stop ]

if :m2y > 80 [ make “m2y -80 stop ]

end

to moveshipp1

local “gravity

make “gravity gravitycalc :starx :stary :p1x :p1y

make “p1tx :p1tx + first :gravity

make “p1ty :p1ty + last :gravity

make “p1x :p1x + :p1tx

make “p1y :p1y + :p1ty

checkboundsp1

make “update 1

if :p1tc > 0 [make “p1tc :p1tc – 1]

end

to moveshipp2

local “gravity

make “gravity gravitycalc :starx :stary :p2x :p2y

make “p2tx :p2tx + first :gravity

make “p2ty :p2ty + last :gravity

make “p2x :p2x + :p2tx

make “p2y :p2y + :p2ty

checkboundsp2

make “update 1

end

to movemissile1

if :m1life = 0 [stop]

make “m1life :m1life – 1

local “gravity

make “gravity gravitycalc :starx :stary :m1x :m1y

make “m1tx :m1tx + first :gravity

make “m1ty :m1ty + last :gravity

make “m1x :m1x + :m1tx

make “m1y :m1y + :m1ty

checkboundsm1

make “update 1

end

to movemissile2

if :m2life = 0 [stop]

make “m2life :m2life – 1

local “gravity

make “gravity gravitycalc :starx :stary :m2x :m2y

make “m2tx :m2tx + first :gravity

make “m2ty :m2ty + last :gravity

make “m2x :m2x + :m2tx

make “m2y :m2y + :m2ty

checkboundsm2

make “update 1

end

to shrinktails

if :p1tc > 0 [make “p1tc :p1tc – 1]

if :p2tc > 0 [make “p2tc :p2tc – 1]

if :p1tc < 0 [make “p1tc :p1tc + 1]

if :p2tc < 0 [make “p2tc :p2tc + 1]

end

to distance :x1 :y1 :x2 :y2

local “dx

make “dx difference :x1 :x2

local “dy

make “dy difference :y1 :y2

local “s

make “s :dx * :dx + :dy * :dy

local “d

make “d sqrt :s

output :d

end

to explode

toot 200 120

end

to explodep1 :points

star :p1x :p1y 3 13

addscorep2 :points

explode

resetp1

end

to explodep2 :points

star :p2x :p2y 3 13

addscorep1 :points

explode

resetp2

end

to wormhole :x :y :max :angle :pc

local “s make “s 1

pu

setx :x

sety :y

pd

setpc :pc

repeat :max [ fd :s rt :angle make “s :s + 1 ]

pu

end

to hyperspacep1

local “r make “r random 100

if :r > :hypersuccess [explodep1 1000 stop]

wormhole :p1x :p1y 10 90 14

make “p1x random 280 – 140

make “p1y random 192 – 96

make “p1h random 360

make “p1tc 0

wormhole :p1x :p1y 10 90 14

toot 1000 3

toot 777 6

toot 333 3

toot 111 10

end

to hyperspacep2

local “r make “r random 100

if :r > :hypersuccess [explodep2 1000 stop]

wormhole :p2x :p2y 10 90 14

make “p2x random 280 – 140

make “p2y random 192 – 96

make “p2h random 360

make “p2tc 0

wormhole :p2x :p2y 10 90 14

toot 1000 3

toot 777 6

toot 333 3

toot 111 10

end

to checkstar :x :y :size :pnum

local “d

make “d distance :x :y :starx :stary

if :d > 2 * :stars [stop]

star :x :y :size 13

if :pnum = 1 [explodep1 100]

if :pnum = 2 [explodep2 100]

end

to checkplayermissle :x :y :mx :my :size :pnum :mnum

local “d

make “d distance :x :y :mx :my

if :d > 4 * :size [stop]

star :x :y :size 13

if :pnum = 1 [explodep1 1000]

if :pnum = 2 [explodep2 1000]

if :pnum = 3 [toot 2000 10]

if :mnum = 1 [make “m1life 0 make “m1x 0 make “m1y 0]

if :mnum = 2 [make “m2life 0 make “m2x 0 make “m2y 0]

end

to checkmissiles

checkmissiles1

checkmissiles2

end

to checkmissiles1

if :m1life = 0 [stop]

if :m1life > 45 [stop]

checkplayermissle :p1x :p1y :m1x :m1y 1 1 1

checkplayermissle :p2x :p2y :m1x :m1y 1 2 1

checkplayermissle :starx :stary :m1x :m1y 2 3 1

end

to checkmissiles2

if :m2life = 0 [stop]

if :m2life > 45 [stop]

checkplayermissle :p1x :p1y :m2x :m2y 1 1 2

checkplayermissle :p2x :p2y :m2x :m2y 1 2 2

checkplayermissle :starx :stary :m2x :m2y 2 3 2

end

to checkplayerstar

checkstar :p2x :p2y :p2size 2

checkstar :p1x :p1y :p1size 1

end

to checkships

local “d

make “d distance :p1x :p1y :p2x :p2y

if :d > :p1size [stop]

star :p1x :p1y :p1size 13

star :p2x :p2y :p2size 13

explode

resetpositions

end

to movethings

movemissile1

movemissile2

moveshipp2

moveshipp1

end

to checkinputs

checkkeys

checkjs1

checkjs2

end

to checkthings

checkships

checkmissiles

checkplayerstar

checkinputs

end

to finishgame

cleartext

setcursor list 0 20

if :p1score > :p2score [ pr [PLAYER ONE WINS] ]

if :p2score > :p1score [ pr [PLAYER TWO WINS] ]

if :p2score = :p1score [ pr [DRAW] ]

pr [Type start number to start the game.]

end

to mainloop

ht

shrinktails

checkthings

movethings

frame

wait 1

make “cyclecount :cyclecount – 1

if :cyclecount > 0 [ mainloop ]

finishgame

end

to start :cycles

make “cyclecount :cycles

make “p1score 0

make “p2score 0

resetlevel

mainloop

end

start 1000