use Deducer.frink /** This plays Nerdle using the Deducer framework. https://nerdlegame.com/ 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 NerdleProblem implements DeducerProblem { // Number of letters in the game var numChars // Create a new game with the specified number of letters. new[numChars=8] := { this.numChars = numChars } /** Rank/score a particular move with respect to target and return an object of type NerdleRank */ rank[move is NerdleMove, target is NerdleMove] := { return new NerdleRank[move, target] } /** Return all possible moves as an array or enumerating expression of NerdleMove appropriate for this problem. */ allPossibleMoves[] := { filename = "nerdle$numChars.txt" f = newJava["java.io.File", filename] if ! f.exists[] or f.length[] == 0 generateMoves[] opts = new array for opt = lines[f] opts.push[new NerdleMove[opt]] return opts } /** If no moves have been calculated for this game, calculate them and save them to a file. */ generateMoves[] := { num = toArray["0" to "9"] numNonZero = toArray["1" to "9"] ops = ["+", "-", "*", "/"] opSet = toSet[ops] numOrOp = concat[num, ops] opts = new array w = new Writer["nerdle$numChars.txt", "UTF-8", false, 32768] wd = new Writer["nerdle${numChars}d.txt", "UTF-8", false, 32768] for len=2 to numChars-2 { args = new array args.push[numNonZero] for i=1 to len-2 args.push[numOrOp] args.push[num] VAL: multifor val = args { left = joinstr[val] len1 = length[left] lastop = false for i=1 to len1-2 { c = substrLen[left, i, 1] if opSet.contains[c] if lastop next VAL i else lastop = true else lastop = false } for [numeric] = left =~ %r/([0-9]+)/g // No lone zero on left if left[numeric, 1] == "0" next VAL right = eval[left] if ! isInteger[right] or right < 0 // Positive integers only on right next VAL eq = left + "=" + right if length[eq] != numChars // Make sure solution is right length next VAL if eq =~ %r/[^0-9]0[0-9]/ // Number won't start with 0 next VAL opts = new array w.println[eq] print[eq] if allDifferent[charList[eq]] { wd.println[eq] println[" *"] } else println[] opts.push[new NerdleMove[eq]] } } w.close[] wd.close[] } } /** This represents a move for a Nerdle game. */ class NerdleMove implements DeducerMove { var word // The word as a string var chars // The word as an array of chars /** Construct a NerdleMove 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 symbols. */ compareTo[other is NerdleMove] := { 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 Nerdle move. Its data is an array of chars like ["B", "B", "Y", "G", "G"] */ class NerdleRank implements DeducerRank { var result // An array of chars like ["B", "B", "Y", "G", "G"] /** Create a new NerdleRank by determining how good a move was at matching the specified target. */ new[move is NerdleMove, target is NerdleMove] := { 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 NerdleRank 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 joinstr[result] } } /** Main play loop. */ chars = eval[input["Number of letters: ", "8"]] 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 NerdleProblem[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 NerdleMove[uc[trim[input["Enter guess: "]]]] else move = d.pickSmartMove[] if computerPicksWord { r1 = new NerdleRank[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 NerdleRank[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 solutions remaining. Either the solution is not in my list or I got bad feedback."] else println["Remaining solution is " + first[d.movesRemaining[]].toString[] + " after " + (guesses+1) + " guesses"] }