В настоящее время я пытаюсь написать небольшую игровую программу (Skat) в качестве хобби-проекта.Скат - это игра, в которой два игрока играют против одного игрока.Поскольку есть разные виды игроков (локальный проигрыватель, сетевой игрок, компьютер и т. Д.), Я хотел бы абстрагировать интерфейс от проигрывателя.
Моя основная идея - использовать класс типов Player
, который определяет все виды вещей, которые игрок должен делать и знать (разыгрывая карту, получая уведомление о том, кто выиграл трюк и т. Д.).Затем вся игра выполняется функцией playSkat :: (Player a, Player b, Player c) => a -> b -> c -> IO ()
, где a
, b
и c
могут быть разными игроками.Затем игрок может реагировать определенным способом реализации.Локальный игрок может получить какое-то сообщение на своем терминале, сетевой игрок может отправить некоторую информацию по сети, а компьютерный игрок может рассчитать новую стратегию.
Поскольку игрок может захотеть сделать какой-нибудь ввод-вывод и определенно захочетиметь какое-то государство для отслеживания личных вещей, оно должно жить в какой-то монаде.Поэтому я подумал об определении класса Player
следующим образом:
class Player p where
playCard :: [Card] -> p -> IO (Card,p)
notifyFoo :: Event -> p -> IO p
...
Этот шаблон, похоже, очень похож на преобразователь состояния, но я не уверен, как с ним справиться.Если бы я написал его как дополнительный монад-трансформер поверх IO, у меня было три разных монады в конце дня.Как я могу написать эту абстракцию хорошим способом?
Чтобы уточнить, что мне нужно, вот как должен выглядеть обычный поток управления:
При игре трюк первый игрок играет карту,затем второй и, наконец, третий.Для этого логике необходимо выполнить функцию playCard
трижды для каждого игрока.После этого логика решает, какой игрок выиграл трюк, и отправляет информацию о том, кто выиграл, всем игрокам.