# Antisplice 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](https://hub.darcs.net/enum/ironforge/browse/src/Game/Antisplice/Dungeon). ## Game design ### Rooms 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. ### Stereos 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 `addStereo` * objects, using `addFeature` with the `Stereo` feature (and relation `Near`, `Carried` or `Worn`) * rooms, by adding an invisible object with a `Stereo Near` feature ### 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": ```haskell 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. ```haskell 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: ```haskell 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. ```haskell 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: ```haskell 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. ```haskell Verb "acquire" :-: SeenObject :-: Nil #- objectIdOf :-: Nil &-> acquireObject +? Acquirable :-: Nil ``` Finally, there are also combined post-processing and predicate masks, built on Either: ```haskell 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: ```haskell 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. * `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 `Nothing` for success, and a `Just ReError` for failure. Instances: Functor, Applicative, Monad * `HandlerBox`, `PrerequisiteBox` and `PredicateBox` are boxed versions of `Handler` or `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 `PrerequisiteBoxes` corresponding to boolean operators. * `Invokable`: A `Handler` with 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. * `InvokableP`: A `Predicate` with 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.