Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How would I go about representing levels/scenes in a game in Haskell?

The problem

I'm working on a text adventure game as a hobby project to teach myself some new Haskell concepts.

I've now run into the challenge of representing the levels (or 'scenes') in the game and the various ways in which they're connected. To keep things simple for myself, I've visualised the 'map' of the game as a simple 2d space, in which scenes are connected via cardinal directions (NESW):

        Lake
        ⇳
        Forest
        ⇳
Home  ⬄ Field*

*: Starting location

Here, going south from the lake would lead you to the forest, and vice versa; going west from the field would lead you to the home. The challenge here is that I need some way to encode not only which scenes are connected, but how they are connected, where 'how' refers to a cardinal direction.

Two options I've considered so far are representing levels as a graph or using lists of tuples, but I'm not entirely happy with either approach.

Representing scenes as a Graph

My basic approach in representing scenes and their connections as a graph was as follows:

data Scene = InitialField | Forest | House | Lake

-- |`gameScenes` maps scenes to their integer representation, as used in `sceneGraph`.
gameScenes :: Map Int Scene
gameScenes =
    let scenes =
            [ (0, InitialField)
            , (1, Forest)
            , (2, House)
            , (3, Lake)
            ]
    in
        fromList scenes

-- |the `sceneGraph` describes the way different scenes of the game connect to each other.
sceneGraph :: Graph
sceneGraph =
    let
        bounds = (0, 3)
        edges =
            [ (0, 1) -- link initial field to forest
            , (0, 2) -- link initial field to house
            , (1, 3) -- link forest to lake
            ]
    in
        buildG bounds edges

Downside: The main limitation here is that this graph does not encode any information with regards to cardinal directions. This is unfortunate, because aside from that limitation, a graph seems like a perfect data structure to represent scenes (vertices) and their linkages (edges). Is there any way to encode 'metadata' (in this case, cardinal directions) of edges in a graph?

Representing scenes using list of tuples

When I came upon the above limitation, I decided to try a different approach and represent the links between scenes as a list of tuples:

data Scene = InitialField | Forest | House | Lake
data CardinalDirection = North | East | South | West
type LinkedScene = (CardinalDirection, Scene)

-- |`linkedScenes`, applied to a `Scene`, returns a list of the connected scenes and their cardinal directions.
linkedScenes :: Scene -> [LinkedScene]
linkedScenes InitialField = [(North, Forest)]
linkedScenes Forest = [(South, InitialField), (West, Lake)]
-- The other links are left as an exercise for the reader ;)
linkedScenes _ = []

Downside: This approach does allow us to encode the linkages between different scenes simply by applying sceneLinks to the Scene the player is currently in, but it has a big, ugly downside: it requires us to double-endedly encode the spatial linkages between scenes. In other words, if we know that the forest is north of the field, we need to write two functions: one that takes Forest and yields [(South, InitialField)], and an inverse one that takes InitialField and yields [(North, Forest)]. This seems like it's begging for bugs: if I ever want to add a scene, or change the layout of the game world, I need to remember to double-check every single linkage from both ends.

Summing up: requirements

Ideally, I am looking for an approach that:

  • Allows me to encode a bidirectional relationship between scenes using a single data point (no 'double-ended' encoding as in the second approach, since that is asking for bugs);
  • Allows me to easily change the layout of the game/map at a later stage, without a giant headache;
  • Is guaranteed to be spatially consistent at compile time, meaning that it does not allow for invalid spatial configurations, such as a state in which the player starts at the field, goes north to the forest, goes back south and suddenly finds themselves in the lake. I am not certain that this is possible without dependent types, however.

If anyone could think of such an approach, it would surely be someone in the Haskell community :)

Thanks!

like image 845
Niek Avatar asked Oct 14 '25 15:10

Niek


1 Answers

Start with triples describing some of the connections:

data Scene = Field | Forest | House | Lake
data Dir = N | E | S | W
type Edge = (Scene, Dir, Scene)

uniEdges :: [Edge]
uniEdges = [(Field, N, Forest), (Forest, W, Lake)]

Expand to the full map by reversing all the tuples:

class Dual a where dual :: a -> a
instance Dual Dir where
    dual N = S
    dual E = W
    dual S = N
    dual W = E
instance Dual Edge where dual (s, d, s') = (s', dual d, s)
instance Dual a => Dual [a] where dual = map dual

allEdges :: [Edge]
allEdges = uniEdges ++ dual uniEdges

Then insert these into whatever data structure you like; a Map Scene (Dir, Scene) or a Gr or whatever.

One nice feature of this approach: it leaves you with the ability to decide later that not all edges should turn back on themselves without changing all the users of the scene graph, as in this map with a curved path:

House
  |
   \
    `--Lake
like image 93
Daniel Wagner Avatar answered Oct 17 '25 13:10

Daniel Wagner



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!