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, eliasen@mindspring.com