-
Notifications
You must be signed in to change notification settings - Fork 0
/
RockPaperScissors.hs
149 lines (125 loc) · 4.26 KB
/
RockPaperScissors.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE ViewPatterns #-}
module Games.RockPaperScissors where
import Prelude hiding (getChar, putStrLn, getLine, readLn)
import Data.Time
import GHC.IO.Unsafe (unsafePerformIO)
import Test.IOTasks
import Data.List (inits)
import Test.IOTasks.ValueMap
import Test.IOTasks.Var
import Test.IOTasks.OutputPattern
{- Taken with slight modification from
- "Haskell, the craft of functional programming"
-
- The specification does not work for the hacky
- randomStrategy that uses unsafePerformIO.
-}
specification :: Strategy -> Specification
specification strategy =
tillExit (
readInput move (fromList [0,1,2,-1]) AssumeValid <>
branch (currentValue move `isNotIn` listLit [0,1,2])
exit
(writeOutput [wildcard <> text "I play: " <> resultOf opponentMove <> text " you play: " <> resultOf yourMove])
) <>
writeOutput [resultOf finalTournament]
where
move = intVar "m"
yourMove :: Term 'PartiallyOpaque String
yourMove = liftOpaque (show . convertToMove,"show move") $ currentValue move
opponentMove :: Term 'PartiallyOpaque String
opponentMove = liftOpaque (show . strategy . map convertToMove,"apply strategy") $ valuesBefore 1 move
finalTournament :: Term 'PartiallyOpaque String
finalTournament = liftOpaque (displayResults . reconstructTournament strategy . map convertToMove . tail,"show results") $ allValues move
reconstructTournament :: Strategy -> [Move] -> Tournament
reconstructTournament strategy (reverse -> moves) = (,moves) $ map strategy $ init $ inits $ moves
testP :: OutputPattern 'SpecificationP
testP = resultOf $ liftOpaque (id :: Integer -> Integer,"show move") $ currentValue move
-- testP = resultOf $ liftOpaque (show . convertToMove,"show move") $ currentValue move
where move = intVar "move"
testM :: ValueMap
testM = insertValue (wrapValue @Integer 1) someMove $ emptyValueMap [someMove]
where someMove = someVar $ intVar "move"
play :: MonadTeletype io => Strategy -> io ()
play strategy =
playInteractive strategy ([],[])
-- replacement for getChar, so the specification can work in Integers
getMoveChar :: MonadTeletype io => io Char
getMoveChar = do
x <- readLn
pure $ case x of
0 -> 'R'
1 -> 'P'
2 -> 'S'
-1 -> 'q'
playInteractive :: MonadTeletype io => Strategy -> Tournament -> io ()
playInteractive s t@(mine,yours) = do
-- ch <- getChar
ch <- getMoveChar
if ch `notElem` "rpsRPS"
then showResults t
else do
let next = s yours
let yourMove = convertMove ch
putStrLn ("\nI play: " ++ show next ++ " you play: " ++ show yourMove)
playInteractive s (next:mine, yourMove:yours)
showResults :: MonadTeletype io => Tournament -> io ()
showResults = putStrLn . displayResults
displayResults :: Tournament -> String
displayResults t =
case compare (result t) 0 of
GT -> "I won!"
EQ -> "Draw!"
LT -> "You won: well done!"
data Move = Rock | Paper | Scissors
deriving (Show,Eq)
type Tournament = ([Move],[Move])
outcome :: Move -> Move -> Integer
outcome Rock Rock = 0
outcome Paper Paper = 0
outcome Scissors Scissors = 0
outcome Rock Scissors = 1
outcome Scissors Paper = 1
outcome Paper Rock = 1
outcome Rock Paper = -1
outcome Paper Scissors = -1
outcome Scissors Rock = -1
result :: Tournament -> Integer
result = sum . uncurry (zipWith outcome)
type Strategy = [Move] -> Move
rock, paper, scissors :: Strategy
rock _ = Rock
paper _ = Paper
scissors _ = Scissors
cycle :: Strategy
cycle moves =
case length moves `rem` 3 of
0 -> Rock
1 -> Paper
2 -> Scissors
_ -> error "impossible"
randomStrategy :: Strategy
randomStrategy _ = convertToMove $ randInt 3
convertMove :: Char -> Move
convertMove 'r' = Rock
convertMove 'R' = Rock
convertMove 'p' = Paper
convertMove 'P' = Paper
convertMove 's' = Scissors
convertMove 'S' = Scissors
convertMove _ = error "invalid argument"
convertToMove :: Integer -> Move
convertToMove 0 = Rock
convertToMove 1 = Paper
convertToMove 2 = Scissors
convertToMove _ = error "invalid argument"
randomInt :: Integer -> IO Integer
randomInt n =
do
time <- getCurrentTime
return ( (`rem` n) $ read $ take 6 $ formatTime defaultTimeLocale "%q" time)
randInt :: Integer -> Integer
randInt = unsafePerformIO . randomInt