antisplice: An engine for text-based dungeons.

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

An engine for text-based dungeons. See the README for a guide on how to use it.

[Skip to Readme]


Change log
Dependencies base (>=4.13 && <4.15), chatty (>=0.7 && <, chatty-utils (>=0.7), haskeline (>=0.7), mtl (>=2.1), template-haskell (>=2.8), text, time (>=1.4), transformers [details]
License AGPL-3.0-only
Author Enum Cohrs
Category Game
Uploaded by implementation at 2021-01-11T17:46:36Z



Maintainer's Corner

For package maintainers and hackage trustees

Readme for antisplice-

[back to package description]


Antisplice is an engine for text-based dungeons. The world is modeled in rooms and characters can move through it using text commands. In theory, both single-user and multi-user dungeons are supported, but to my knowledge, no multi-user dungeons are using it.

For inspiration and examples, it might be a good idea to have a look at Ironforge.

Game design


The world is organized in rooms. Rooms are connected by paths, which can be uni- or bidirectional. In total, they form a graph. Paths that are associated with cardinal directions (or "up" or "down") are called "exits", but not all paths are exits. Other paths can be taken by entering an object, or casting a spell. Exits can be guarded by gates, i.e. they open as soon as a requirement is fulfilled.


A stereo is a set of traits that may be attached to a player, object or room. It may:

Stereos may be attached to:

Skills and recipes

Dungeon construction

Dungeon monads

Input masks

Input masks are heterogenous lists that describe the syntax of commands, skills, recipes, etc, in a type-safe way.

The following reads as "whenever the command line only consists of the verb 'quit', throw QuitError":

Verb "quit" :-: Nil #->> throwError QuitError

On the left of the #->> operator, there is a mask describing that the input line only consists of the verb "quit". On the right, there is a Handler. Together, they form a Consumer.

Input masks can also pass parameters to the handler, for example by matching against available objects.

Verb "enter" :-: AvailableObject :-: Nil #-> \o -> objectTriggerOnEnterOf o

Whenever you pass an argument from the mask to the handler, you use the #-> operator. #->> is like #->, in cases where there is no argument to pass. The "quit" example could have also been written like this:

Verb "quit" :-: Nil #-> \() -> throwError QuitError

In a similar style as input masks, there are also preprocessing masks, that may modify your parsed arguments before passing them to the handler.

Verb "drop" :-: CarriedObject :-: Nil #- objectIdOf :-: Nil &-> dropObject

In the above case, CarriedObject matches against an object from the player's inventory and returns the ObjectState. But because dropObject only expects an id, we use objectIdOf as a postprocessing mask. In this case, it is equivalent to the following:

Verb "drop" :-: CarriedObject :-: Nil #-> dropObject . objectIdOf

In case we already use post-processing masks, we can also add a predicate mask. The following example makes sure you can only acquire objects that are actually acquirable.

Verb "acquire" :-: SeenObject :-: Nil #- objectIdOf :-: Nil &-> acquireObject +? Acquirable :-: Nil

Finally, there are also combined post-processing and predicate masks, built on Either:

Verb "go" :-: CatchFixe :-: Nil
              #- (\case
                    "north" -> Right North
                    "south" -> Right South
                    "east" -> Right East
                    "west" -> Right West
                    "northeast" -> Right NorthEast
                    "northwest" -> Right NorthWest
                    "southeast" -> Right SouthEast
                    "southwest" -> Right SouthWest
                    "up" -> Right Up
                    "down" -> Right Down
                    s -> Left $ Unint 0 ("\""++s++"\" is not a direction.")) :-: Nil &?-> changeRoom

Here's an overview of those weird arrow operators:

input_mask #-> \args -> handler
input_mask #->> handler
input_mask #- postproc_mask &-> \args -> handler
input_mask #- postproc_mask &-> (\args -> handler) +? predicate_mask
input_mask #- combi_mask &?-> \args -> handler

Types of invokables

There are quite many types in this library that represent something invokable. This can be confusing, but in fact most of them are only used internally, and not to be meant directly. There are also typeclasses like IsConsumer and Extensible that automatically convert the types in the right way.

You should know Handler, Prerequisite, Action and Consumer though.