crem-0.1.1.0: examples/Crem/Example/TheHobbit.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wmissing-deriving-strategies
{-# OPTIONS_GHC -Wno-missing-deriving-strategies #-}
#if __GLASGOW_HASKELL__ >= 908
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag-Wmissing-poly-kind-signatures
{-# OPTIONS_GHC -Wno-missing-poly-kind-signatures #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag-Wmissing-role-annotations
{-# OPTIONS_GHC -Wno-missing-role-annotations #-}
#endif
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wunticked-promoted-constructors
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
-- https://downloads.haskell.org/ghc/latest/docs/users_guide/using-warnings.html#ghc-flag--Wunused-type-patterns
{-# OPTIONS_GHC -Wno-unused-type-patterns #-}
-- | This is a small adventure game based on [The Hobbit](https://en.wikipedia.org/wiki/The_Hobbit_(1982_video_game))
-- video game. You can play it by running @cabal run hobbit-game@. It will ask
-- you to enter commands. You need to use the precise names of the constructors
-- for the `HobbitCommand` data type, like `GoEast`, `GoWest` and so on.
-- Moreover, with the @cabal run hobbit-map@ command, you could generate, from
-- the definition of the state machine, a diagram with the map of the game.
module Crem.Example.TheHobbit where
import Crem.BaseMachine
import Crem.Render.RenderableVertices (AllVertices (..), RenderableVertices)
import Crem.Topology
import "base" Data.Semigroup
import "singletons-base" Data.Singletons.Base.TH
data HobbitCommand
= GoEast
| GoWest
| GoNorth
| GoSouth
| Wait
| GetKey
| UnlockDoor
deriving stock (Eq, Read, Show)
newtype HobbitMessage = HobbitMessage {getMessage :: String}
deriving (Semigroup) via (Last HobbitMessage)
instance Monoid HobbitMessage where
mempty = HobbitMessage ""
$( singletons
[d|
data HobbitVertex
= TunnelLikeHall
| Lonelands
| TrollsClearing
| Rivendell
| MistyMountain
| TrollsPath
| TrollsCave
deriving stock (Eq, Show, Enum, Bounded)
hobbitTopology :: Topology HobbitVertex
hobbitTopology =
Topology
[ (TunnelLikeHall, [Lonelands])
, (Lonelands, [TunnelLikeHall, TrollsClearing])
, (TrollsClearing, [Rivendell, TrollsPath])
, (Rivendell, [TrollsClearing, MistyMountain])
, (MistyMountain, [Rivendell])
, (TrollsPath, [TrollsClearing, TrollsCave])
, (TrollsCave, [TrollsPath])
]
|]
)
deriving via AllVertices HobbitVertex instance RenderableVertices HobbitVertex
data KeyState
= NoKey
| DayDawned
| GotKey
| DoorUnlocked
deriving stock (Eq)
data HobbitState (vertex :: HobbitVertex) where
TunnelLikeHallState :: HobbitState 'TunnelLikeHall
LonelandsState :: HobbitState 'Lonelands
TrollsClearingState :: KeyState -> HobbitState 'TrollsClearing
RivendellState :: KeyState -> HobbitState 'Rivendell
MistyMountainState :: KeyState -> HobbitState 'MistyMountain
TrollsPathState :: KeyState -> HobbitState 'TrollsPath
TrollsCaveState :: HobbitState 'TrollsCave
type role HobbitState nominal
stateMessage :: HobbitState vertex -> HobbitMessage
stateMessage TunnelLikeHallState =
HobbitMessage
"You are in a tunnel-like hall.\nYou can only go east to the Lonelands"
stateMessage LonelandsState =
HobbitMessage
"You are in the lonelands.\nYou can either go west to a tunnel-like hall\nor go east to the Trolls clearing"
stateMessage (TrollsClearingState keyState) =
if keyState == DayDawned
then
HobbitMessage
"You are in the Trolls clearing.\nYou could go north to the Trolls path,\nyou can go east to Rivendell\nor you could get the key for the TrollsCave"
else
HobbitMessage
"You are in the Trolls clearing.\nYou could go north to the Trolls path,\nyou can go east to Rivendell"
stateMessage (RivendellState _) =
HobbitMessage
"You are in Rivendell.\nYou could either go west to the Trolls clearing\nor go east to the Misty mountains\n"
stateMessage (MistyMountainState _) =
HobbitMessage
"You are in the Misty mountains.\nYou can only go east to Rivendell"
stateMessage (TrollsPathState keyState) =
case keyState of
NoKey ->
HobbitMessage
"You are in the Trolls path.\nYou can go south to the Trolls clearing\nor you can wait a bit"
DayDawned ->
HobbitMessage
"You are in the Trolls path.\nYou can go south to the Trolls clearing\nor you can wait some more"
GotKey ->
HobbitMessage
"You are in the Trolls path.\nYou can go south to the Trolls clearing,\nyou can unlock the door to the Trolls cave\nor you can wait some more"
DoorUnlocked ->
HobbitMessage
"You are in the Trolls path.\nYou can go south to the Trolls clearing\nor you can go north to the Trolls cave"
stateMessage TrollsCaveState =
HobbitMessage
"Welcome to the Trolls cave!\nNow you can go back south to the Trolls path"
hobbitResult
:: (Applicative m, AllowedTransition HobbitTopology initialVertex finalVertex)
=> HobbitState finalVertex
-> ActionResult m HobbitTopology HobbitState initialVertex HobbitMessage
hobbitResult hobbitState = pureResult (stateMessage hobbitState) hobbitState
hobbitMachine :: HobbitState vertex -> BaseMachine HobbitTopology HobbitCommand HobbitMessage
hobbitMachine initialState =
BaseMachineT
{ initialState = InitialState initialState
, action = \case
TunnelLikeHallState -> \case
GoEast -> hobbitResult LonelandsState
_ -> hobbitResult TunnelLikeHallState
LonelandsState -> \case
GoEast -> hobbitResult $ TrollsClearingState NoKey
GoWest -> hobbitResult TunnelLikeHallState
_ -> hobbitResult LonelandsState
TrollsClearingState keyState -> \case
GoEast -> hobbitResult $ RivendellState NoKey
GoNorth -> hobbitResult $ TrollsPathState keyState
GetKey ->
if keyState == DayDawned
then hobbitResult $ TrollsClearingState GotKey
else hobbitResult $ TrollsClearingState keyState
_ -> hobbitResult $ TrollsClearingState keyState
RivendellState keyState -> \case
GoEast -> hobbitResult $ MistyMountainState keyState
GoWest -> hobbitResult $ TrollsClearingState keyState
_ -> hobbitResult $ RivendellState keyState
MistyMountainState keyState -> \case
GoWest -> hobbitResult $ RivendellState keyState
_ -> hobbitResult $ MistyMountainState keyState
TrollsPathState keyState -> \case
GoSouth -> hobbitResult $ TrollsClearingState keyState
GoNorth ->
if keyState == DoorUnlocked
then hobbitResult TrollsCaveState
else hobbitResult $ TrollsPathState keyState
Wait ->
if keyState == NoKey
then hobbitResult $ TrollsPathState DayDawned
else hobbitResult $ TrollsPathState keyState
UnlockDoor ->
if keyState == GotKey
then hobbitResult $ TrollsPathState DoorUnlocked
else hobbitResult $ TrollsPathState keyState
_ -> hobbitResult (TrollsPathState keyState)
TrollsCaveState -> \case
GoSouth -> hobbitResult $ TrollsPathState DoorUnlocked
_ -> hobbitResult TrollsCaveState
}