[Date Prev][Date Next][Thread Prev][Thread Next]
[Search]
[Date Index]
[Thread Index]
Re: [FWP] A "Cheating" hangman dealer
Ronald J Kimball wrote:
>
> I'm afraid this solution does not use Quantum::Superpositions. But it was
> fun to write anyway. :)
I'm afraid that my solution does not even use Perl, but it too was
fun to write. :)
I apologize to those folks who may feel it off-list, but of late I've
been finding much Fun the practice of converting FWP challenges into
Haskell code. Since Perl is eager, imperative, and rather flexible in
syntax, and Haskell is lazy, purely functional, and relatively set in
its syntax, things that Perl can do well make for particularly
interesting Haskell challenges. The Cheating Hangman challenge
was no exception.
The Literate Haskell source code for my Cheating Hangman can be found
here (use Save As because the PDF Viewer plug-in was having trouble
with the last one):
http://www.ellium.com/~thor/hangman/cheating-hangman.lhs
http://www.ellium.com/~thor/hangman/cheating-hangman.pdf
The code portion of the source is just 75 lines and included below.
(The PDF version is much easier to read.)
Take a look at the findBestSubset routine. It's called when the player
guesses a letter that we can't ``cheat away.'' For example, if the
cheat list was down to ["forces", "stumps", "stones"] and the player
guessed "s", we would be forced to reveal at least one "s" in some
position(s) of our hidden word. We could reveal ".....s", in which
case we would have to reduce our cheat list to ["forces"] because
the other two words don't match the ".....s" pattern, leaving
little room to cheat later. The routine, however, does some work
to ensure that we pick the optimum position(s) to reveal, in this
example "s....s", so that the cheat list remains as long as possible
(["stumps", "stones"]). This leaves more opportunity to cheat
later. The extra smarts (or should I say dishonesty) comes at a
cost of four lines of code, which seems well worth the effort if
we can cheat an extra guess out of the player.
It's interesting to note that for languages that many would
consider to be on the opposite ends of the programming spectrum,
Haskell and Perl have much in common. Consider the correlations
between list functions, for example: filter=>grep, map=>map,
sort=>sort. Also consider that Perl lets you write largely
functional programs if you want to restrict yourself that way.
Also, both languages are Very Fun to code in.
Cheers,
Tom
=== begin cheating-hangman.hs
module Main (main) where
import List
import Random
import Char
import System
type GameState = (Words, Guesses, StdGen)
type Words = [String]
type Guesses = [Char]
defaultDictionary = "/usr/dict/words" :: FilePath
main :: IO ()
main = do
args <- getArgs
pnam <- getProgName
let usage = "Usage: " ++ pnam ++ ": wordlen [dictionary]\n"
dictionary = case args of
_:[] -> defaultDictionary
_:d:[] -> d
_ -> error usage
wordLen = case args of x:_ -> x; _ -> error usage
hangman (read wordLen) dictionary
hangman :: Int -> FilePath -> IO ()
hangman wordLen dictionary = do
rnd <- getStdGen
dictWords <- readFile dictionary
let gameWords = filter (\w -> length w == wordLen && all isLower w) $
words $ dictWords
state = (gameWords, [], rnd) :: GameState
playTurn state
playTurn :: GameState -> IO ()
playTurn state@(_, gs, _) = do
putStrLn . stateToStr $ state
if gameOverQ state
then do putStrLn $ "Game over in " ++ show (length gs) ++ " guesses."
else do
putStr "Your guess? "
guess <- getLine
case guess of
[] -> do playTurn state -- no guess
g:_ -> if g `elem` gs
then do putStrLn $ "You already guessed `" ++ [g] ++ "'."
playTurn state
else do let (state', msg) = applyGuess state g
putStrLn msg
playTurn state'
gameOverQ :: GameState -> Bool
gameOverQ (ws, gs, _) =
let firstWord = head ws in
firstWord == filter (`elem` gs) firstWord
stateToStr :: GameState -> String
stateToStr (ws, gs, _) =
let wordRep = map (\c -> if c `elem` gs then c else '.') $ head ws
wordsLeft = show $ length ws
in "\n" ++ wordRep ++ " [" ++ gs ++ "] (words=" ++ wordsLeft
++ (if length ws <= 5 then " " ++ show ws else "") ++ ")"
applyGuess :: GameState -> Char -> (GameState, String)
applyGuess (ws, gs, rnd) g =
let gs' = sort (g:gs)
ws' = filter (g `notElem`) ws -- try to cheat
in
if not (null ws') then ((ws', gs', rnd), "Sorry!") -- we cheat!
else
-- darn! we can't cheat away this guess, but we can
-- pick the ``matches'' that maximize future cheating
let (ws'', rnd') = findBestSubset ws g rnd
in ((ws'', gs', rnd'), "Good guess!")
findBestSubset :: Words -> Char -> StdGen -> (Words, StdGen)
findBestSubset ws g rnd =
let matchIndices = group . sort . map (elemIndices g) $ ws
matchLensAndInds = map (\mi -> (length mi, head mi)) matchIndices
maxLen = maximum $ map fst matchLensAndInds
maxSubsetInds = map snd $ filter ((==maxLen).fst) matchLensAndInds
(pick, rnd') = randomR (0, length maxSubsetInds - 1) rnd
bestIndices = maxSubsetInds !! pick
bestSubset = filter ((==bestIndices) . elemIndices g) ws
in (bestSubset, rnd')
=== end cheating-hangman.hs
==== Want to unsubscribe from Fun With Perl? Well, if you insist...
==== Send email to <fwp-request@technofile.org> with message _body_
==== unsubscribe