use Deducer.frink /** This plays Wordle using the Deducer framework. 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 */ class WordleProblem implements DeducerProblem { // Number of letters in the game var numChars // Create a new game with the specified number of letters. new[numChars=5] := { this.numChars = numChars } /** Rank/score a particular move with respect to target and return an object of type WordleRank */ rank[move is WordleMove, target is WordleMove] := { return new WordleRank[move, target] } /** Return all possible moves as an array or enumerating expression of WordleMove appropriate for this problem. */ allPossibleMoves[] := { opts = new array for opt = select[lines["file:/home/eliasen/prog/mobydict/scrabble/sowpods.txt"], {|x, data| length[x] == data}, numChars] opts.push[new WordleMove[opt]] return opts } } /** This represents a move for a Wordle game. */ class WordleMove implements DeducerMove { var word // The word as a string var chars // The word as an array of chars /** Construct a WordleMove for the specified word. */ new[word] := { this.word = word chars = charList[word] } /** Compares the goodness of this move to another move. A move is considered "better" if it has more different letters. */ compareTo[other is WordleMove] := { return length[toSet[this.chars]] <=> length[toSet[other.chars]] } /** Returns a string representation of the move suitable for presenting to a human. */ toString[] := word } /** This implements DeducerRank to represent a rank for a Wordle move. Its data is an array of chars like ["B", "B", "Y", "G", "G"] */ class WordleRank implements DeducerRank { var result // An array of chars like ["B", "B", "Y", "G", "G"] /** Create a new WordleRank by determining how good a move was at matching the specified target. */ new[move is WordleMove, target is WordleMove] := { targetCopy = target.chars.shallowCopy[] result = new array[[length[target.chars]], undef] for i = rangeOf[move.chars] if (move.chars)@i == (target.chars)@i { result@i = "G" targetCopy.removeValue[(move.chars)@i] } for i = rangeOf[move.chars] if result@i == undef { if targetCopy.removeValue[(move.chars)@i] result@i = "Y" else result@i = "B" } } /** Construct a WordleRank from a string like "BBYGG" */ new[str] := { result = charList[str] } /** Compares this rank with another rank and returns true if they are equal. */ equals[other is DeducerRank] := { return result == other.result } /** Returns a string representation of the rank for display to a human. */ toString[] := { return join["", result] } } /** Main play loop. */ chars = eval[input["Number of letters: ", 5]] 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 // Play the game. d = new Deducer[new WordleProblem[chars]] // Pick a target play at random. if computerPicksWord target = random[d.movesRemaining[]] if mode == 0 println["Computer picks: " + target.toString[]] winCondition = repeat["G", chars] guesses = 0 // The main loop. do { if humanGuesses move = new WordleMove[uc[trim[input["Enter guess: "]]]] else move = d.pickSmartMove[] // Pick a smart move. if computerPicksWord { r1 = new WordleRank[move, target] // Tentative rank against target if ! humanGuesses result = uc[input["Rank of " + move.toString[], r1.toString[]]] else { println["Rank is " + r1.toString[]] result = r1.toString[] } } else result = uc[input["Rank of " + move.toString[] + ": "]] guesses = guesses + 1 rank = new WordleRank[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 rank.toString[] != winCondition and d.numMovesRemaining[] > 1 // All green? We guessed right last time! if rank.toString[] == winCondition 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. Either the word is not in my list or I got bad feedback."] else println["Remaining solution is " + first[d.movesRemaining[]].toString[] + " after " + (guesses+1) + " guesses"] }