mastermindDeducer.frink

Download or view mastermindDeducer.frink in plain text format


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



Download or view mastermindDeducer.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