12

リアクティブバナナを使用して、ボットを作成できるトラベラーゲームを作成したいと思います。FRPは私にとってまったく新しいものであり、私はスタートを切るのに苦労しています。始めたときはもっと手の込んだグラフを作成しましたが、ここでの目的のために、できるだけシンプルにするように努めました。主にどこから始めればよいか、そしてこの大きな問題を小さな問題に分解して解決する方法について、いくつかのガイダンスが必要です。これが初期設計です。

アイデアは、LNodeのグラフを作成することです(簡単にするために、今のところ無視している重み付きエッジを使用します)。私がsとして説明するこれらのLNode Planet。には名前があり、とPlanetのプレイヤーのマップがあります。プレイヤーはID、リソース、惑星を持っています。ここにデータ構造といくつかの関連する機能があり、その後にさらに議論が続きます。PlanetResource

-- 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ます。移動は、がエッジで接続されているLNode場合に、1つから別のトラバーサルを許可します 。Takeは、現在の「オン」からLNode削除し、それらをに 追加します。Giveは反対のことをします。resourcesPlanetPlayerresourcesPlayer

この大きな問題を小さな問題に分割して、この問題に頭を悩ませることができるようにするにはどうすればよいですか?

更新:ハント・ザ・ワンパスはFRPの学習に役立つ良い選択ではないことが判明しました。ここで、FRPの目的の説明を参照してください。これは、ハインリッヒ・アフェルムスによる回答です。

そうは言っても、今のところネットワークコードは完全に無視します。タイミングなどをテストするために、ダミーのボットをいくつか書くことができます。

更新:一部の人々はこの問題に興味を持っているようですので、ここで関連する質問を追跡します。

タイマーを作る

入力処理

4

1 に答える 1

11

これは複雑なプロジェクトです。ゲームは大まかに次の部分に分解されます。

  • 入力レイヤー(入力をゲームイベントに変換します)
  • エンジン(新しいゲーム状態を生成するためにイベントと現在の状態を取得します)
  • 出力レイヤー(イベントの実行によって生成されたゲームの状態とメッセージを表示します)

最初に、コンソールの入力と出力(つまり、stdinとstdout)を使用して、入力層と出力層を単純化します。後で、ネットワークサポートを追加できます。

第二に、私はゲーム自体を単純化します。たとえば、シングルプレイヤーゲームから始めます。最近、LandofLispのゲーム「GrandTheftWumpus」を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を作成する関数を記述します。そうすれば、それを知る前に、実際のインタラクティブなゲームを手に入れることができます。

于 2012-09-06T18:12:29.283 に答える