Функционально-банановая игра путешественника - интригует и сводит с ума

Я хочу использовать реактивный банан, чтобы написать игру для путешественников, для которой люди могут писать ботов. FRP совершенно новый для меня, и мне трудно начать. Когда я начал, я создал более сложный График, но для своих целей я постарался сделать его максимально простым. Я хотел бы получить некоторые рекомендации, в основном о том, с чего начать и как разбить эту большую проблему на более мелкие проблемы, которые необходимо решить. Вот начальный дизайн.

Идея состоит в том, чтобы иметь граф LNodes (со взвешенными ребрами, которые я пока игнорирую для простоты). Эти LNode я описываю как Planet s. Planet у них есть имя, карта игроков на Planet а также Resource s. У игроков есть идентификатор, ресурсы и планета. Вот структуры данных и некоторые связанные с ними функции, после чего следует больше обсуждений.

-- Graph-building records and functions

data PlanetName = Vulcan
                | Mongo
                | Arakis
                | Dantooine
                | Tatooine
                     deriving (Enum,Bounded,Show)

data Planet = Planet {pName :: PlanetName
                     ,player :: IntMap Player
                     ,resources :: Int
                     } deriving Show

data Player = Player {pid :: Int
                    ,resources :: Int
                    } deriving Show



makePlanetNodes :: PlanetName -> [LNode Planet]
makePlanetNodes planet = Prelude.map makePlanetNodes' $
                         zip [planet ..] [0 ..]
  where makePlanetNodes' (planet,i) =
          (i,Planet {pName = planet, players = IM.empty})

makePlanetGraph p = mkGraph p [(0,1,1),(1,2,2),(2,3,4),(3,4,3),(4,0,2)]

-- Networking and command functions

prepareSocket :: PortNumber -> IO Socket
prepareSocket port = do
   sock' <- socket AF_INET Stream defaultProtocol
   let socketAddress = SockAddrInet port 0000
   bindSocket sock' socketAddress
   listen sock' 1
   putStrLn $ "Listening on " ++ (show port)
   return sock'

acceptConnections :: Socket -> IO ()
acceptConnections sock' = do
   forever $ do
       (sock, sockAddr) <- Network.Socket.accept sock'
       handle <- socketToHandle sock ReadWriteMode
       sockHandler sock handle

sockHandler :: Socket -> Handle -> IO ()
sockHandler sock' handle = do
    hSetBuffering handle LineBuffering
    forkIO  $ commandProcessor handle
    return ()

commandProcessor :: Handle -> IO ()
commandProcessor handle = untilM (hIsEOF handle) handleCommand >> hClose handle
  where
    handleCommand = do
        line <- hGetLine handle
        let (cmd:arg) = words line
        case cmd of
            "echo" -> echoCommand handle arg
            "move" -> moveCommand handle arg
            "join" -> joinCommand handle arg
            "take" -> takeCommand handle arg
            "give" -> giveCommand handle arg
            _ -> do hPutStrLn handle "Unknown command"


echoCommand :: Handle -> [String] -> IO ()
echoCommand handle arg = do
    hPutStrLn handle (unwords arg)

moveCommand = undefined

joinCommand = undefined

takeCommand = undefined

giveCommand = undefined

Вот что я знаю до сих пор, мои события будут включать типы Planet а также Player, Поведение будет включать в себя движение, присоединиться, взять и дать. Когда игрок присоединяется, он создаст новый Player событие и обновить карту на Вулкане с этим Player, Move позволит пройти от одного LNode другому, при условии LNode s связаны ребром. Бери уберу resources от текущего Planet Player "на" и добавить эти resources к Player, Давай сделаем обратное.

Как я могу разбить эту большую проблему на более мелкие, чтобы я мог разобраться с этим?

Обновление: Оказывается, Hunt the Wumpus не является хорошим выбором, чтобы помочь в изучении FRP, см. Объяснение того, для чего предназначен FRP. Это в ответе Генриха Апфельма.

Тем не менее, я буду полностью игнорировать сетевой код сейчас. Я мог бы просто написать несколько фиктивных ботов для проверки времени и т. Д.

Обновление: Некоторые люди, кажется, заинтересованы в этой проблеме, поэтому я собираюсь отслеживать связанные вопросы здесь.

построение таймера

обработка ввода

1 ответ

Решение

Это сложный проект. Игра примерно разбита на следующие части:

  • входной слой (переводит входные данные в игровые события)
  • движок (принимает событие и текущее состояние для создания нового игрового состояния)
  • выходной слой (отображает состояние игры и сообщения, полученные при выполнении событий)

Сначала я упростил бы слои ввода и вывода, используя консольный ввод и вывод (т.е. stdin и stdout.) Позже вы можете добавить поддержку сети.

Во-вторых, я бы упростил саму игру. Например, начните с однопользовательской игры. Недавно мне было очень весело переводить игру Land the Lisp "Grand Theft Wumpus" на Haskell.

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

  • Каково состояние игры?
  • Какие игровые события?

Определите структуры данных Haskell для состояния игры и каждого события. Обратите внимание, что в состоянии игры необходимо записывать все, что имеет отношение к игре: карту, местоположения игроков, состояние игроков и даже случайные числа семян.

Состояние игры обычно будет типом продукта:

data GameState = {  _mapNodes :: [Node]
                   ,_mapEdges  :: [ (Node,Node) ]
                   ,_player   :: Node
                   , ...
                 }

События игры должны быть определены как тип суммы:

data GameEvent =
  | MovePlayer Node
  | Look
  | ...

После того как вы определили эти структуры данных, напишите performEvent функция:

performEvent :: GameEvent -> GameState -> IO(GameState)

Причина в том, что результат performEvent является IO(GameState) является то, что вам, вероятно, нужно будет информировать игроков о том, что произошло, и использовать IO На данном этапе игры монада будет самым простым способом сделать это (без каламбура). Есть способы очистить такую ​​функцию, как performEvent, но это совсем другая тема.

Пример:

performEvent :: GameEvent -> GameState -> IO(GameState)
performEvent (Move p n) s =
  do putStrLn "Moving from " ++ (show $ s _player) ++ " to " ++ (show n)
     return s { _player = n }
performEvent Look       s =
  do putStrLn "You are at " ++ (show $ s _player)
     return s

После того, как вы проверили performEvent, вы можете добавить интерфейс для перевода строки текста в GameEvent:

parseInput :: Text -> Maybe GameEvent
parseInput t = case Text.words t of
                 ("look":_)    -> Just Look
                 ("move":n:_)  -> Move <$> (parseNode n)
                 otherwise     -> Nothing

Затем добавьте цикл ввода, напишите функцию для создания исходного GameState, и, прежде чем вы это узнаете, у вас будет настоящая интерактивная игра!

Другие вопросы по тегам