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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
|
import System.Random
import Control.Monad
import Data.Bits
import Control.Monad.Trans.State
import Control.Monad.IO.Class
monstersNum :: Int
monstersNum = 12
data Player = Player { pHealth :: Int,
pAgility :: Int,
pStrength :: Int }
instance Show Player where
show (Player h a s) =
"You are a valiant knight with a health of " ++ show h ++
", an agility of " ++ show a ++
", and a strenght of " ++ show s ++ "."
type HitPoint = Int
class Monster a where
health :: a -> Int
description :: a -> String
hit :: a -> HitPoint -> a
attack :: a -> Player -> IO (Player, MonsterInstance)
data MonsterInstance = forall a. Monster a => MonsterInstance a
data Orc = Orc { oHealth :: Int,
clubLevel :: Int }
instance Monster Orc where
health (Orc h _) = h
description (Orc _ cl) = "A wicked Orc with a level " ++ show cl ++ " club."
hit orc hitPoints = orc { oHealth = oHealth orc hitPoints }
attack orc (Player h a s) = do
blowPower <- randVal $ clubLevel orc
putStrLn $ "An orc swings his club at you and knocks off "
++ show blowPower ++ " of your healths points."
return (Player (h blowPower) a s, MonsterInstance orc)
orcBuilder :: IO MonsterInstance
orcBuilder = do
orcHealth <- randVal 10
orcClubLevel <- randVal 8
return (MonsterInstance $ Orc orcHealth orcClubLevel)
randVal :: Int -> IO Int
randVal n = randomRIO (1, n)
data Hydra = Hydra { hHealth :: Int }
instance Monster Hydra where
health (Hydra h) = h
description (Hydra h) = "A malicious Hydra with " ++ show h
++ " heads."
hit hydra hitPoints = hydra { hHealth = hHealth hydra hitPoints }
attack hydra@(Hydra hh) (Player h a s) = do
blowPower <- randVal (hHealth hydra `shiftR` 1)
putStrLn $ "A hydra attacks you with " ++ show hh ++ " of its heads!"
putStrLn "It also grows back one more head!"
return (Player (h blowPower) a s, MonsterInstance (Hydra (hh + 1)))
hydraBuilder :: IO MonsterInstance
hydraBuilder = do
hydraHealth <- randVal 10
return (MonsterInstance $ Hydra hydraHealth)
data Brigand = Brigand { bHealth :: Int }
instance Monster Brigand where
health (Brigand h) = h
description (Brigand _) = "A fierce Brigand."
hit brig hitPoints = brig { bHealth = bHealth brig hitPoints }
attack brig (Player h a s) = chooseBest $ maximum [h,a,s]
where
chooseBest b
| b == h = do
putStrLn "A brigand hits you with his slingshot, taking off 2 health points!"
return (Player (h 2) a s, MonsterInstance brig)
| b == a = do
putStrLn "A brigand catches your leg with his whip, taking off 2 agility points!"
return (Player h (a 2) s, MonsterInstance brig)
| otherwise = do
putStrLn "A brigand cuts your arm with his whip, taking off 2 strength points!"
return (Player h a (s 2), MonsterInstance brig)
brigandBuilder :: IO MonsterInstance
brigandBuilder = do
brigandHealth <- randVal 10
return (MonsterInstance $ Brigand brigandHealth)
data Slime = Slime { sHealth :: Int,
sSliminess :: Int}
instance Monster Slime where
health (Slime h _) = h
description (Slime _ s) = "A slime mold with a sliminess of "
++ show s
hit slime hitPoints = slime { sHealth = sHealth slime hitPoints }
attack slime (Player h a s) = do
blowPower <- randVal $ sSliminess slime
putStrLn $ "A slime mold wraps around your legs and "
++ "decreases your agility by " ++ show blowPower ++ "!"
squirtProbability <- randVal 3
if squirtProbability == 1
then do
putStrLn "It also squirts in your face, taking away a health point!"
return (Player (h 1) (a blowPower) s, MonsterInstance slime)
else return (Player h (a blowPower) s, MonsterInstance slime)
slimeBuilder :: IO MonsterInstance
slimeBuilder = do
slimeHealth <- randVal 10
sliminess <- randVal 5
return (MonsterInstance $ Slime slimeHealth sliminess)
hitInstance :: MonsterInstance -> HitPoint -> MonsterInstance
hitInstance (MonsterInstance m) hp = MonsterInstance $ hit m hp
monsterBuilders :: [IO MonsterInstance]
monsterBuilders = [orcBuilder, hydraBuilder, brigandBuilder, slimeBuilder]
type WorldState = (Player, [MonsterInstance])
type Game = StateT WorldState IO ()
main :: IO ()
main = do
world <- initWorld
evalStateT gameLoop world
print "Thanks for playing."
return ()
initWorld :: IO WorldState
initWorld =
let player = Player 30 30 30
in do
monsters <- forM [1 .. monstersNum] (\_ ->
do
builderNum <- randVal $ length monsterBuilders
monsterBuilders !! (builderNum 1))
return (player, monsters)
gameLoop :: Game
gameLoop = do
(player, monsters) <- get
let endCondition = isPlayerDead player || areMonstersDead monsters
when (isPlayerDead player) (liftIO $ print "You are dead.")
when (areMonstersDead monsters) (liftIO $ print "You win!.")
unless endCondition $ do
liftIO $ print player
liftIO $ showMonsters monsters
playerAttack
monstersAttack
gameLoop
isPlayerDead :: Player -> Bool
isPlayerDead p = pHealth p <= 0
isMonsterDead :: MonsterInstance -> Bool
isMonsterDead (MonsterInstance m) = health m <= 0
areMonstersDead :: [MonsterInstance] -> Bool
areMonstersDead = all isMonsterDead
showMonsters :: [MonsterInstance] -> IO ()
showMonsters ms = do
putStrLn ""
putStrLn "Your foes:"
forM_ [1 .. length ms] showMonsterAtPos
where
showMonsterAtPos :: Int -> IO ()
showMonsterAtPos idx =
let monster = ms !! (idx 1)
in if isMonsterDead monster
then putStrLn $ " " ++ show idx ++ ". *DEAD*"
else showAliveMonster monster
where
showAliveMonster :: MonsterInstance -> IO ()
showAliveMonster (MonsterInstance m) =
putStrLn $ " " ++ show idx ++ ". (Health="
++ show (health m) ++ ") "
++ show (description m)
playerAttack :: Game
playerAttack = do
liftIO $ putStrLn ""
liftIO $ putStrLn "Attack style: [s]tab [d]ouble swing [r]oundhouse:"
liftIO $ putStr "?> "
!attackType <- liftIO getLine
case attackType of
"s" -> performStab
"d" -> performDoubleSwing
"r" -> performRoundhouse
_ -> do
liftIO $ putStrLn "Wrong attack type. Choose between s,d or r."
playerAttack
where
performStab :: Game
performStab = do
(p, m) <- get
damage <- liftIO $ randomStrength p
liftIO $ putStrLn $ "You are stabbing a random monster with a power of "
++ show damage
!newMonsterPark <- liftIO $ monsterHit m damage
put (p, newMonsterPark)
where
randomStrength p = do
r <- randVal $ shiftR (pStrength p 1) 1
return (r + 2)
performDoubleSwing :: Game
performDoubleSwing = do
(p,m) <- get
let upperBound = truncate (fromIntegral (pStrength p) / 6 :: Double)
swingPower <- liftIO $ randVal upperBound
mIdx <- liftIO $ pickMonster m
liftIO $ putStrLn $ "Double swing at monster #" ++ show (mIdx + 1)
attackMonsterAt mIdx swingPower
attackMonsterAt mIdx swingPower
performRoundhouse :: Game
performRoundhouse = do
(p,_) <- get
let times = 1 + truncate (fromIntegral (pStrength p) / 3 :: Double)
forM_ [0..times]
(\_ -> do
(_,m) <- get
newPark <- liftIO $ monsterHit m 1
put (p, newPark))
pickMonster :: [MonsterInstance] -> IO Int
pickMonster monsters = do
putStrLn "Choose the monster to attack: "
mIdx <- getLine
let idx = read mIdx
if idx >= 1 && idx <= length monsters
then return $ idx 1
else do
putStrLn "Please choose a valid monster."
pickMonster monsters
attackMonsterAt :: Int -> Int -> Game
attackMonsterAt idx pow = do
(p,m) <- get
newPark <- liftIO $ forM [ 0 .. (length m 1)]
(\i -> if i == idx
then return (hitInstance (m !! idx) pow)
else return (m !! i))
put (p, newPark)
monsterHit :: [MonsterInstance] -> HitPoint -> IO [MonsterInstance]
monsterHit monsters hp = do
!mIdx <- randomMonster monsters
monstersHitAux mIdx
where
monstersHitAux mIdx =
forM [0 .. (length monsters 1)]
(\i ->
if i == mIdx
then return (hitInstance (monsters !! mIdx) hp)
else return (monsters !! i))
randomMonster :: [MonsterInstance] -> IO Int
randomMonster monsters = do
randomIdx <- randVal $ length monsters
let pickedMonster = monsters !! (randomIdx 1)
in if isMonsterDead pickedMonster
then randomMonster monsters
else return (randomIdx 1)
monstersAttack :: Game
monstersAttack = do
ws <- get
newState <- liftIO $ aux ws []
put newState
where
aux :: WorldState -> [MonsterInstance] -> IO WorldState
aux (p,[]) newPark = return (p, newPark)
aux (p, MonsterInstance monster : xs) park =
if isMonsterDead (MonsterInstance monster)
then aux (p, xs) (park ++ [MonsterInstance monster])
else do
(newP, newM) <- attack monster p
aux (newP, xs) (park ++ [newM])
|