patternMinor
Rock Paper Scissors game
Viewed 0 times
scissorspapergamerock
Problem
This is a very simple rock paper scissors game implemented in Haskell.
All kinds of reviews are welcome, but I am specifically interested in knowing what kind of beginner mistakes I might have done. For example, are there things that I could simplify, or are things that you wouldn't see in a intermediate level haskell code?
import System.IO (hFlush, stdout)
import System.Random (getStdGen, randomR)
data RPS = Rock | Paper | Scissors deriving (Eq, Show, Read)
instance Ord RPS where
Rock `compare` Scissors = GT
Scissors `compare` Rock = LT
Paper `compare` Rock = GT
Rock `compare` Paper = LT
Scissors `compare` Paper = GT
Paper `compare` Scissors = LT
_ `compare` _ = EQ
prompt :: String -> IO String
prompt text = do
putStr text
hFlush stdout
getLine
main = do
gen Rock
1 -> Paper
2 -> Scissors
humanchoice' = read humanchoice :: RPS
case compchoice' `compare` humanchoice' of
EQ -> putStrLn "Tie."
GT -> putStrLn "You Lose :("
LT -> putStrLn "You Win! :)"All kinds of reviews are welcome, but I am specifically interested in knowing what kind of beginner mistakes I might have done. For example, are there things that I could simplify, or are things that you wouldn't see in a intermediate level haskell code?
Solution
As @200_success mentions in a comment, this isn't a kosher use of
What you should do instead is just write a grab bag of pure functions and domain-specific datatypes. This would look something like—
All top-level definitions should have a type signature always, full stop.
Since you're not doing anything fancy with your
One typeclass it would be appropriate to instance is
The last thing I would do would be to factor out the result message code from
And with all of those changes you end up with a
Ord. I believe though that it is the property of antisymmetry that is broken, not transitivity. \$Rock \leq Paper \leq Scissors \leq Rock\$ means that by transitivity \$Paper \leq Rock\$, which violates antisymmetry since \$Rock \neq Paper\$. This may seem academic, but many library functions will break or produce incoherent results given your instance. Try to guess what sort [Rock, Scissors, Paper] will return. Don't get in the habit of writing bad instances just because you like their namespace.What you should do instead is just write a grab bag of pure functions and domain-specific datatypes. This would look something like—
data Winner = PlayerOne | PlayerTwo | Tie
shoot :: RPS -> RPS -> Winner
shoot Rock Rock = Tie
shoot Rock Paper = PlayerTwo
shoot Rock Scissors = PlayerOne
-- ...All top-level definitions should have a type signature always, full stop.
main is always main :: IO ().Since you're not doing anything fancy with your
StdGen, just use the *IO functions.main = do
humanchoice <- prompt "Rock, Paper, or Scissors: "
compchoice <- randomRIO (0,2)
-- ...One typeclass it would be appropriate to instance is
Random, so that you can have a more semantic computer-player interface. I'll toss in Enum and Bounded instances because they make the Random instance more future-proof.data RPS = Rock | Paper | Scissors
deriving (Eq, Show, Read, Enum, Bounded)
instance Random RPS where
random g = randomR (minBound, maxBound) g
randomR (lo, hi) g = first toEnum $ randomR (fromEnum lo, fromEnum hi) g
where first f (a, b) = (f a, b)The last thing I would do would be to factor out the result message code from
main, it's a little repetitive having putStrLn there three times, and you can conceive of various scenarios like making this two player where you'd want to be able to easily swap around result message generators.humanTwo :: Winner -> String
humanTwo PlayerOne = "You lose :("
humanTwo PlayerTwo = "You win! :)"
humanTwo Tie = "Tie."And with all of those changes you end up with a
main function like—main :: IO ()
main = do
humanchoice <- fmap read $ prompt "Rock, Paper or Scissors: "
compchoice <- randomIO
let winner = shoot compchoice humanchoice
putStrLn $ humanTwo winnerCode Snippets
data Winner = PlayerOne | PlayerTwo | Tie
shoot :: RPS -> RPS -> Winner
shoot Rock Rock = Tie
shoot Rock Paper = PlayerTwo
shoot Rock Scissors = PlayerOne
-- ...main = do
humanchoice <- prompt "Rock, Paper, or Scissors: "
compchoice <- randomRIO (0,2)
-- ...data RPS = Rock | Paper | Scissors
deriving (Eq, Show, Read, Enum, Bounded)
instance Random RPS where
random g = randomR (minBound, maxBound) g
randomR (lo, hi) g = first toEnum $ randomR (fromEnum lo, fromEnum hi) g
where first f (a, b) = (f a, b)humanTwo :: Winner -> String
humanTwo PlayerOne = "You lose :("
humanTwo PlayerTwo = "You win! :)"
humanTwo Tie = "Tie."main :: IO ()
main = do
humanchoice <- fmap read $ prompt "Rock, Paper or Scissors: "
compchoice <- randomIO
let winner = shoot compchoice humanchoice
putStrLn $ humanTwo winnerContext
StackExchange Code Review Q#88395, answer score: 5
Revisions (0)
No revisions yet.