A dungeon engine
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.
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:
arbitrary modify player stats (examples: an equipment piece could add a constant number of points to your agility, or there might be a sanctuary room where everyone's strength is 0)
add extra skills (examples: only some players may oink, in some rooms you can purchase something, and you can only pray if you wear)
add extra recipes and construction methods (examples: not everyone knows how to bake pancakes)
Stereos may be attached to:
- players, using
- objects, using
Stereofeature (and relation
- rooms, by adding an invisible object with a
Skills and recipes
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
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
#->> 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 meant to be used directly. There are also typeclasses like IsConsumer and Extensible that automatically convert the types in the right way.
You should know
Handler: alias for
ChattyDungeonM (), just some monadic Haskell function that may have side effects. Instances: Functor, Applicative, Monad
Prerequisite: alias for
ChattyDungeonM Bool, just some monadic Haskell function that represents a condition and may not have side effects. Instances: Functor, Applicative, Monad
Predicate: alias for
ChattyDungeonM (Maybe ReError), also represents a condition, but instead of
True, it returns
Nothingfor success, and a
Just ReErrorfor failure. Instances: Functor, Applicative, Monad
PredicateBoxare boxed versions of
Predicate, to circumvent type system restrictions. Instances: Semigroup, Monoid, IsAction
Action: A combination of a handler and a predicate. The handler can only be run if the predicate succeeds. Instances: Semigroup, Monoid, IsAction
IsAction: A typeclass providing combinators for Actions, PredicateBoxes and
PrerequisiteBoxescorresponding to boolean operators.
Handlerwith a string list as parameters. The string list contains the tokens from command line. It does not make sense to create Invokables manually, consider using masks.
Predicatewith a string list as parameters. Checks whether the string list is in a correct syntax and applicable to an associated Invokable etc. This process is normally hidden behind the masks.
Condition: A boxed InvokableP. Also mostly hidden behind the masks.
Consumer: A combination of an Invokable and a Condition. This type is very frequent, but never created manually; once again: use masks or toConsumer.
ToConsumer: Typeclass for anything that can be converted to a
Consumer, i.e. Skill, Recipe, HandlerBox, Condition, PredicateBox, Action
Skill: A consumer with a name. Skills can be attached to stereos, and directly called from the user command line.
Recipe: A consumer with a name and a recipe method. Methods can be something like cooking, baking, carpentering, building, drawing, etc, just ways of creating new objects. For instance, Ironforge has two recipe methods, cooking and building.