2012-01-18 17 views
5

Estoy haciendo un juego. El juego consiste en un plano infinito. Las unidades deben estar en un cuadrado discreto, por lo que se pueden ubicar con un simple Location { x :: Int, y :: Int }Cómo modelar un mundo 2D en Haskell

Puede haber muchos tipos de Unit s. Algunos pueden ser criaturas, y algunos son solo objetos, como un bloque de piedra, o madera (piense 2d minecraft allí). Muchos estarán vacíos (solo hierba o lo que sea).

¿Cómo modelizarías esto en Haskell? He considerado hacer lo siguiente, pero ¿qué pasa con Object vs Creature? pueden tener diferentes campos? ¿Normalizarlos a todos en la unidad?

data Unit = Unit { x :: Int, y :: Int, type :: String, ... many shared properties... } 

También he considerado tener un tipo de ubicación

data Location = Location { x :: Int, y :: Int, unit :: Unit } 
-- or this 
data Location = Location { x :: Int, y :: Int } 
data Unit = Unit { unitFields... , location :: Location } 

¿Tiene alguna idea? En un lenguaje OO, probablemente habría heredado Location o Unit del otro y he hecho que los tipos específicos de Unidad hereden el uno del otro.

Otra consideración es que enviará muchos de estos objetos a través del cable, por lo que tendré que serializarlos en JSON para usarlos en el cliente, y no quiero escribir toneladas de texto repetitivo.

+1

Usa un 'Data.Map.Map' como tu grilla. –

+0

Entonces, ¿la x an y no es más que una clave en el Mapa, y las Unidades simplemente tienen sus diversos campos? ¿Qué pasa cuando llega el momento de enviarlos al cliente, debería enviar un mapa/hash allí también? –

Respuesta

7

Location es simplemente un tipo bidimensional simple Point.

Recomendaría no vincular Unit s a la ubicación en la que se encuentran; solo use un Map Location Unit para manejar el mapa entre las posiciones en la grilla y qué (si hay algo) está presente allí.

En cuanto a los tipos específicos de Unit van, lo haría, por lo menos, recomendar la factorización campos comunes a cabo en un tipo de datos:

data UnitInfo = UnitInfo { ... } 

data BlockType = Grass | Wood | ... 

data Unit 
    = NPC UnitInfo AgentID 
    | Player UnitInfo PlayerID 
    | Block UnitInfo BlockType 

o similar.

Generalmente, factorizar cosas comunes en su propio tipo de datos, y mantener datos tan simples y "aislados" como sea posible (es decir, mover cosas como "¿en qué ubicación está esta unidad?" En estructuras separadas asociando , de modo que los tipos de datos individuales sean tan "intemporales", reutilizables y abstractos como sea posible).

Tener un String para el "tipo" de un Unit es un fuerte antipatrón en Haskell; en general, indica que está tratando de implementar estructuras dinámicas de tipaje o OOP con tipos de datos, lo cual es un mal ajuste.

Su requisito JSON complica las cosas, pero this FAQ entry muestra un buen ejemplo de cómo lograr idiomáticamente este tipo de generalidad en Haskell sin String escribir o cortes de clase de tipo de lujo, utilizando funciones y tipos de datos como unidades primarias de la abstracción . Por supuesto, el primero te causa problemas aquí; es difícil serializar funciones como JSON. Sin embargo, se puede mantener un mapa de un TAD que representa un "tipo" de la criatura o bloque, etc., y su aplicación real:

-- records containing functions to describe arbitrary behaviour; see FAQ entry 
data BlockOps = BlockOps { ... } 
data CreatureOps = CreatureOps { ... } 
data Block = Block { ... } 
data Creature = Creature { ... } 
data Unit = BlockUnit Block | CreatureUnit Creature 
newtype GameField = GameField (Map Point Unit) 

-- these types are sent over the network, and mapped *back* to the "rich" but 
-- non-transferable structures describing their behaviour; of course, this means 
-- that BlockOps and CreatureOps must contain a BlockType/CreatureType to map 
-- them back to this representation 
data BlockType = Grass | Wood | ... 
data CreatureType = ... 
blockTypes :: Map BlockType BlockOps 
creatureTypes :: Map CreatureType CreatureOps 

Esto le permite tener toda la extensibilidad y NO HACER-repetir-yourself naturaleza de las estructuras OOP típicas, manteniendo la simplicidad funcional y permitiendo la transferencia de red simple de los estados del juego.

Generalmente, debe evitar pensar en términos de herencia y otros conceptos de OOP; en cambio, intente pensar en el comportamiento dinámico en términos de funciones y composición de estructuras más simples. Una función es la herramienta más poderosa en la programación funcional, de ahí el nombre, y puede representar cualquier patrón complejo de comportamiento. También es mejor no permitir que requisitos como el juego en red influyan en su diseño básico; como en el ejemplo anterior, casi siempre es posible superponer estas cosas en arriba de un diseño creado para la expresividad y la simplicidad, en lugar de restricciones como el formato de comunicación.

+0

Aún digiriendo, pero tan útil. ¡Gracias! –

+0

Una cosa que me está lanzando a un bucle es un campo de identificación. Estoy planeando almacenar el estado en redis, y la API de JSON requerirá alguna comunicación en términos de identificadores. ¿Debo agregar un campo de id a mis Unidades o asociarlo de alguna manera? Por ejemplo, un nuevo cliente podría querer decir "¡Soy nuevo! Devuélveme el objeto de jugador". Querrán su propia identificación, pero enviarán algunos campos para especificar sobre su jugador. El servidor lo genera y lo devuelve. Entonces su objeto no tiene uno, el que yo devuelve. –

+0

@SeanClarkHess: Sí, una 'Unidad de InterMap' o algo así como asociar identificadores con unidades es probablemente la mejor opción allí; incluyendo el identificador mismo en la 'Unidad 'es razonable también. Consideraría factorizar los campos especificados por el cliente en su propia estructura, e incluirlo en 'Creature', de modo que los bits" inofensivos "que el cliente puede decidir estén aislados de cosas que no deberían poder tocar (por ejemplo, puntos de vida); di 'CreatureTemplate'. – ehird

Cuestiones relacionadas