Ancient Tongues: Logo Spacewar!


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