1
\$\begingroup\$

I am writing a Settlers of Catan server in Haskell for fun. Currently I am passing around state manually. I want to learn more about the State monad and want to incorporate it into my project.

However, my GameState has multiple data constructors that I pattern match on to yield different functionality. Three of such data constructors are for the WaitingLobby (when people can still join over the internet), the SetupPhase (before the game has really started and people have to build their initial settlements), and one for the RunningGame (when you can play the game turn by turn normally). Code:

data GameState = WaitingLobby ... | SetupPhase ... | RunningGame ...

Lots of functions pattern match on the type of data constructor used. For example the function that handles building a settlement after it has been decided that the player is indeed allowed to do so:

doConstructBuilding :: GameState -> Building -> VertexCoordinate -> GameState 
doConstructBuilding gs@(SetupPhase _ _ board _ _ _) building coord          = gs { board = placeBuilding building coord board,
                                                                                   _placedSettlement = True }
doConstructBuilding gs@(RunningGame playerList cur board _) building coord  = gs { board = placeBuilding building coord board, 
                                                                                   playerList = map payIfCur playerList}
                                    where payIfCur p | cur == playerId p = playerPays p $ constructionCost building
                                                     | otherwise         = p

So the different GameState dataconstructors have different implementations: In the SetupPhase the building is placed and it is noted that the player has placed a settlement in the state, while in the RunningGame the building is also placed, but here the player also pays resources for it.

The question: My understanding is that with the state monad you only pull out the state once you're in the monadic context on the right side of the =. So How would I implement something like this?

Note: Maybe there are other solutions for this particular method, but I have a lot of these data constructor pattern match cases.

\$\endgroup\$
2
\$\begingroup\$

In general, you would extract the state using get and then case-match on it, like so:

type Game = State GameState

doConstructBuilding :: Building -> VertexCoordinate -> Game ()
doConstructBuilding building coord = do
  gs <- get
  case gs of
    SetupPhase _ _ board _ _ _ -> do
      put $ gs { board = placeBuilding building coord board
               , _placedSettlement = True }
    RunningGame playerList cur board _ -> do
      let payIfCur p | cur == playerId p = playerPays p $ constructionCost building
                     | otherwise = p
      put $ gs { board = placeBuilding building coord board
               , playerList = map payIfCur playerList }

There are various ways to make this more succinct. For example, both of your cases involve a single State action that modifies the state, so it can be rewritten in terms of modify:

doConstructBuilding' :: Building -> VertexCoordinate -> Game ()
doConstructBuilding' building coord = modify go
  where go gs@(SetupPhase _ _ board _ _ _)
          = gs { board = placeBuilding building coord board
               , _placedSettlement = True }
        go gs@(RunningGame playerList cur board _)
          = gs { board = placeBuilding building coord board
               , playerList = map payIfCur playerList }
          where payIfCur p | cur == playerId p = playerPays p $ constructionCost building
                           | otherwise         = p
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.