nerdleDeducer.frink

Download or view nerdleDeducer.frink in plain text format


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"]
}


Download or view nerdleDeducer.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 20217 days, 15 hours, 38 minutes ago.