更新时间:2022-10-18 23:23:18
这是一个复杂的项目。游戏大致分为以下几部分:
$ b $ ul
我首先使用控制台输入和输出(即stdin和stdout)来简化输入和输出图层。稍后您可以添加网络支持。
其次,我会简化游戏本身。例如,从单人游戏开始。最近,我将Lisp Land的游戏侠盗猎车手翻译成Haskell很有趣。第三,我将从游戏引擎开始。这意味着你必须考虑:
定义游戏状态和每个事件的Haskell数据结构。请注意,游戏状态需要记录与游戏相关的所有内容:地图,玩家位置,玩家状态以及随机数种子。
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 Planet
s. The Planet
s have a name, a Map of the Players on the Planet
and Resource
s. 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 LNode
s 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.
This is a complex project. A game roughly decomposes into the following pieces:
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:
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!