; [Mastermind game] cslsload "buttons cslsload "streams to master [:numsquares 4] [:dup.ok "false] [:mysecret "true] ; Mastermind game program. ; Program is controlled by mouse clicks or keystrokes. if :LogoPlatform = "Windows [maximize.window "true] window if :LogoPlatform = "wxWidgets [localmake "fontsize labelsize] localmake "colors "ROYGBV localmake "colornums [[R 4] [O 14] [Y 6] [G 2] [B 1] [V 13]] localmake "exact "true local [numguesses numcolors column guess gotnum winloop permuting] local [perms oldcount newcount guess.exact guess.inexact guess.word] catch "quit [forever [ catch "master [ make "numguesses 0 make "numcolors 0 make "column 0 make "winloop "false initdraw ; Clear screen, draw color palette ifelse :mysecret [ ifelse :dup.ok ; Choose secret permutation [make "secret (choose.dup :numsquares :colors)] [make "secret (choose.nodup :numsquares :colors)] newguess ; Display first guess frame action.loop ; Read keyboard characters or mouse clicks ] [ catch "win [ ; User's secret, program has to guess. ifelse :dup.ok [ make "permuting "false ; Lots of cases with dups okay, so make "perms (list copies :numsquares "x) make "newcount 0 ; find colors systematically first. catch "perm [ for [i 1 6] [ ; Learn how many red, then orange, etc. make "oldcount :newcount doguess subst :i "x head :perms make "newcount :guess.exact + :guess.inexact make "perms flatten stream.map `[insert ,[:newcount-:oldcount] ,:i ?] :perms make "perms stream.filter `[okay? ? ,:guess.exact ,:guess.inexact ,:guess.word] :perms check.consistency :perms if equalp :newcount :numsquares [throw "perm] ] check.consistency [] ; Tried all colors, user lied. ] make "permuting "true ] [ make "perms perms "123456 :numsquares ; not :dup.ok make "permuting equalp :numsquares 6 ] forever [ ; common portion doguess head :perms if equalp :numsquares :guess.exact + :guess.inexact ~ [make "permuting "true] make "perms stream.filter `[okay? ? ,:guess.exact ,:guess.inexact ,:guess.word] :perms check.consistency :perms ] ; Can't get here; either doguess finds a winner or ; check.consistency complains. ] ; We get here on throw "win from doguess. move [15 12] setpc 7 label "WIN! ct print (sentence [I win in] :numguesses "turns.) make "winloop "true action.loop ] ]]] cs ct setpc 7 st end ;;; ================== LOGIC FOR MY GUESSES (USER SECRET) ================= to doguess :guessword ; Present computer's guess to user and ask about matches. newguess ; Draw frame for guess. make "guess.word :guessword ; Remember my colors. foreach :guessword [apply "putguess item ? :colornums] ; Show colors. askexact ; Ask user for exact matches. make "gotnum "false catch "ready [action.loop] pu setpos [150 205] setpc 0 filled 0 [repeat 2 [fd 35 rt 90 fd 110 rt 90]] setpc 7 ifelse :guess.exact < :numsquares [ ; Not all colors are exact. ifelse :permuting [ ; If we know all the colors, make "exact "false ; compute how many are inexact getnum :numsquares-:guess.exact ; without asking. ] [ askinexact ; Otherwise, ask for inexact. make "gotnum "false catch "ready [action.loop] pu setpos [150 205] setpc 0 filled 0 [repeat 2 [fd 35 rt 90 fd 110 rt 90]] setpc 7 if :guess.exact + :guess.inexact > :numsquares ~ [check.consistency []] ; Quick error message if too many matches. ] ] [ throw "win ; All colors are exact, we win. ] end to subst :new :old :word ; For dups-okay guessing: Substitute the next trial color for ; all unknown squares in a partial permutation. output map [ifelse equalp ? :old [:new] [?]] :word end to copies :num :letter output cascade :num [word ? :letter] " end to insert :num :new :word ; For dups-okay guessing: We've learned that there are :NUM instances ; of color :NEW in the secret combination, so stick that many of them into ; a still-possible partial permutation, in every possible size=:NUM ; subset of the unknown slots. ; The result is a *stream* of possible (partial) permutations. if :num=0 [output (list :word)] ; No slots needed, just one result. if emptyp :word [output []] ; Not enough slots, no results! if equalp first :word "x ; Else combine results of choosing or ~ [op flatten ; not choosing to replace into this X. stream insert :num-1 :new word :new butfirst :word `[(list stream.map [word "x ?] insert ,:num ",:new bf ",:word )]] output stream.map `[word ",[first :word] ?] insert :num :new butfirst :word end to check.consistency :str ; If the stream of still-possible permutations is empty, then ; the user has lied to us. if emptyp :str [ct print [Error -- inconsistent answers!] repeat 2 [setbg 4 wait 1 setbg 0 wait 1] type [Click or type anything to restart.] wait 0 waitforclick throw "master] end to perms :word :num ; Output the stream of permutations of :NUM letters chosen from :WORD. if :num=0 [output (list "|| )] if emptyp :word [output []] ; Can't happen (would mean :num>count :word). output flatten stream.map ~ `[[letter] stream.map `[word ,:letter ?] perms remonce :letter ,:word ,[:num-1]] ~ :word end to okay? :perm :guess.exact :guess.inexact :guess.word output and (equalp :guess.exact exact :perm :guess.word) ~ (equalp :guess.inexact inexact :perm :guess.word) end to askexact ; ct type "|How many EXACT matches? | ; pu setpos [185 210] setpc 6 label "EXACT? localmake "caption.scrunch 1.5 setbutton [152 210] [100 25] [] "true 0 "EXACT? [] ern "caption.scrunch make "exact "true end to askinexact ; ct type "|How many INEXACT matches? | ; pu setpos [185 210] setpc 6 label "INEXACT? localmake "caption.scrunch 1.5 setbutton [152 210] [100 25] [] "true 0 "INEXACT? [] ern "caption.scrunch make "exact "false end ;;; ================== LOGIC FOR USER GUESSES (MY SECRET) ================= to choose.dup :number :colors if :number = 0 [output "] output word (pick :colors) (choose.nodup :number-1 :colors) end to choose.nodup :number :colors if :number = 0 [output "] make "color pick :colors output word :color (choose.nodup :number-1 remonce :color :colors) end ;;;;; ================ Used by both kinds of logic ====================== to exact :secret :guess if empty? :secret [output 0] output ehelp + (exact butfirst :secret butfirst :guess) end to ehelp ifelse equal? (first :secret) (first :guess) [output 1] [output 0] end to inexact :secret :guess output (anymatch :secret :guess) - (exact :secret :guess) end to anymatch :secret :guess if empty? :secret [output 0] if member? first :secret :guess ~ [output 1 + anymatch (butfirst :secret) (remonce first :secret :guess)] output anymatch butfirst :secret :guess end to remonce :this :those if empty? :those [output "] if equal? :this first :those [output butfirst :those] output word (first :those) (remonce :this butfirst :those) end ;;;;; =================== USER INTERFACE (DRAWING) ======================= to initdraw fs init.buttons localmake "bigwidth ifelse :LogoPlatform = "wxWidgets [5*first :fontsize] [40] localmake "bigbutton list :bigwidth 25 localmake "tallheight ifelse :LogoPlatform="wxWidgets [2+2*last :fontsize] [30] localmake "tallbutton list :bigwidth :tallheight ifelse :mysecret ~ [colorchart 6 "ROYGBV [4 14 6 2 1 13] 165 setbutton [-245 -15] :bigbutton [clear] "false 0 "erase "DEL] ~ [numchart 0 165] setbutton pos0 [-245 -45] :bigbutton [if not :winloop [guess]] "true 0 "OK "RET setbutton nxt :tallheight :tallbutton [throw "master] "false 0 [new game] "N setbutton nxt 25 :bigbutton [throw "quit] "false 0 "quit "Q ignore nxt 10 setbutton nxt :tallheight :tallbutton [make "mysecret "true throw "master] ~ :mysecret 0 [I guess] "I setbutton nxt :tallheight :tallbutton [make "mysecret "false throw "master] ~ (not :mysecret) 0 [Logo guess] "L caption [-245 206] [65 29] [Number |of colors:|] numsquares -170 2 6 caption [-10 206] [65 29] [Duplicates allowed:] ifelse :LogoPlatform = "wxWidgets [ localmake "buttonx ((first :fontsize)*10)-5 setbutton list :buttonx 210 [25 25] [make "dup.ok "true throw "master] ~ :dup.ok 0 "yes [] setbutton list :buttonx+30 210 [25 25] [make "dup.ok "false throw "master] ~ (not :dup.ok) 0 "no [] ] [ setbutton [70 210] [25 25] [make "dup.ok "true throw "master] ~ :dup.ok 0 "yes [] setbutton [100 210] [25 25] [make "dup.ok "false throw "master] ~ (not :dup.ok) 0 "no [] ] end to numsquares :xcor :num :last if :num > :last [stop] setbutton (list :xcor 210) [25 25] `[make "numsquares ,:num throw "master] ~ (:num = :numsquares) 0 :num [] numsquares :xcor+30 :num+1 :last end to colorchart :num :names :colors :ycor if :num = 0 [stop] setbutton (list -245 :ycor) [25 25] ~ `[putguess ",[first :names] ,[first :colors]] "false ~ (first :colors) [] (first :names) colorchart :num-1 bf :names bf :colors :ycor-30 end to numchart :num :ycor if :num > :numsquares [stop] setbutton (list -245 :ycor) [25 25] ~ `[if not :winloop [getnum ,:num]] "false 0 :num :num numchart :num+1 :ycor-30 end to pos0 :pos make "controlpos :pos output :pos end to nxt :height make "controlpos list (first :controlpos) ((last :controlpos) - (:height + 5)) output :controlpos end to move :start ; Move the turtle to the given coordinates ; relative to the lower left corner of the first empty square ; in the current frame. ; Depends on :COLUMN (0 or 1 for >14 guesses), :NUMGUESSES, and :NUMCOLORS ; Note, since :NUMGUESSES starts at 1, ; first frame is at [-180 170] not [-180 200]. pu setpos (list (-145 + (first :start) + 210*:column + 25*(:numcolors-1)) (200 + (last :start) - 30*(:numguesses - 14*:column))) pd end ; ----------------------------------------------- to newguess ; Called from MASTER for first guess frame, ; then from GUESS for later guess frames (my secret), ; or from DOGUESS (user's secret). make "numguesses :numguesses+1 if :numguesses > 14 [make "column 1] make "numcolors 1 move [0 0] drawframe make "guess " end to drawframe setpc 7 seth 0 repeat :numsquares [square 25 rt 90 fd 25 lt 90] end to square :side repeat 4 [fd :side rt 90] end ;;;;; =================== USER INTERFACE (READING) ======================= to waitforclick action.off ; Wait for any key or mouse click, then return, ignoring which/where. if buttonp [while [buttonp] [] stop] if keyp [ignore rc stop] waitforclick end ; ----------- Procedures to carry out user commands --------------- to getnum :num [:cursor cursor] ; Called for digit key or mouse click on digit button. make ifelse :exact ["guess.exact] ["guess.inexact] :num move list ifelse :exact [15] [35] 12 setpc 0 filled 0 [repeat 4 [fd 20 rt 90]] setpc 7 label :num ; type :num setcursor :cursor make "gotnum "true end to putguess :colorletter :colornumber ; Called from mouse click in color palette; ; first input is a letter for :GUESS (e.g. R for red), ; second input is a Logo color number for SETPC (e.g. 4 for red). if :numcolors < 1 [stop] if :numcolors > :numsquares [stop] if not :dup.ok [if member? :colorletter :guess [stop]] make "guess word :guess :colorletter move [0 0] filled :colornumber [repeat 4 [fd 25 rt 90]] make "numcolors :numcolors+1 end to clear ; Called by clicking ERASE button if :numcolors < 2 [stop] make "guess butlast :guess make "numcolors :numcolors-1 move [0 0] filled 0 [repeat 4 [fd 25 rt 90]] end to guess ; Called by clicking GUESS button. if not :mysecret [if :gotnum [ct wait 0 throw "ready] stop] if not (:numcolors > :numsquares) [stop] ifelse equal? :guess :secret [ move [15 12] setpc 7 label "WIN! print (sentence [You win in] :numguesses "turns.) ] [ move [15 12] setpc 7 label exact :secret :guess move [35 12] setpc 7 label inexact :secret :guess newguess ] end