Consider the below game of ‘pub darts’:
You can open it in our Javascript-based IDE by clicking here
If you hover over the various primitives in the editor, a popup will tell you what they do.
The game consists of three main parts: the dartboard, the darts and the game itself.
The dartboard is constructed primarily using the ringarc primitive and a number of repeat loops. It uses the oddp boolean to decide which color each segment of each ring should be, allowing us to match the colors of a standard dartboard.
TO board
;create the dart board
hideturtle
rt 9 setfc 1 polyspot 5 20
;bullseye
lo 0.1 setfc 4 polyspot 10 20
;outer bullseye
repeat 20 [if oddp repcount [setfc 13] [setfc 0] ringarc 25 10 20 1 rt 18]
repeat 20 [if oddp repcount [setfc 4] [setfc 1] ringarc 5 35 20 1 rt 18]
repeat 20 [if oddp repcount [setfc 13] [setfc 0] ringarc 20 40 20 1 rt 18]
repeat 20 [if oddp repcount [setfc 4] [setfc 1] ringarc 5 60 20 1 rt 18]
;rings
lo 0.1 setfc 0 cylinder 80 10 20 lt 9 penup setfc 15 rt 180
;backboard
The numbers around the outside of the dartboard are created using the inscribe and orbitleft primitives. We offset the numbers as required to center them on their relevant wedges. We also use the orbit / pullin / pullout functionality to create the wire frame overlaid on the dartboard.
dropanchor pullout 65 ra 2
foreach "i [20 1 18 4 13 6 10 15 2 17 3 19 7 16 8 11 14 9 12 5] [
lt 90 if :i > 9 [bk 10] [bk 5] inscribe :i
if :i > 9 [fd 10] [fd 5] rt 90 orbitleft 18
]
;numbers
home setpw 1.2 setpc 5 setfc 5
dropanchor tether pullout 5 orbitleft 9
repeat 6 [
pd
repeat 20 [orbitright 18 ico 0.6]
pu switch {repcount}
case 1 [pullout 5] case 2 [pullout 25]
case 3 [pullout 5] case 4 [pullout 20]
case 5 [pullout 5]
]
;metal rings
home pullout 10 orbitleft 9
repeat 20 [pd pullout 55 pu pullin 55 orbitright 18]
;metal lines
home
END
The darts are assembled using cylinders, cones, cutcones and poilyspots (for the fletchings).
TO dart :color
;create the dart models
lower 50 setfillcolor 10 cone 1 4 20
raise 20 cylinder 1 20 20
ra 10 setfc 5 cutcone 3.5 2 10 20
ra 20 setfc 10 cylinder 3 20 20
ra 5 setfc 5 cylinder 3.5 5 20
setfc item :color [11 6]
ra 20 cutcone 2.5 3 20 20
ra 5 setfc 10 cutcone 2 2.5 5 20
setfc 5 ra 5 cutcone 1.5 2 5 20
ra 10 setfc 0 cutcone 1 1.5 10 20
ra 20 cutcone 1 1 20 20 up 180
cone 1 15 20 rr 90
repeat 3 [
up 60 setfc 10 twosided
polyspot 13 6 setfc :color
ring 2 13 6
]
;fletchings
END
The game itself consists of a setup section and the main game loop. Inside the game loop we have the aiming section and the scoring section. Every three darts we switch between players. If one of the players score exceeds 301 then they are declared the winner and the game ends.
TO game
;world's smallest dart game
reset
clearfrozen
cleartext
print |Welcome to darts! Two players take turns until one scores more than 301.|
cursordown
print |Try to aim the dart with the mouse and press the mouse button to throw...|
The setup section creates the dart ‘room’, draws and ‘freezes’ the board (breaks it off into its own ‘turtle track’) and creates the dart models using the dart procedure. Default containers (variables) are created using surnames (container classes), one for each player. Surnames enable us to use the same code for each player without needing to use lists or tables.
pu sl 300 bk 300 ra 600
setfc pick [3 5 8 9] setfs 10
setbs fillshade setbg fillcolor
voxel -607 setfs 0 home
;create dart room
board
;create board
cam:pullout 180
freeze "board
newmodel "dart1 [dart 1]
newmodel "dart2 [dart 2]
;create dart models
setmodel "dart1
setmodelscale 0.7
setanchor [0 0 0]
foreach "i [red blue] [setsurname :i
rt 180 make "down forwarddir rt 180
make "updown random 2 make "leftright random 2
make "dart 0 make "frame 0 make "total 0
make "oldx mousex make "oldy mousey
]
setsurname "red
;define player containers
print word surname |'s turn...|
showturtle
setposition [0 0 250]
The main game loop moves the dart according to the mouse position. The dart moves up and down and side to side to simulate shaky hands, in an exaggerated fashion to make the game more challenging. The player pushes the left mouse button to launch the dart. The camera follows the dart in a method dictated by a random number.
forever [
;main game loop
if or :oldx != mousex :oldy != mousey [
make "oldx mousex make "oldy mousey
setposition {-100 + 200 * mousex / 100 50 - 100 * mousey / 100 250}
cam:sety myrtle:ypos
;move the dart with the mouse
]
move 1 random 360
if :updown = 0 [up 1 if and pitch < 350 pitch > 20 [make "updown 1]]
if :updown = 1 [dn 1 if and pitch < 350 pitch > 20 [make "updown 0]]
if :leftright = 0 [rl 0.5 if and roll < 350 roll > 10 [make "leftright 1]]
if :leftright = 1 [rr 0.5 if and roll < 350 roll > 10 [make "leftright 0]]
;shaky hands, maybe try drinking herbal tea?
wait 1
;delay
if buttonp 0 [
;if button clicked:
make "camdir random 4
;pick random camera action
dountil (item 3 extrapolate position vectorsub updir [0 0 0] 35) < 0 [
;until the tip of the dart hits the board (basically):
setpremodel {"lt loopcount}
;spin the dart model
lo 1 dn 0.1 cam:pullin 1
;toward the board and down a little
if :camdir != 3 [cam:sety myrtle:ypos]
if :camdir = 1 [cam:orbitleft 0.3]
if :camdir = 2 [cam:orbitright 0.3]
if :camdir = 3 [cam:orbitup 0.3]
;camera actions
]
Once the dart reaches the wall / board, we calculate if the dart hit the board or the wall by checking its distance from the center of the dart board [0 0 0]. If it actually hit the board then we 'stamp' the model in place.
norender
;stop rendering graphics while we deal with things
ht lo 34
make "vec vectors
setvectors originvectors
updatestate
;need to update the state for towards to work
;with rendering disabled
make "dir towards [0 0]
;where did we land relative to the center?
if and :dir >= 171 :dir < 189 [make "score 20]
if and :dir >= 189 :dir < 207 [make "score 1]
if and :dir >= 207 :dir < 225 [make "score 18]
if and :dir >= 225 :dir < 243 [make "score 4]
if and :dir >= 243 :dir < 261 [make "score 13]
if and :dir >= 261 :dir < 279 [make "score 6]
if and :dir >= 279 :dir < 297 [make "score 10]
if and :dir >= 297 :dir < 315 [make "score 15]
if and :dir >= 315 :dir < 333 [make "score 2]
if and :dir >= 333 :dir < 351 [make "score 17]
if or :dir >= 351 :dir < 9 [make "score 3]
if and :dir >= 9 :dir < 27 [make "score 19]
if and :dir >= 27 :dir < 45 [make "score 7]
if and :dir >= 45 :dir < 63 [make "score 16]
if and :dir >= 63 :dir < 81 [make "score 8]
if and :dir >= 81 :dir < 99 [make "score 11]
if and :dir >= 99 :dir < 117 [make "score 14]
if and :dir >= 117 :dir < 135 [make "score 9]
if and :dir >= 135 :dir < 153 [make "score 12]
if and :dir >= 153 :dir < 171 [make "score 5]
;calculate dart position and assign score
make "dist distance extrapolate position vectorsub updir [0 0 0] zpos [0 0 0]
;how far away from the center is the tip of the dart?
if :dist <= 5 [make "score 50]
;bullseye!
if and :dist > 5 :dist < 10 [make "score 25]
;half bullseye
if and :dist >= 35 :dist <= 40 [make "score :score * 3]
;triple ring
if and :dist >= 60 :dist <= 65 [make "score :score * 2]
;double ring
if :dist > 65 [make "score 0]
;missed!
We use the towards primitive (an original Apple Logo II primitive!) to determine the number of degrees the landed dart (which is in reality pointed upwards toward the ceiling, the dart 'descending' towards the board along the Z axis) would have to turn to face [0 0]. This, combined with the distance from the center allows us to calculate the dart's score.
(print |Dart| word :dart + 1 |:| :score) make "frame :frame + :score
setvectors :vec
if :dist < 79 [
;'stick' a dart to the board using stamp
playsound "click2
ra 34
run premodel
if surname = "red [stamp "dart1] [stamp "dart2]
render wait 120
]
else [
playsound "knock
pr "Missed! ra 34 st render
repeat (ypos + 300) / 4 [drift 4 :down cam:lo 4 wait 1]
]
;drop the dart to the floor
cam:cs
;reset the camera
setheading 0 setpitch 0 setroll 0
setvectors originvectors
;reset state
make "updown random 2
make "leftright random 2
;pick random starting wobble directions
;for the next dart
inc "dart
;add one to :dart
if :dart = 3 [
;if we've thrown three darts:
(print |Frame Score: | :frame)
make "total :total + :frame
(print word surname |'s Total Score: | :total)
make "dart 0 make "frame 0
cam:pushturtle
cam:run pick [[repeat 30 [orbitleft 1 wait 1]] [repeat 30 [orbitright 1 wait 1]]]
wait 120 cs cam:popturtle
if :total > 301 [(print surname "wins!) finish]
;the end
if surname = "red [setsurname "blue setmodel "dart2]
else [setsurname "red setmodel "dart1]
print word surname |'s turn...|
;switch players
]
setpremodel [] cam:pullout 180 setposition [0 0 250] showturtle
;position camera, reset 'spin', show the next dart
]
;end of throw
]
;end of main game loop
END
Logo code is like poetry! It's easy to read and describes what the computer is doing in fairly broad terms. This is why it has always been great as a first text-based coding language.