patternMinor
Haskell Text-Adventure Game
Viewed 0 times
textgameadventurehaskell
Problem
I just started making a text adventure game in Haskell. Because this is the largest project I have done in Haskell, I wanted to ask about it here before I got too far on it. I am still pretty new to Haskell, so I would really appreciate any advice at all on how this could be improved.
To compile the game, I just used
Here is my code:
Main.hs
Direction.hs
```
module Direction
( Direction(..)
, directionFromString
) where
import Data.Char (toLower)
data Direction = North | NorthEast | East | SouthEast
| South | SouthWest
To compile the game, I just used
ghc Main.Here is my code:
Main.hs
import System.IO (hFlush, stdout)
import Control.Monad (unless, when, guard)
import Control.Applicative ((), ())
import Data.Maybe (fromMaybe, isJust, fromJust)
import Control.Monad.State
import Data.Char (isSpace)
import Room (Room)
import qualified Room as Room
import Game (Game, GameState)
import qualified Game as Game
import Direction
import Item (Item)
import qualified Item as Item
type GameResponse = IO (String, Game)
trim :: String -> String
trim = foldr pickChars []
where
pickChars ' ' (' ':xs) = ' ':xs
pickChars c1 (x:xs) = c1:x:xs
pickChars c1 [] = [c1]
travel :: Direction -> GameState
travel d = state $ \g -> fromMaybe ("You can't go that way.", g)
(flip runState g . Game.enterRoom
Room.roomInDirection d (Game.currentRoom g))
main = do
(msg, _) GameResponse
play x = do
(msg, gameData) > putStrLn ""
putStr "> "
hFlush stdout
response getLine
if response `elem` ["q", "quit"]
then return ("Adiós!", gameData)
else play . return $ runState (exec response) gameData
exec :: String -> GameState
exec "look" = state $ \g -> (Room.nameWithDescription $ Game.currentRoom g, g)
exec s
| isJust direction = travel (fromJust direction)
| take 4 s == "take" || take 3 s == "get" = Game.takeItem s
| s `elem` ["i", "inv", "inventory"] = Game.displayInv
| s `elem` [" ", ""] = return ""
| otherwise = return "What?"
where
direction = directionFromString sDirection.hs
```
module Direction
( Direction(..)
, directionFromString
) where
import Data.Char (toLower)
data Direction = North | NorthEast | East | SouthEast
| South | SouthWest
Solution
The last two lines of
Note that
The recommendation to use abstractions theoretically also goes for
pickChars collapse to pickChars c xs = c:xs. trim = unwords . words may be to your liking.StateT can handle play's state-passery, but you'll have to redefine GameState as Monad m => StateT Game m String to allow play's IO actions.main = putStrLn =>= play) Game.gameData
play :: StateT Game IO String
play msg = do
unless (null msg) $ putStrLn msg >> putStrLn ""
putStr "> "
hFlush stdout
response getLine
if response `elem` ["q", "quit"]
then return "Adiós!"
else exec response >>= playexec "look" = gets $ Room.nameWithDescription . Game.currentRoom| Just direction runState everywhere misses the point.lens can help with nested data structures. An example:data Room = Room
{ _name :: String
, _description :: String
, _directions :: Map Direction String
, _visited :: Bool
, _items :: [Item]
} _deriving (Show, Eq)
makeFields ''Room
find :: String -> Lens' Game Room
find n = roomMap . singular (ix n)
enterRoom :: String -> GameState
enterRoom n = do
r <- use $ find n
v <- find n . Room.visited <<.= True
currentRoom .= r
return $ r ^. if v then Room.name else Room.nameWithDescriptionNote that
currentRoom is the version of r that does not have its visited set to True yet. This could have been averted if currentRoom merely contained the room's name.The recommendation to use abstractions theoretically also goes for
travel, but what's needed here isn't available in the common libraries. Using my package prototype pointed-alternative and missing StateT combinator getsT:travel :: Direction -> GameState
travel d = ascertain "You can't go that way."
$ Game.enterRoom
=<< getsT (Room.roomInDirection d . Game.currentRoom)Code Snippets
main = putStrLn =<< evalStateT (Game.initGame >>= play) Game.gameData
play :: StateT Game IO String
play msg = do
unless (null msg) $ putStrLn msg >> putStrLn ""
putStr "> "
hFlush stdout
response <- trim <$> getLine
if response `elem` ["q", "quit"]
then return "Adiós!"
else exec response >>= playdata Room = Room
{ _name :: String
, _description :: String
, _directions :: Map Direction String
, _visited :: Bool
, _items :: [Item]
} _deriving (Show, Eq)
makeFields ''Room
find :: String -> Lens' Game Room
find n = roomMap . singular (ix n)
enterRoom :: String -> GameState
enterRoom n = do
r <- use $ find n
v <- find n . Room.visited <<.= True
currentRoom .= r
return $ r ^. if v then Room.name else Room.nameWithDescriptiontravel :: Direction -> GameState
travel d = ascertain "You can't go that way."
$ Game.enterRoom
=<< getsT (Room.roomInDirection d . Game.currentRoom)Context
StackExchange Code Review Q#159069, answer score: 4
Revisions (0)
No revisions yet.