【问题标题】:Functional-Banana Traveller Game - Intriguing and Maddening功能性香蕉旅行者游戏 - 有趣而疯狂
【发布时间】:2012-08-30 20:26:11
【问题描述】:

我想使用响应式香蕉来编写人们可以为其编写机器人的旅行者游戏。 FRP 对我来说是全新的,我很难开始。当我开始时,我创建了一个更精细的图表,但为了我的目的,我试图让它尽可能简单。 我想要一些指导,主要是关于从哪里开始,以及如何将这个更大的问题分解成更小的问题来解决。这是最初的设计。

这个想法是有一个 LNodes 的图(为了简单起见,我现在忽略了加权边缘)。我将这些 LNode 描述为 Planets。 Planets 有一个名字,PlanetResources 上的玩家地图。玩家有一个 id、resources 和一个 Planet。这里是 Data Structues 和一些相关的函数,接下来是更多的讨论。

-- 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

这是我目前所知道的,我的事件将涉及类型 PlanetPlayer。行为 将涉及移动、加入、获取和给予。当玩家加入时,它将创建一个新的Player 事件并使用该Player 更新 Vulcan 上的地图。移动将允许从一个遍历 LNode 到另一个,前提是 LNodes 通过边连接。 Take 将从当前的Planet 中删除resources Player 是“on”并将这些resources 添加到 Player。给予会适得其反。

我怎样才能把这个大问题分解成更小的问题,这样我才能解决这些问题?

更新:事实证明 Hunt the Wumpus 不是帮助学习 FRP 的好选择,请参阅 here 的 FRP 解释说明。这是 Heinrich Apfelmus 的回应。

也就是说,我现在将完全忽略网络代码。我可以写一些虚拟机器人来测试时间等。

更新:有些人似乎对这个问题感兴趣,所以我将在这里跟踪相关问题。

building a timer

input handling

【问题讨论】:

    标签: haskell reactive-banana


    【解决方案1】:

    这是一个复杂的项目。一个游戏大致分为以下几部分:

    • 输入层(将输入转换为游戏事件)
    • 引擎(采用事件和当前状态来产生新的游戏状态)
    • 输出层(显示游戏状态和执行事件产生的消息)

    我会首先通过使用控制台输入和输出(即标准输入和标准输出)来简化输入和输出层。稍后您可以添加网络支持。

    其次,我会简化游戏本身。例如,从单人游戏开始。最近我在将 Lisp 游戏“Grand Theft Wumpus”翻译成 Haskell 时获得了很多乐趣。

    第三,我将从游戏引擎开始。这意味着您必须考虑:

    • 游戏状态如何?
    • 游戏活动有哪些?

    为游戏状态和每个事件定义 Haskell 数据结构。请注意,游戏状态需要记录与游戏相关的所有内容:地图、玩家位置、玩家状态甚至随机数种子。

    游戏状态通常是产品类型:

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

    游戏事件应定义为 sum 类型:

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

    定义完这些数据结构后,编写performEvent函数:

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

    performEvent 的结果是IO(GameState) 的原因是你可能需要通知玩家发生了什么,而在这个阶段使用IO monad 将是最简单的方法。游戏(没有双关语。)有一些方法可以净化像 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,在不知不觉中,您将拥有一个真正的互动游戏!

    【讨论】:

    • 啊,猎杀乌普斯!是的,从小处着手听起来不错。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-05
    • 2013-06-18
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    • 2012-09-23
    • 1970-01-01
    相关资源
    最近更新 更多