Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to model a 2D world in Haskell

Tags:

haskell

I'm making a game. The game consists of an infinite plane. Units must be on a discrete square, so they can be located with a simple Location { x :: Int, y :: Int }

There might be many kinds of Units. Some might be creatures, and some are just objects, like a block of stone, or wood (think 2d minecraft there). Many will be empty (just grass or whatever).

How would you model this in Haskell? I've considered doing the below, but what about Object vs Creature? they might have different fields? Normalize them all on Unit?

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

I've also considered having a location type

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

Do you have any ideas? In an OO language, I probably would have had Location or Unit inherit from the other, and made the specific types of Unit inherit from each other.

Another consideration is this will be sending lots of these objects over the wire, so I'll need to serialize them to JSON for use on the client, and don't want to write tons of parsing boilerplate.

like image 243
Sean Clark Hess Avatar asked Jan 18 '12 01:01

Sean Clark Hess


1 Answers

Location is just a simple two-dimensional Point type.

I would advise against tying Units to the location they're in; just use a Map Location Unit to handle the map between positions on the grid and what (if anything) is present there.

As far as the specific types of Unit go, I would, at the very least, recommend factoring common fields out into a data type:

data UnitInfo = UnitInfo { ... }

data BlockType = Grass | Wood | ...

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

or similar.

Generally, factor common things out into their own data-type, and keep data as simple and "isolated" as possible (i.e. move things like "what location is this unit at?" into separate structures associating the two, so that individual data-types are as "timeless", reusable and abstract as possible).

Having a String for the "type" of a Unit is a strong antipattern in Haskell; it generally indicates you're trying to implement dynamic typing or OOP structures with data types, which is a bad fit.

Your JSON requirement complicates things, but this FAQ entry shows a good example of how to idiomatically achieve this kind of genericity in Haskell with no String typing or fancy type-class hacks, using functions and data-types as the primary units of abstraction. Of course, the former causes you problems here; it's hard to serialise functions as JSON. However, you could maintain a map from an ADT representing a "type" of creature or block, etc., and its actual implementation:

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

This lets you have all the extensibility and don't-repeat-yourself nature of typical OOP structures, while keeping the functional simplicity and allowing simple network transfer of game states.

Generally, you should avoid thinking in terms of inheritance and other OOP concepts; instead, try to think of dynamic behaviour in terms of functions and composition of simpler structures. A function is the most powerful tool in functional programming, hence the name, and can represent any complex pattern of behaviour. It's also best not to let requirements like network play influence your basic design; like the example above, it's almost always possible to layer these things on top of a design built for expressiveness and simplicity, rather than constraints like communication format.

like image 144
ehird Avatar answered Nov 15 '22 04:11

ehird