-
Notifications
You must be signed in to change notification settings - Fork 75
/
Copy path02-Domain.purs
148 lines (117 loc) · 4.9 KB
/
02-Domain.purs
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
module RandomNumber.Free.Standard.Domain (BackendEffectsF(..), GenRandomIntF(..), API_F(..), Game, game) where
import Prelude
import Control.Monad.Free (Free, liftF)
import Data.Either (Either(..))
import Data.Functor.Coproduct (Coproduct)
import Data.Functor.Coproduct.Inject (inj)
import Data.Int (fromString)
import Data.Maybe (Maybe(..))
import RandomNumber.Core (Bounds, mkBounds, totalPossibleGuesses, RandomInt, mkRandomInt, Guess, mkGuess, RemainingGuesses, mkRemainingGuesses, outOfGuesses, decrement, (==#), GameResult(..))
-- | Defines the effects we'll need to run
-- | this game via Node or the Browser
data BackendEffectsF a
= Log String a
| GetUserInput String (String -> a)
derive instance functorBackendEffectsF :: Functor BackendEffectsF
-- | Defines the random number generating effects
-- | that works regardless of which backend we use
data GenRandomIntF a
= GenRandomInt Bounds (Int -> a)
derive instance functorGenRandomIntF :: Functor GenRandomIntF
-- Our entire API as a language
type API_F = Coproduct BackendEffectsF GenRandomIntF
-- `Free` stuff
type Game = Free API_F
getUserInput :: String -> Game String
getUserInput prompt = liftF $ inj (GetUserInput prompt identity)
log :: String -> Game Unit
log msg = liftF $ inj (Log msg unit)
genRandomInt :: Bounds -> Game Int
genRandomInt bounds = liftF $ inj (GenRandomInt bounds identity)
game :: Game GameResult
game = do
-- explain rules
log "This is a random integer guessing game. In this game, you must try \
\to guess the random integer before running out of guesses."
-- setup game
log "Before we play the game, the computer needs you to define two things."
bounds <- defineBounds
totalGueses <- defineTotalGuesses bounds
randomInt <- generateRandomInt bounds
log $ "Everything is set. You will have " <> show totalGueses <>
" guesses to guess a number between " <> show bounds <>
". Good luck!"
-- play game
result <- gameLoop bounds randomInt totalGueses
case result of
PlayerWins remaining -> do
log "Player won!"
log $ "Player guessed the random number with " <>
show remaining <> " try(s) remaining."
PlayerLoses randomInt' -> do
log "Player lost!"
log $ "The number was: " <> show randomInt'
-- return game result
pure result
-- | Calls `makeGuess` recursively until either the random number is
-- | correctly guessed or the player runs out of guesses
gameLoop :: Bounds -> RandomInt -> RemainingGuesses -> Game GameResult
gameLoop bounds randomInt remaining
| outOfGuesses remaining = pure $ PlayerLoses randomInt
| otherwise = do
let remaining' = decrement remaining
guess <- makeGuess bounds
if guess ==# randomInt
then pure $ PlayerWins remaining'
else do
log $ "Incorrect. You have " <> show remaining' <> " guesses remaining."
gameLoop bounds randomInt remaining'
-- domain -> API
getIntFromUser :: String -> Game Int
getIntFromUser prompt =
recursivelyRunUntilPure (inputIsInt <$> getUserInput prompt)
defineBounds :: Game Bounds
defineBounds = do
log "Please, define the range from which to choose a random integer. \
\This could be something easy like '1 to 5' or something hard like \
\`1 to 100`. The range can also include negative numbers \
\(e.g. '-10 to -1' or '-100 to 100')"
bounds <- recursivelyRunUntilPure
(mkBounds
<$> getIntFromUser "Please enter either the lower or upper bound: "
<*> getIntFromUser "Please enter the other bound: ")
log $ "The random number will be between " <> show bounds <> "."
pure bounds
defineTotalGuesses :: Bounds -> Game RemainingGuesses
defineTotalGuesses bounds = do
log $ "Please, define the number of guesses you will have. This must \
\be a postive number. Note: due to the bounds you defined, there are "
<> (show $ totalPossibleGuesses bounds) <> " possible answers."
totalGuesses <- recursivelyRunUntilPure
(mkRemainingGuesses <$>
getIntFromUser "Please enter the total number of guesses: ")
log $ "You have limited yourself to " <> show totalGuesses <> " guesses."
pure totalGuesses
generateRandomInt :: Bounds -> Game RandomInt
generateRandomInt bounds = do
recursivelyRunUntilPure
(mkRandomInt bounds <$> genRandomInt bounds)
makeGuess :: Bounds -> Game Guess
makeGuess bounds = do
recursivelyRunUntilPure
((mkGuess bounds) <$> getIntFromUser "Your guess: ")
recursivelyRunUntilPure :: forall e a. Show e => Game (Either e a) -> Game a
recursivelyRunUntilPure computation = do
result <- computation
case result of
Left e -> do
log $ show e <> " Please try again."
recursivelyRunUntilPure computation
Right a -> pure a
data InputError = NotAnInt String
instance ies :: Show InputError where
show (NotAnInt s) = "User inputted a non-integer value: " <> s
inputIsInt :: String -> Either InputError Int
inputIsInt s = case fromString s of
Just i -> Right i
Nothing -> Left $ NotAnInt s