ScatterGrams is a two-player game of scrambled words. It is fully described in the Terrapin Logo Tutorial Manual.
; ScatterGrams ; ; A game of jumbled words in which two players ; compete for the highest score. Players can ; control the word size and turtle speed. ; The setup routine is invoked automatically after ; the ScatterGrams.lgo file is loaded into the ; workspace. The WAIT statement causes the ; Listener is 'sleep' in that it will not respond ; to any keystrokes made by the players during ; the game. ; ; It would seem like nothing could run with the ; Listener asleep, but since the game is actually ; started with a click of a button, this is not ; a problem. to setup ; Differences between Logo 3 and Logo 4; make "this.version 3 ; default since that's where it started if member? "|Version 4| version make "this.version 4 ; adjustments are needed setup.screen setup.globals demo.screen pprop "scores "visible "true wait 999999999 ; force Listener to sleep end ; The setup.screen routine adjusts the Listener ; and Grahpics windows, creates an additional ; graphic window, and them places the player controls ; in the extra graphic window. to setup.screen command findmenuid "window "|standard window layout| wrap ; make the Listener invisible pprop "Listener "visible "false ; setup Graphics screen pprop "Graphics "title "|ScatterGrams| pprop "Graphics "drawsize [457 457] ; get rid of the color picker and the toolbox pprop "colorpicker "visible "false pprop "toolbox "visible "false ; setup the extra graphics screen for player controls ; initally invisible until all controls are placed declare "graphics "scores pprop "scores "visible "false pprop "scores "drawsize [157 457] pprop "scores "position [465 0] pprop "scores "title "|Scores| ; setup player 1 controls declare "editbox "name.1 pprop "name.1 "size [150 20] pprop "name.1 "position [0 213] pprop "name.1 "text "|Player 1 put name here| declare "listbox "word.list.1 pprop "word.list.1 "size [150 68] pprop "word.list.1 "position [0 170] repeat 3 [ask "word.list.1 [remove 0]] declare "statictext "score.1 pprop "score.1 "size [150 16] pprop "score.1 "position [0 125] pprop "score.1 "text "|Score: 0| declare "scrollbar "speed.1 pprop "speed.1 "size [150 13] pprop "speed.1 "position [0 108] if :this.version = 3 [pprop "speed.1 "minimum 20][pprop "speed.1 "minimum 100] if :this.version = 3 [pprop "speed.1 "maximum 200][pprop "speed.1 "maximum 800] if :this.version = 3 [pprop "speed.1 "value 100][pprop "speed.1 "value 300] declare "statictext "speednote.1 pprop "speednote.1 "size [150 16] pprop "speednote.1 "position [0 93] pprop "speednote.1 "text "| Slow <-- Turtle speed --> Fast| declare "popup "word.size.1 pprop "word.size.1 "size [150 21] pprop "word.size.1 "position [0 72] repeat 3 [ask "word.size.1 [remove 0]] ignore ask "word.size.1 [append "|Random word size|] ignore ask "word.size.1 [append "|3-letter words|] ignore ask "word.size.1 [append "|4-letter words|] ignore ask "word.size.1 [append "|5-letter words|] ignore ask "word.size.1 [append "|6-letter words|] ignore ask "word.size.1 [append "|7-letter words|] ignore ask "word.size.1 [append "|8-letter words|] ignore ask "word.size.1 [append "|9-letter words|] ignore ask "word.size.1 [append "|10-letter words|] ignore ask "word.size.1 [append "|11-letter words|] ignore ask "word.size.1 [append "|12-letter words|] pprop "word.size.1 "index 0 ; setup player 2 controls declare "editbox "name.2 pprop "name.2 "size [150 20] pprop "name.2 "position [0 -70] pprop "name.2 "text "|Player 2 put name here| declare "listbox "word.list.2 pprop "word.list.2 "size [150 68] pprop "word.list.2 "position [0 -113] repeat 3 [ask "word.list.2 [remove 0]] declare "statictext "score.2 pprop "score.2 "size [150 16] pprop "score.2 "position [0 -158] pprop "score.2 "text "|Score: 0| declare "scrollbar "speed.2 pprop "speed.2 "size [150 13] pprop "speed.2 "position [0 -175] if :this.version = 3 [pprop "speed.2 "minimum 20][pprop "speed.2 "minimum 100] if :this.version = 3 [pprop "speed.2 "maximum 200][pprop "speed.2 "maximum 800] if :this.version = 3 [pprop "speed.2 "value 100][pprop "speed.2 "value 300] declare "statictext "speednote.2 pprop "speednote.2 "size [150 16] pprop "speednote.2 "position [0 -190] pprop "speednote.2 "text "| Slow <-- Turtle speed --> Fast| declare "popup "word.size.2 pprop "word.size.2 "size [150 21] pprop "word.size.2 "position [0 -211] repeat 3 [ask "word.size.2 [remove 0]] ignore ask "word.size.2 [append "|Random word size|] ignore ask "word.size.2 [append "|3-letter words|] ignore ask "word.size.2 [append "|4-letter words|] ignore ask "word.size.2 [append "|5-letter words|] ignore ask "word.size.2 [append "|6-letter words|] ignore ask "word.size.2 [append "|7-letter words|] ignore ask "word.size.2 [append "|8-letter words|] ignore ask "word.size.2 [append "|9-letter words|] ignore ask "word.size.2 [append "|10-letter words|] ignore ask "word.size.2 [append "|11-letter words|] ignore ask "word.size.2 [append "|12-letter words|] pprop "word.size.2 "index 0 ; place buttons initially disabled declare "button "play.button pprop "play.button "position [0 30] pprop "play.button "text "|Play| pprop "play.button "run [make "process.id launch [play.game]] pprop "play.button "enabled "false declare "button "instruction.button pprop "instruction.button "position [0 0] pprop "instruction.button "text "|Instructions| pprop "instruction.button "run [show.instructions] pprop "instruction.button "enabled "false declare "button "quit.button pprop "quit.button "position [0 -30] pprop "quit.button "text "|Quit| pprop "quit.button "run [quit.routine] pprop "quit.button "enabled "false ; now make the controls visible pprop "scores "visible "true end ; The demo.screen routine in invoked by setup ; and sets up SCATTERGRAMS as the secret word ; which is then automatically solved. to demo.screen make "secret "scattergrams make "scattered jumble :secret make "solved.x :solved.x.anchor - 32 * (count :secret) / 2 assign.turtles :scattered wait 3000 auto.solve :secret pprop "play.button "enabled "true pprop "instruction.button "enabled "true pprop "quit.button "enabled "true end ; The assign.turtles routine creates the turtles needed ; for the current secret word which has been jumbled and ; passed as an input. Turtle.1 gets the first letter, ; Turtle.2 gets the second letter, and so on. Turtle.0 ; is not used because I wanted the letter position and ; the turtle number to be the same. The turtles are made ; 50% larger than normal with SETTS (set turtle size). ; I make sure the turtles are showing (ST), their pens ; are up (drawing lines here would be messy), and each ; is assigned a random color (PICK :MY.COLORS). I set the ; turtles' SHAPELOCK attribute so the letters would always ; be upright and easier to read. Turtle headings are set ; using a formula similar to drawing a polygon. I decided ; that this symmetry was the best way to go even though the ; game is called ScatterGrams; a random heading assignment ; could have given multiple turtles the same heading which ; would have resulted in overlapped letters. Turtles are ; set in motion (SETVELOCITY) based on the setting of the ; current player's speed control. ; ; NOTE: Some of these commands had to been done with EACH ; so that each individual turtle exectues the command ; for itself. For example, if PICK :MY.COLORS was not ; done inside EACH, all the turtles would end up the ; same color. Other commands are applied to all active ; turtles at the same time and in the same way. to assign.turtles :word setactivewindow "graphics setturtles 1 + count :word ask 0 [ht] tellall 1 count :word each [setshape (word "~ (item who :word) ".bmp)] setts 1.5 each [showturtle penup setpc pick :my.colors] each [pprop who "shapelock "true] each [setheading who * (360 / count :word)] setvelocity current.speed end ; The auto.solve routine steps through the SCATTERGRAMS word ; and calls the disable routine for each letter. to auto.solve :word if empty? :word [stop] disable first :word auto.solve butfirst :word end ; The disable routine is called when a correct letter ; has been typed. The position number of the letter in ; the :scattered variable is the number of the turtle ; to work with. The turtle size is reset to normal, the ; speed is set to zero, the turtle is positioned at the ; bottom of the screen, a random musical note is played, ; and the who list is updated. Once a letter has been ; guessed and the corresponding turtle's speed has been set ; to zero, I don't want that turtle included in the list of ; currently active turtles. Also, the :scattered ; variable is updated by replacing the letter with a ; hyphen so that this letter will not be used again and ; also to keep the other letters in their original ; positions (remember that letter positions correspond ; to turtle numbers). to disable :letter (local "turtle "current.who) make "current.who .who make "turtle find :letter :scattered make "scattered mark :letter :scattered if :this.version = 3 [pprop :turtle "velocity 0][pprop :turtle "crawl 1] pprop :turtle "size [31 31] pprop :turtle "position solved.position play list pick [o3 o4 o5] word 16 pick [a b c d e f g] make "current.who butmember :turtle :current.who tell :current.who end ; The find routine outputs the position number of ; the first occurrence of :letter in its input word. ; It does not matter if the word has duplicate letters. to find :letter :word if :letter = first :word [output 1] output 1 + find :letter butfirst :word end ; The mark routine substitutes a hyphen in place of ; the first occurrence of :letter in its input word. ; It does not matter if the word has duplicate letters. to mark :letter :word if :letter = first :word [output word "- butfirst :word] output word first :word mark :letter butfirst :word end ; The solved.position routine outputs a list containing the ; x and y coordinates of where a turtle will be placed after ; its letter has been guessed. The :solved.x variable is ; updated for the next position. to solved.position local "temp make "temp list :solved.x :solved.y make "solved.x :solved.x + :solved.x.inc output :temp end ; The setup.globals procedure assigns values to variables that may be used ; by many procedures. I know there have been discussions about whether or ; not globals should be used at all, but I think this is a reasonable thing ; to do for this game. I felt it was simpler to have these global variables ; accessible to any procedure without worrying about remembering to pass them ; as parameters. to setup.globals make "word.list.3 [cat hat bat sat dog log hog the him her his our tot rot got too \ sit mop man see son sun set hop one pun pin tin lot fun fan sap zip hip ray dig big \ fig rig pig red bed run nun win sip tie two cry sin fin can jam ran bit fit bee cow \ set pet wet bug rug hug put nut bag gag rag tag lip car jar far pen hen ask toe arm \ egg fly dry why try low sew mix six fix] make "word.list.4 [colt that what logo pogo good look mint book them ruin fail safe \ tree said bolt yell blue pink show from prom fast past buzz nest door best vein part \ rest cook look most bake rake lake fish dish wish hand sand band land crab bear deaf \ drip fall tall ball call wall base case vase dime tell bell vest test nail rail sail \ cart beam seam pail tail task doll pill hill will bill fill pair hair nose knee gate \ foot tear pear show fear skin skip clip bird word moon lamp fame ring sing] make "word.list.5 [print diary space place pizza salad green crime fight might light \ sight today mouse house cream dream flask earth still photo chair thumb elbow ankle \ wrist waist mouth grape melon apple peach lemon brush heard jewel equal angel fruit \ plant paint sheet shirt pants socks radio world great guest fling thing heart blood \ uncle horse nurse floor clock throw three eight cover] make "word.list.6 [marine people rewind legend career hockey museum artist sister \ stereo finger orange yellow cherry banana breeze mirror branch cheese planet flower \ remote pillow bureau sermon church scream drawer hangar people mother father nephew \ doctor candle basket tissue saucer temper health] make "word.list.7 [receive vacuums beatles picture pitcher printer spinach survive \ bundles players between buttons physics] make "word.list.8 [receiver headache comedian keyboard baseball football function \ macaroni terrapin reaching addition whenever] make "word.list.9 [comedians macintosh telephone procedure spaghetti astronaut \ cartesian agreement functions geography chemistry implement workspace saxophone] make "word.list.10 [astronauts procedures microscope background winchester \ characters television earthquake beekeepers] make "word.list.11 [interactive approximate evaporation radioactive vaccination \ information programming mathematics instruction illustrator entertainer hummingbird] make "word.list.12 [hummingbirds neighborhood entertainers condensation subprocedure \ approximates instructions experimental encyclopedia] make "min.word.size 3 make "max.word.size 12 make "already.used  make "solved.x.anchor 0 make "solved.y -200 make "solved.x.inc 32 make "scattered " make "current.player 1 ; To make this work with Logo 3 and Logo 4, I switched the colors to RGB triplets to ; avoid color name errors. ; It was: make "my.colors butmember "white bm "yellow bm "silver bm "gray colors make "my.colors [[0 0 0] [139 0 0] [255 0 0] [255 165 0] [0 255 0] [0 255 255] [0 128 0] [0 0 255] [0 0 128] [255 0 255] [128 0 128] [165 42 42] [255 192 203] [255 215 0]] make "total.score.1 0 make "total.score.2 0 make "words.per.game 5 end ; The setup.replay routine is invoked by either the 'Restart game' button ; or the 'Play again' button (actually they are the same button but the ; TEXT attribute has been changed). Certain global variables need to be ; reset and then the play.game procedure is launched to start a new game. to setup.replay if :this.version = 3 [(halt :process.id)][halt :process.id] repeat 5 [ask "word.list.1 [remove 0]] repeat 5 [ask "word.list.2 [remove 0]] pprop "score.1 "text 0 pprop "score.2 "text 0 make "total.score.1 0 make "total.score.2 0 make "already.used  tell every "turtle hideturtle wait 1000 make "process.id launch [play.game] end ; The unique.random procedure selects a new random word for each round. ; Words that have already been chosen are kept in the :already.used ; list so they will not be chosen again for the current game. to unique.random :word.list local "selection make "selection item (random count :word.list) :word.list if member? :selection :already.used [output unique.random :word.list] make "already.used fput :selection :already.used output :selection end ; The jumble, jumble1 and remove.item procedures work together to ; mix up the letters of a word. to jumble :word if (count :word) < 2 [output :word] output jumble1 :word random count :word end to jumble1 :word :n if (count :word) < 2 [output :word] output word (item :n :word) (jumble1 (remove.item :n :word) (random ((count :word) - 1))) end to remove.item :n :word if :n < 2 [output butfirst :word] output word (first :word) (remove.item (:n - 1) (butfirst :word)) end ; The current.word.list procedure was made up as a shortcut for ; referencing the list of words to pick a word from. Notice that ; it uses the current.word.size procedure which outputs a number ; based on the current player's word size control setting. to current.word.list output thing word "word.list. current.word.size end ; The current.word.size procedure uses the index of the currently ; selected item in the current player's word size control to ; determine the word size to use for this round. The first item ; in the word size control is index 0 which contains the phrase ; 'Random word size.' Index 1 contains '3-letter words, Index 2 ; contains '4-letter words' and so on. to current.word.size local "index make "index gprop (word "word.size. :current.player) "index if :index = 0 \ [output random.between :min.word.size :max.word.size] \ [output 2 + :index] end ; The current.speed procedure outputs the value of the ; current player's speed control. to current.speed output round gprop (word "speed. :current.player) "value end ; The random.between procedure outputs a random number ; that is between the starting value :a and the ending ; value :b. to random.between :a :b output (:a - 1) + random (:b - :a + 1) end to show.instructions if empty? gprop "Instructions "visible [create.instructions] make "standard.output gprop "Instructions "channel setactivewindow "Instructions print "~C print "|Welcome to ScatterGrams!| print " print "|It's a fun game of jumbled words and letters.| print " print "|The object is to type the word more quickly and| print "|accurately than your opponent to score more| print "|points.| print " print "|You can control the word size and the turtle speed| print "|but keep in mind that longer words and faster turtles| print "|usually mean a higher score.| print " print "|So, fill in your names, make your selections, and| print "|press PLAY to begin the fun.| end to create.instructions declare "output "Instructions pprop "Instructions "position [0 0] pprop "Instructions "drawsize [457 457] end ; The play.game routine is the real starting point of a game. It ; uses the subprocedures play.rounds, play.round, play.word and ; play.letter. Notice the changes that are made to the play button. ; When ScatterGrams is first loaded into the workspace, this button ; is labeled 'Play'. Once a game has started, the button's text is ; changed to 'Restart game' and the RUN attribute is changed to ; invoke the setup.replay procedure. to play.game setactivewindow "Graphics pprop "play.button "run [setup.replay] pprop "play.button "text "|Restart game| play.rounds :words.per.game wait 1000 game.done end ; The play.rounds procedure is essentially a loop that ; counts down the number of rounds (defined as 5 in ; setup.globals). So, when it gets down to 1, it is ; really at the last round of the game. If player 2 is ; already ahead in the score by the last round, then ; there is no need to play this round. (Just like in ; baseball when the home team is ahead in the last ; inning.) to play.rounds :rounds if :rounds = 0 [stop] make "current.player 1 play.round if and (:rounds = 1) (:total.score.2 > :total.score.1) [stop] make "current.player 2 play.round play.rounds :rounds - 1 end ; The play.round procedure selects the secret word for ; this round, resets the location for the solved letters, ; assigns the turtles, resets the typo counter, gets the ; starting time, clears any buffered keystrokes, and ; then invokes play.word. ; ; NOTE: The RUN attribute of the current player's speed ; control is changed here to allow just this ; player to affect the turtle speed. to play.round get.ready make "secret unique.random current.word.list make "scattered jumble :secret make "solved.x :solved.x.anchor - 32 * (count :secret) / 2 assign.turtles :scattered pprop word "speed. :current.player "run [setvelocity current.speed] make "typos 0 make "time.0 time play.word :secret end ; The play.word procedure is just a loop that invokes ; play.letter for each letter of its input word. After ; all the letters have been played, it invokes the ; word.finish procedure and stops. to play.word :word if empty? :word [word.finish (discard 0) stop] play.letter first :word play.word butfirst :word end ; The play.letter procedure is where most of the ; games time is spent. This procedure reads the ; keyboard input and compares it to the letter ; given as input. When a match is found, the ; corresponding turtle is disabled and this ; procedure stops. Any other input is a typo. to play.letter :letter while [not equal? :letter (uppercase char getbyte)] [ make "typos :typos + 1 play [m10 n56 r] ] disable :letter end ; The word.finish procedure gets the end time, updates the ; current player's word list with the word that was just ; played along with its score, updates the current player's ; total score, and clears the RUN attribute of the current ; player's speed control. to word.finish local "put.word make "time.1 time make "put.word parse (word "append char 32 char 34 char 124 lowercase :secret char 32 char 40 word.score char 41 char 124) ignore ask word "word.list. :current.player :put.word update (word "total.score. :current.player) word.score pprop (word "score. :current.player) "text (word "|Score: | (thing word "total.score. :current.player)) pprop word "speed. :current.player "run  end ; The word.score procedure compute the score of the word just played ; based on the length of the word, the number of seconds it took for ; the player to solve the word, the number of typos that were made, ; and the speed at which the turtles were moving. ; ; NOTE: If you want adjust the scoring, this is where to do it. to word.score (local "start.time "end.time "time.penalty "points.per.letter "temp.score) make "start.time (3600 * first :time.0) + (60 * first bf :time.0) + last :time.0 make "end.time (3600 * first :time.1) + (60 * first bf :time.1) + last :time.1 make "time.penalty :end.time - :start.time - count :secret make "points.per.letter ((current.speed / 10) * (:max.word.size / count :secret)) make "temp.score round (:points.per.letter * ((count :secret) - (:typos / 2)) - :time.penalty) if :temp.score < 0 [output 0] output :temp.score end ; The game.done procedure determines the winner (or a tie) and ; uses the winner's name (or 'TIE GAME') as the secret word ; which it then automatically solves. Once the game is ; finished, the 'Restart game' button is changed to 'Play again'. to game.done (discard 0) make "secret get.winner make "scattered jumble :secret make "solved.x :solved.x.anchor - 32 * (count :secret) / 2 assign.turtles :scattered if :this.version = 3 [setvelocity 100][setvelocity 200] wait 3000 auto.solve :secret tellall 1 count :secret setheading 0 if :this.version = 3 [setvelocity 60][setvelocity 100] pprop "play.button "text "|Play again| end ; The get.winner procedure output either the name of ; the winning player or 'TIE GAME' if it's a tie score. to get.winner if :total.score.1 = :total.score.2 [output "|tie game|] if :total.score.1 > :total.score.2 [output get.name 1] output get.name 2 end ; The get.name procedure uses format.name and displayable? to ; make up the name of the winning player. There is a limit to ; how many and which characters can be displayed. I only have ; shapes for letters, digits and a blank. to get.name :player local "winner make "winner uppercase gprop word "name. :player "text if empty? :winner [output word "player :player] make "winner format.name :winner :max.word.size if empty? :winner [otuput word "player :player] output :winner end to format.name :name :max if empty? :name [output " ] if :max = 0 [output " ] if displayable? first :name [output word (first :name) (format.name butfirst :name :max - 1)] output format.name butfirst :name :max end to displayable? :letter if :letter = char 32 [output "true] output member? :letter "abcdefghijklmnopqrstuvwxyz0123456789 end ; The update routine uses indirect references to add one ; thing to another. to update :item :value make :item (thing :item) + :value end ; The get.ready routine is just a pause while one player turns over the ; control of the keyboard to another player before the round starts. ; I did not want the timer to start until a player was ready to go. to get.ready (discard 0) ignore alert (word "|Player | :current.player "| -- Just press Enter when ready|) end to quit.routine command findmenuid "debug "|restart logo| end TO ABOUT (LOCAL "LF "PP "SAMPLE.TEXT "P1 "P2 "P3 "P4 "P5 "P6 "P7 "P8 "P9 "P10) MAKE "LF CHAR 10 MAKE "PP WORD :LF :LF MAKE "P1 ScatterGrams is a fun game of scrambled words written by Stan Munson. MAKE "P2 It is fully explained in the Terrapin Logo Tutorial Manual. MAKE "SAMPLE.TEXT (WORD :P1 :PP :P2) IGNORE ALERT :SAMPLE.TEXT END ; Startup routine setup