我想使用reactive-banana 编写一个旅行者游戏,人们可以为其编写机器人。 FRP 对我来说是全新的,我很难开始。刚开始时,我创建了一个更精细的图表,但出于我的目的,我试图使其尽可能简单。
我想要一些指导,主要是关于从哪里开始,以及如何将这个更大的问题分解成更小的问题来解决。这是最初的设计。
这个想法是有一个 LNode 的图(为了简单起见,我现在忽略了加权边)。我将这些 LNode 描述为 Planet
s。 Planet
s 有一个名称,Planet
和 Resource
s 上的玩家 map 。玩家有一个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
这是我到目前为止所知道的,我的事件将涉及类型
Planet
和 Player
。行为将涉及移动、加入、获取和给予。当玩家加入时,它将创建一个新的
Player
事件并使用该 Player
更新 Vulcan 上的 map 。移动将允许从一个遍历LNode
到另一个,前提是 LNode
由一条边连接。 Take 将从当前的 resources
中删除 Planet
Player
并将这些 resources
添加到Player
。给予会适得其反。我怎样才能把这个大问题分解成小问题,这样我才能解决这些问题?
更新:结果 Hunt the Wumpus 不是帮助学习 FRP 的好选择,请参阅 FRP 对 here 的解释。这是 Heinrich Apfelmus 的回应。
也就是说,我现在将完全忽略网络代码。我可以写一些虚拟机器人来测试时间等。
更新:有些人似乎对这个问题感兴趣,所以我将在这里跟踪相关问题。
building a timer
input handling
最佳答案
这是一个复杂的项目。一个游戏大致可以分解为以下几个部分:
我将首先使用控制台输入和输出(即 stdin 和 stdout)简化输入和输出层。稍后您可以添加网络支持。
其次,我会简化游戏本身。例如,从单人游戏开始。最近,我在将 Lisp 游戏“侠盗猎车手”翻译成 Haskell 时获得了很多乐趣。
第三,我会从游戏引擎开始。这意味着您必须考虑:
为游戏状态和每个事件定义 Haskell 数据结构。请注意,游戏状态需要记录与游戏相关的所有内容: map 、玩家位置、玩家状态甚至随机数种子。
游戏状态通常是产品类型:
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,不知不觉中,您将拥有一个真正的交互式游戏!
关于haskell - 功能性香蕉旅行者游戏 - 引人入胜且令人抓狂,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/12292934/