# Rock Paper Scissors game

This is a very simple rock paper scissors game implemented in Haskell.

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 <- getStdGen

humanchoice <- prompt "Rock, Paper, or Scissors: "

let (compchoice, _) = randomR (0,2) gen
compchoice' = case (compchoice :: Int) of
0 -> 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?

As @200_success mentions in a comment, this isn't a kosher use of 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 winner

• I think let (compchoice, _) = randomRIO (0,2) gen should be compchoice <- randomRIO (0,2). According to hackage.haskell.org/package/random-1.0.0.3/docs/… randomRIO doesnt get a generator and doesnt return one – yasar Apr 30 '15 at 18:34
• Any suggestions to improve the shoot function? It can get very large especially if we play Sheldon's variant: bigbangtheory.wikia.com/wiki/Rock_Paper_Scissors_Lizard_Spock – GeneralBecos Apr 30 '15 at 22:12
• @yasar D'oh! You're completely right, I switched out randomIO for randomRIO without adjusting any of the context. – bisserlis Apr 30 '15 at 22:53
• @GeneralBecos Interesting question. Provided an Enum RPS instance and a specifically ordered RPS definition, we can reduce the problem to modular arithmetic. E.g., data RPS = Rock | Paper | Scissors | Lizard | Spock deriving Enum shoot left right | result == 0 = Tie; | odd result = PlayerOne; | even result = PlayerTwo; where result = mod ((fromEnum left + 1) - (fromEnum right + 1)) 5. – bisserlis Apr 30 '15 at 23:40
• You can use random-derive to automatically create a Random instance defined in terms of Enum and Bounded. – Frerich Raabe May 3 '15 at 20:36

Just a footnote to the already excellent answer from bisserlis above. Started with a short discussion arising from the comments. I would implement the shoot function as below. It is simpler to understand (for me) + easier to extend to Rock-Paper-Scissors-Lizard-Spock

beats :: RPS -> RPS -> Bool
beats Paper    Rock     = True
beats Rock     Scissors = True
beats Scissors Paper    = True
beats _        _        = False

shoot :: RPS -> RPS -> Winner
shoot a b
| a == b      = Tie
| a beats b = PlayerOne
| otherwise   = PlayerTwo

• Guard notation would be more idiomatic and tidier than if-then-else if-then-else. – 200_success May 4 '15 at 16:15
• Oh nice, that's very clever. I might even go so far as to define beats infix, e.g., Paper beats Rock = True. – bisserlis May 5 '15 at 0:50