Состояние монады и сопоставление с образцом в конструкторах данных

Я пишу сервер Settlers of Catan на Haskell для развлечения. В настоящее время я передаю состояние вручную. Я хочу больше узнать о монаде State и хочу включить ее в свой проект.

Однако в моем GameState есть несколько конструкторов данных, которые я сопоставил с шаблоном, чтобы получить различную функциональность. Три таких конструктора данных предназначены для WaitingLobby (когда люди все еще могут присоединиться к сети через Интернет), SetupPhase (до того, как игра действительно начнется и люди должны построить свои первоначальные поселения), и один для RunningGame (когда вы можете играть в игра по очереди нормально). Код:

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

Многие функции соответствуют шаблону в зависимости от типа используемого конструктора данных. Например, функция, которая обрабатывает строительство поселения после того, как было решено, что игроку действительно разрешено это делать:

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

Таким образом, разные конструкторы данных GameState имеют разные реализации: в SetupPhase помещается здание и отмечается, что игрок поместил поселение в состояние, в то время как в RunningGame здание также размещается, но здесь игрок также платит за него ресурсы. .

Вопрос: я понимаю, что с монадой состояния вы извлекаете состояние только тогда, когда находитесь в монадическом контексте справа от =. Итак, как мне реализовать что-то подобное?

Примечание. Возможно, есть другие решения для этого конкретного метода, но у меня много таких случаев сопоставления с шаблоном конструктора данных.

1 ответ
1

В общем, вы должны извлечь состояние, используя get а затем совпадение по регистру, вот так:

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 }

Есть разные способы сделать его более лаконичным. Например, оба ваших случая связаны с одним State действие, которое изменяет состояние, поэтому его можно переписать в терминах 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

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *