use Deducer.frink /** This is an implementation of the Mastermind game using the Deducer class. If you want to let it guess a target for you, you can just hit the "enter" key and watch it play. See Deducer.frink for the deducer framework. To implement this game, we have to implement 3 interfaces from the Deducer framework: * DeducerProblem * DeducerRank * DeducerMove For an example that doesn't use the Deducer framework, see mastermind.frink */ class MastermindProblem implements DeducerProblem { /* The possible colors. You can change these to whatever you want, including full names. You can also use more or fewer colors and everything will just work right. */ class var colors=["G","B","Y","P","O","R"] // You can change from the normal 4-peg game to more or fewer pegs. class var numPegs = 4 /** Rank/score a how a particular move would score against a target and return an object of type DeducerRank */ rank[move is MastermindMove, target is MastermindMove] := { return new MastermindRank[move, target] } /** Return all possible moves as an array or enumerating expression of DeducerMove appropriate for this problem. In this case, returned values are of type MastermindMove which implements DeducerMove. */ allPossibleMoves[] := { // Create an array with all possible plays. opts = new array multifor x = makeArray[[numPegs], colors] opts.push[new MastermindMove[x]] return opts } } /** The DeducerRank interface describes the methods for ranking a particular move. For example, a Mastermind ranking would include the count of black and white pegs. For Wordle, this would indicate which letters are green, which are yellow, and which are black. */ class MastermindRank implements DeducerRank { var black var white /** Construct a rank from a [black, white] array. */ new[blackWhiteArray] := { if isArray[blackWhiteArray] and length[blackWhiteArray] == 2 [black, white] = blackWhiteArray else { black = blackWhiteArray.black white = blackWhiteArray.white } } /** Construct a rank by evaluating the move against the target. */ new[move is MastermindMove, target is MastermindMove] := { black = 0 white = 0 targetCopy = target.vals.shallowCopy[] // First, count total number of matches in any position. As a match is // found in any position, remove it from the list so it's not counted // twice. for mpiece = move.vals if targetCopy.removeValue[mpiece] // true if a piece was removed white = white + 1 // Now count pieces in the correct positions. For each one found, remove // one from the "white" count and add one to the "black" count. for i = 0 to length[target.vals]-1 if move.vals@i == target.vals@i { white = white - 1 black = black + 1 } } /** Compares this rank with another rank and returns true if they are equal. */ equals[other is DeducerRank] := { return this.black == other.black and this.white == other.white } /** Returns a string representation of the rank for display to a human. */ toString[] := "Black:$black White:$white" } /** This represents a move for Mastermind. The values are stored in the array vals. These will have the same values as MastermindProblem.colors. */ class MastermindMove implements DeducerMove { /** The array of values */ var vals new[vals] := { if isString[vals] // Parse single-char names from a string badly this.vals = charList[vals] else this.vals = vals } /** Compares the goodness of this move to another move. A move is considered "better" if it has more different colors. */ compareTo[other is MastermindMove] := { return length[toSet[this.vals]] <=> length[toSet[other.vals]] } /** Returns a string representation of the move suitable for presenting to a human. */ toString[] := toString[vals] } println["0.) Computer plays itself"] println["1.) Human guesses computer word"] println["2.) Computer plays outside puzzle"] println["3.) Assistant mode"] mode = eval[input["Mode: ", "0"]] computerPicksWord = bitAnd[mode, 2] == 0 humanGuesses = bitAnd[mode, 1] != 0 guesses = 0 // Play the game. d = new Deducer[new MastermindProblem] // Pick a target play at random. if computerPicksWord target = random[d.movesRemaining[]] if mode == 0 println["Computer picks: " + target.toString[]] // The main loop. do { if humanGuesses move = new MastermindMove[uc[trim[input["Enter guess: "]]]] else move = d.pickSmartMove[] // Pick a smart move. if computerPicksWord { r1 = new MastermindRank[move, target] // Tentative rank against target if ! humanGuesses result = eval[input["Move is " + move.toString[], [["Black: ", r1.black], ["White: ", r1.white]]]] else { println["Rank is " + r1.toString[]] result = r1 } } else result = eval[input["Move is " + move.toString[], [["Black: "], ["White: "]]]] guesses = guesses + 1 rank = new MastermindRank[result] d.doMove[move,rank] // Eliminate options that can't match. println["\nPossible solutions remaining: " + d.numMovesRemaining[]] if mode == 3 { for m1 = d.movesRemaining[] print[m1.toString[] + " "] println[] } } while result@0 != MastermindProblem.numPegs and d.numMovesRemaining[] > 1 // All green? We guessed right last time! if result@0 == MastermindProblem.numPegs println["Guessed the solution correctly in $guesses guesses!"] else // Otherwise, we know what the solution will be. { if length[d.movesRemaining[]] == 0 println["No words remaining. I may have gotten bad feedback."] else println["Remaining solution is " + first[d.movesRemaining[]].toString[] + " after " + (guesses+1) + " guesses"] }