Tutorial: Fancy a game of darts?
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.