Я пишу сервер 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 ответ
В общем, вы должны извлечь состояние, используя 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
