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