MasterMind

MasterMind is a board game with two players; in this case the user chooses the secret and the program does the guessing. Here is a sample interaction:
examples> ./MasterMind
Welcome to Mastermind!
Choose your secret. Press return when ready.

My guess: Red Blue Blue Red
Answer (two integers): 1 1
My guess: Blue Black Yellow Red
Answer (two integers): 2 0
My guess: Green Black Red Red
Answer (two integers): 0 0
My guess: Blue Blue Yellow White
Answer (two integers): 3 0
My guess: Blue Blue Yellow Blue
Answer (two integers): 3 0
My guess: Blue Blue Yellow Yellow
Answer (two integers): 4 0
Yippee!
Do you want to play again? (y/n) n
examples> 
The program is rather long and is appended at the end of this page. We only note a few things:
module MasterMind where

import Data.Functional.List
import RandomGenerator
import POSIX

data Colour = Red | Blue | Green | Yellow | Black | White

default eqColour :: Eq Colour
        showColour :: Show Colour 
        parseColour :: Parse Colour
        eqAnswer :: Eq Answer

type Guess = [Colour]

data Answer = Answer Int Int

instance showAnswer :: Show Answer where
   show (Answer e n) = show e ++ " " ++ show n

instance showGuess :: Show Guess where
  show ss = unwords (map show ss)

type Board = [(Guess,Answer)]

allColours = [Red, Blue, Green, Yellow, Black, White]

allCodes :: Int -> [Guess]
allCodes 0 = [[]]
allCodes n = concat [ [c:cs | c <- allColours] 
                    | cs <- allCodes (n-1)]

mkAnswer :: String -> Answer
mkAnswer cs = Answer e n
    where [e,n] = map parse (words cs)

answer :: Guess -> Guess -> Answer
answer guess code = Answer e n
   where 
      e = equals guess code

      n  = sum [ min (count c guess) (count c code) 
               | c <- allColours] - e

      count c xs = length [x | x <- xs, x==c]

      equals [] []  = 0
      equals (x:xs) (y:ys)
        | x==y      = 1 + equals xs ys
        | otherwise = equals xs ys


contradictions :: Board -> Guess -> Board
contradictions board c = 
      [ (g,r) | (g,r) <- board, answer g c /= r ]

consistent :: Board -> [Guess] -> [Guess]
consistent board cs = 
      [ c | c <- cs, null (contradictions board c) ]

data State = Idle | JustGuessed | GameOver | GetSecret
           
mastermind env = class
  
  gen = new baseGen (microsecOf env.startTime)

  board := []
  cs := [] 
  state := Idle

  shift = do
     r <- gen.next
     n = r `mod` (length cs)
     ys = take n cs
     zs = drop n cs
     cs := zs ++ ys
  
  startGame = do
    board := []
    cs := []
    env.stdout.write 
          "Choose your secret. Press return when ready.\n"
    state := Idle

  mkGuess = do
     if null cs then
         env.stdout.write "Contradictory answers!\n"
         env.stdout.write "Tell me your secret: "
         state := GetSecret
     else
         shift
         env.stdout.write ("My guess: "++show (head cs)++"\n")
         env.stdout.write "Answer (two integers): "
         state := JustGuessed

  checkQuit = do
     env.stdout.write "Do you want to play again? (y/n) "
     state := GameOver

  inpHandler inp = action
    case state of
      Idle ->        
          cs := allCodes 4
          mkGuess

      JustGuessed ->
          case map parse (words inp)  of
             [Right e, Right n] ->
                 if e == 4 then
                     env.stdout.write "Yippee!\n"
                     checkQuit 
                else
                     c:cs' = cs
                     board := (c,ans) : board
                     cs := consistent board cs'
                     mkGuess
             _ -> env.stdout.write "Answer must be two integers separated by spaces; try again\n"
       
      GameOver ->
          if head inp == 'y' then
              startGame
          else
              env.exit 0

      GetSecret ->
        case map parse (words inp) of
          es | length es==4
              && all isRight es ->
                  ss = map fromRight es
                  (g',r'):_ = contradictions board ss
                  env.stdout.write ("When I guessed "++show g'++ 
                  ", you answered "++show r'++".\n")
                  env.stdout.write ("Correct answer should have been "++
                                   show (answer g' ss)++".\n")
                  checkQuit
          _ -> env.stdout.write "Secret must be four colours separated by spaces; try again\n"

  result action
     env.stdin.installR inpHandler
     env.stdout.write "Welcome to Mastermind!\n"
     startGame
         
root = newRoot mastermind