功能 - 香蕉旅行者游戏 - 迷人和疯狂 [英] Functional-Banana Traveller Game - Intriguing and Maddening

查看:115
本文介绍了功能 - 香蕉旅行者游戏 - 迷人和疯狂的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想用反应式香蕉来写一个人们可以写作的机器人游戏。 FRP对我来说是全新的,而且我很难开始。我开始时创建了一个更精细的Graph,但为了我的目的,我试图尽可能简化它。
我希望得到一些指导,主要是从哪里开始,以及如何将这个更大的问题分解成更小的问题来解决。这是最初的设计。



这个想法是有一个LNodes图(为了简单起见,我现在忽略了加权边缘)。这些LNodes描述为 Planet s。 Planet 有一个名字, Planet 和 Resource 秒。玩家有一个ID,资源和一个Planet.Here的数据结构和一些相关的功能,然后进行更多的讨论。

   - 图表建立记录和函数

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

data Planet = Planet {pName :: PlanetName
,player :: IntMap Player
,resources :: Int
}派生显示

数据Player = Player {pid :: Int
,resources :: Int
}派生Show



makePlanetNodes :: PlanetName - > [LNode Planet]
makePlanetNodes planet = Prelude.map makePlanetNodes'$
zip [planet ..] [0 ..]
其中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)]

- 网络和命令函数

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 $正在听++(show port)
返回sock'

acceptConnections :: Socket - > IO()
acceptConnections sock'= do $ b $永远$ do
(sock,sockAddr)< - Network.Socket.accept sock'
句柄< - socketToHandle sock ReadWriteMode
sockHandler sock handle

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

commandProcessor :: Handle - > IO()
commandProcessor handle = untilM(hIsEOF handle)handleCommand>> h关闭句柄
其中
handleCommand = do
行< - hGetLine句柄
let(cmd:arg)=单词行
case cmd
echo - > echoCommand处理arg
移动 - > moveCommand处理arg
加入 - > joinCommand处理arg
take - > takeCommand处理arg
give - > giveCommand处理arg
_ - >做hPutStrLn句柄未知命令


echoCommand :: Handle - > [字符串] - > IO()
echoCommand句柄arg = do
hPutStrLn句柄(unwords arg)
$ b $ moveCommand = undefined

joinCommand = undefined

takeCommand = undefined

giveCommand = undefined

这就是我所知道的很远,我的Events将涉及类型 Planet Player 。行为
将涉及移动,加入,接受和给予。当玩家加入时,它会创建一个新的 Player 事件,并用 Player 更新Vulcan上的地图。如果 LNode s通过边连接,Move将允许从一个
LNode 到另一个的遍历。 Take将从当前的 Planet > Player 中删除​​资源 on并将这些资源添加到
Player 中。



如何将这个大问题分解为更小的问题,以便我能够解决这个问题?

更新:结果Hunt the Wumpus不是一个很好的选择来帮助学习FRP,请参阅FRP的解释此处。这是Heinrich Apfelmus的回应。



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

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

建立计时器



输入处理

解决方案

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

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


    我首先使用控制台输入和输出(即stdin和stdout)来简化输入和输出图层。稍后您可以添加网络支持。



    其次,我会简化游戏本身。例如,从单人游戏开始。最近,我将Lisp Land的游戏侠盗猎车手翻译成Haskell很有趣。第三,我将从游戏引擎开始。这意味着你必须考虑:




    • 什么是游戏状态?

    • 什么是游戏事件?


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



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

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

        data GameEvent = 
      | MovePlayer节点
      |看看
      | ...

      在定义了这些数据结构之后,写下 performEvent 函数:

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

      结果 performEvent IO(GameState)是你可能需要告诉玩家发生了什么,并使用 IO 在游戏的这个阶段,monad将是最简单的方法(没有双关语意思)。有些方法可以净化一个像 performEvent 这样的函数,但这是一个



        performEvent :: GameEvent  - > GameState  - > IO(GameState)
      performEvent(Move pn)s =
      do putStrLn从++(show $ s _player)++移至++(显示n)
      返回s {_player = n}
      performEvent Look s =
      do putStrLn您位于++(show $ s _player)
      return s

      一旦您测试了 performEvent ,您可以添加一个前端将一行文本转换为 GameEvent

        parseInput :: Text  - >也许GameEvent 
      parseInput t = case
      的Text.words t(look:_) - >只要看看
      (move:n:_) - >移动< $> (parseNode n)
      否则 - > Nothing

      然后添加一个输入循环,编写一个函数来创建最初的GameState,并且在您知道它之前你会有一个真正的互动游戏!


      I want to use reactive-banana to write a traveller game that people can write bots for. FRP is completely new to me, and I'm having trouble making a start. I created a more elaborate Graph, when I began, but for my purposes here, I have tried to make it as simple as possible. I'd like some guidance, mostly on where to begin, and how to break this bigger problem down into smaller problems to solve. Here's the initial design.

      The idea is to have a Graph of LNodes (with weighted edges I am ignoring for now for simplicity's sake). These LNodes I describe as Planets. The Planets have a name, a Map of the Players on the Planet and Resources. Players have an id, resources and a Planet.Here's the Data Structues and some associated functions, followed by more discussion.

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

      Here's what I know so far, my Events will involve types Planet and Player. Behaviors will involve move,join,take,and give. When a player joins, it will create a new Player event and update the Map on Vulcan with that Player. Move will allow a traversal from one LNode to another,provided the LNodes are connected by an edge. Take will remove resources from the current Planet the Player is "on" and add those resources to the Player. Give will do the opposite.

      How can I break this big problem into smaller problems so I can get my head around this stuff?

      Update: Turns out Hunt the Wumpus is not a good choice to help learn FRP, See an explaination of what FRP is for here. It's in a response by Heinrich Apfelmus.

      That said, I will totally ignore networking code for now. I could just write some dummy bots to test timing etc.

      Update: Some people seem to be interested in this problem, so I am going to track related questions here.

      building a timer

      input handling

      解决方案

      This is a complex project. A game roughly decomposes into the following pieces:

      • an input layer (translates input to game events)
      • an engine (takes an event and the current state to produce a new game state)
      • an output layer (displays game state and messages produced by performing events)

      I would first simplify the input and output layers by using console input and output (i.e. stdin and stdout.) Later you can add network support.

      Secondly, I would simplify the game itself. For instance, start with a single-player game. Recently I had a lot of fun translating the Land of Lisp game "Grand Theft Wumpus" to Haskell.

      Thirdly, I would start with the game engine. This means you have to think about:

      • What is the game state?
      • What are the game events?

      Define Haskell data structures for the game state and each event. Note that the game state needs to record everything that is relevant about the game: the map, player locations, state of players and even random number seeds.

      The game state will typically be a product type:

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

      The game events should be defined as sum type:

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

      After you've defined these data structures have been defined, write the performEvent function:

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

      The reason that the result of performEvent is IO(GameState) is that you will probably need to inform players about what happened, and using the IO monad is going to be the simplest way to do this at this stage in the game (no pun intended.) There are ways to purify a function like performEvent, but that's a whole other topic.

      Example:

      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
      

      Once you have tested performEvent, you can add a front end to translate a line of text to a GameEvent:

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

      Then add an input loop, write a function to create the initial GameState, and before you know it you'll have a real interactive game!

      这篇关于功能 - 香蕉旅行者游戏 - 迷人和疯狂的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆