packages feed

timberc-1.0.1: examples/MasterMind.t

module MasterMind where

import Data.Functional.List
import RandomGenerator
import POSIX

{-
Standard Mastermind, where the program does the guessing. Answers are given as two integers (separated by space(s)) on 
one row, indicating number of bulls and cows. Command line argument acts as seed for random number generation.
-}

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)]

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
           
root 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,Answer e n) : 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