2012-09-06 8 views
11

Quiero usar reactive-banana para escribir un juego de viajero para el que las personas pueden escribir bots. FRP es completamente nuevo para mí, y tengo problemas para comenzar. Creé un gráfico más elaborado cuando comencé, pero para mis propósitos aquí, he tratado de hacerlo lo más simple posible. Me gustaría obtener alguna orientación, principalmente sobre dónde empezar y cómo resolver este gran problema en pequeños problemas para resolver. Aquí está el diseño inicial.Functional-Banana Traveler Game - Intriguing and Maddening

La idea es tener un gráfico de LNodes (con bordes ponderados que ignoro por ahora por simplicidad). Estos LNodes que describo como Planet s. El Planet s tiene un nombre, un mapa de los jugadores en el Planet y Resource s. Los jugadores tienen una identificación, recursos y un Planeta. Aquí están las Estructuras de Datos y algunas funciones asociadas, seguidas de más discusión.

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

Esto es lo que sé hasta ahora, mis actos supondrán tipos Planet y Player. Behaviors implicará mover, unir, tomar y dar. Cuando un jugador se une, creará un nuevo evento Player y actualizará el Mapa en Vulcan con ese Player. Move permitirá un recorrido de LNode a otro, siempre que el LNode s esté conectado por un borde. Tomar eliminará resources del actual Planet el Player está "on" y agrega los resources al Player. Give hará lo opuesto.

¿Cómo puedo resolver este gran problema en problemas más pequeños para que pueda entender todo esto?

Actualización: Resulta que Hunt the Wumpus no es una buena opción para ayudar a aprender FRP, vea una explicación de lo que es FRP para here. Es en una respuesta de Heinrich Apfelmus.

Dicho esto, voy a ignorar por completo el código de red por ahora. Podría escribir algunos bots falsos para probar el tiempo, etc.

Actualización: Algunas personas parecen estar interesadas en este problema, por lo que voy a seguir las preguntas relacionadas aquí.

building a timer

input handling

Respuesta

10

Se trata de un proyecto complejo. Un juego se descompone más o menos en las siguientes piezas:

  • una capa de entrada (traduce de entrada para los eventos del juego)
  • un motor (toma un evento y el estado actual para producir un nuevo estado del juego)
  • una salida capa (estado pantallas juego y mensajes producidos por los acontecimientos escénicas)

quisiera en primer lugar simplificar las capas de entrada y de salida mediante el uso de la consola de entrada y de salida (es decir, entrada y salida estándar.) Más tarde se puede añadir soporte de red.

En segundo lugar, simplificaría el juego en sí. Por ejemplo, comience con un juego para un solo jugador. Recientemente me divertí mucho traduciendo el juego Land of Lisp "Grand Theft Wumpus" a Haskell.

En tercer lugar, comenzaría con el motor del juego. Esto significa que debe tener en cuenta:

  • ¿Cuál es el estado del juego?
  • ¿Cuáles son los eventos del juego?

Defina las estructuras de datos Haskell para el estado del juego y cada evento. Tenga en cuenta que el estado del juego necesita registrar todo lo relacionado con el juego: el mapa, la ubicación de los jugadores, el estado de los jugadores e incluso las semillas de números aleatorios.

El estado del juego será típicamente un tipo de producto:

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

Los eventos del juego deben ser definidos como de tipo suma:

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

Tras definir estas estructuras de datos han sido definidos, escribir la función performEvent:

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

la razón de que el resultado o f performEvent es IO(GameState) es que probablemente necesite informar a los jugadores sobre lo que sucedió, y usar la mónada IO va a ser la forma más sencilla de hacerlo en esta etapa del juego (sin juego de palabras). Hay formas de purificar un funciona como performEvent, pero ese es un tema completamente diferente.

Ejemplo:

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 

vez que haya probado performEvent, se puede añadir un extremo frontal de traducir una línea de texto a un GameEvent:

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

añadiendo además un bucle de entrada, escribir una Funciona para crear el GameState inicial y, antes de que te des cuenta, ¡tendrás un verdadero juego interactivo!

+1

¡Ah hunt the wumpus! Sí, comenzar sonidos más pequeños es un buen movimiento. –