Public interface
MenuAdventures — ModuleMenuAdventuresA module for creating text adventures based on menus.
julia> using MenuAdventures
julia> using MenuAdventures.Testing
julia> import MenuAdventures: ever_possible, is_transparent, is_vehicle
julia> @universe not_a_struct
ERROR: LoadError: ArgumentError: Cannot parse user struct definition
[...]
julia> @universe struct Universe <: AbstractUniverse
end;
julia> @noun struct Room <: AbstractRoom
already_lit::Bool = true
end;
julia> @noun struct Person <: Noun
end;
julia> @noun struct Key <: Noun
end;
julia> ever_possible(::Take, ::Reachable, ::Key) = true;
julia> ever_possible(::UnlockOrLock, ::Inventory, ::Key) = true;
julia> @noun mutable struct LockableDoor <: AbstractDoor
key::Noun
closed::Bool = true
locked::Bool = true
end;
julia> ever_possible(::OpenOrClose, ::Reachable, ::LockableDoor) = true;
julia> ever_possible(::UnlockOrLock, ::Reachable, ::LockableDoor) = true;
julia> @noun mutable struct Chest <: Noun
key::Noun
closed::Bool = true
locked::Bool = true
end;
julia> ever_possible(::OpenOrClose, ::Reachable, ::Chest) = true;
julia> ever_possible(::UnlockOrLock, ::Reachable, ::Chest) = true;
julia> ever_possible(::PutInto, ::Reachable, ::Chest) = true;
julia> @noun struct Car <: Noun
end;
julia> ever_possible(::GoInto, ::Immediate, ::Car) = true;
julia> is_transparent(::Car) = true;
julia> is_vehicle(::Car) = true;
julia> cd(joinpath(pkgdir(MenuAdventures), "test")) do
check_choices() do interface
you = Person(
"Brandon",
description = (universe, self) -> "What a dork!",
grammatical_person = second_person,
indefinite_article = "",
)
entrance = Room(
"the entrance",
description = (universe, self) -> "The entrance to the castle",
indefinite_article = ""
)
small_key = Key("small key")
large_key = Key("large key")
chest = Chest("chest", small_key)
universe = Universe(
you,
introduction = "Welcome!",
interface = interface
)
universe[entrance, Room("the castle", indefinite_article = "")] = LockableDoor("door", large_key), West()
universe[entrance, you] = Containing()
universe[entrance, small_key] = Containing()
universe[entrance, chest] = Containing()
universe[chest, large_key] = Containing()
universe[entrance, Car("car")] = Containing()
universe
end
end
trueMenuAdventuresMenuAdventures.first_personMenuAdventures.second_personMenuAdventures.third_personMenuAdventures.AbstractDoorMenuAdventures.AbstractRoomMenuAdventures.AbstractUniverseMenuAdventures.ActionMenuAdventures.AnswerMenuAdventures.CarryingMenuAdventures.ContainingMenuAdventures.DirectionMenuAdventures.DomainMenuAdventures.DropMenuAdventures.EastMenuAdventures.ExitDirectionsMenuAdventures.GoMenuAdventures.GoIntoMenuAdventures.ImmediateMenuAdventures.InventoryMenuAdventures.LeaveMenuAdventures.ListInventoryMenuAdventures.LocationMenuAdventures.LookAtMenuAdventures.NorthMenuAdventures.NounMenuAdventures.OpenOrCloseMenuAdventures.PutIntoMenuAdventures.QuitMenuAdventures.ReachableMenuAdventures.RelationshipMenuAdventures.SentenceMenuAdventures.SouthMenuAdventures.TakeMenuAdventures.UnlockOrLockMenuAdventures.VerbMenuAdventures.VisibleMenuAdventures.WestMenuAdventures.get_children_relationshipsMenuAdventures.get_exit_directionsMenuAdventures.get_parent_relationshipMenuAdventures.string_in_colorMenuAdventures.subject_to_verbMenuAdventures.turn!MenuAdventures.@nounMenuAdventures.@universe
Core interface
MenuAdventures.AbstractDoor — Typeabstract type AbstractDoor <: Location endAn abstract door
MenuAdventures.AbstractRoom — Typeabstract type AbstractRoom <: Location endAn abstract room.
In addition to the required fields for @noun, you must also include an already_lit::Bool field for an AbstractRoom.
MenuAdventures.AbstractUniverse — Typeabstract type AbstractUniverse endContains all the information about the game universe.
You will need to create your own AbstractUniverse subtype. See @universe for an easy way to do this.
The universe is organized as an interlinking web of Locations connected by Directions. Each location is the root of a tree of Nouns connected by Relationships.
You can add a new thing to the universe, or change the location of something, by specifying its relation to another thing:
universe[parent_thing, thing] = relationshipYou can add a connection between locations too, optionally interspersed by a door:
universe[origin, destination, one_way = false] = direction
universe[origin, destination, one_way = false] = door, directionBy default, this will create a way back in the MenuAdventures.opposite direction. To suppress this, set one_way = true
MenuAdventures.Answer — TypeAnswer(text::String, object::Any)An answer has two fields: text, which will be how the option is displayed in a menu, and object.
object might be a noun, direction, trigger, or even a question.
MenuAdventures.Direction — Typeabstract type Direction endDirections show the relationships between Locations.
For example, a place can be North of another place. To create a new Direction, you must add a method for
Base.show- the
MenuAdventures.oppositeof the direction.
MenuAdventures.Domain — Typeabstract type Domain endA domain refers to a search space for a specific argument to an Action.
Domains serve both as a way of distinguishing different arguments to an action, and also, categorizing the environment around the player. For example, you are only able to look at things in the Visible domain. To create a new domain, you must add a method for:
MenuAdventures.Location — Typeabstract type Location <: Noun endA location (room or door)
MenuAdventures.Noun — Typeabstract type Noun endYou must make your own custom Noun subtypes for almost everything in your game.
See @noun for information about required fields. Nouns are additionally characterized by the following traits and methods:
MenuAdventures.ever_possibleMenuAdventures.is_shiningMenuAdventures.is_transparentMenuAdventures.is_vehicle
The following IOContext components will be respected when showing nouns:
:capitalize::Bool => false:known::Bool => true, set tofalseto include theindefinite articleif it exists.:is_subject => true, whether the noun is the subject of a clause. If this is set tofalse, you must also include:subject::Noun, the subject of the clause.
MenuAdventures.@noun — Macro@noun user_definitionAutomatically add MenuAdventures.NOUN_FIELDS to a Noun struct definition, including sane defaults.
Adds the following fields and defaults:
name::Stringplural::Bool = falsegrammatical_person::GrammaticalPerson = third_person, seethird_personindefinite_article::String = "a"description = (universe, self) -> ""
Set indefinite_article to "" for proper nouns. description should be a function which takes two arguments, the universe and the thing itself, and returns a description.
MenuAdventures.Relationship — Typeabstract type Relationship endRelationships show the relationships between Nouns.
For example, something can be Containing something else.
MenuAdventures.Sentence — TypeSentence(action::Action; argument_answers = Answer[])A sentence has two fields: action, the Action to be taken, and argument_answers, the arguments to the action.
Arguments will be returned as Answers. The subject is implicitly universe.player.
MenuAdventures.turn! — Functionturn!(universe; introduce = true)Start a turn in the AbstractUniverse, and keep going until an Action returns true.
You can overload turn! for an AbstractUniverse. Use Core.invoke to avoid replicating the turn! machinery.
MenuAdventures.@universe — Macro@universe user_definitionAutomatically add the required fields to an AbstractUniverse struct definition, including sane defaults.
Adds the following fields and defaults:
player::Noun. The player will typically be insecond_person.interface::TTYTerminal = terminalintroduction::String = ""relationships_graph = MetaGraph(DiGraph(), Label = Noun, EdgeMeta = Relationship)), theRelationships betweenNouns.directions_graph = MetaGraph(DiGraph(), Label = Location, EdgeMeta = Direction)), theDirections betweenLocations.choices_log::Vector{Int} = Int[]saves all choices the user makes
See AbstractUniverse for more information.
Directions
MenuAdventures.North — TypeNorth()A Direction.
MenuAdventures.South — TypeSouth()A Direction.
MenuAdventures.East — TypeEast()A Direction.
MenuAdventures.West — TypeWest()A Direction.
Relationships
MenuAdventures.Carrying — TypeMenuAdventures.Containing — TypeDomains
MenuAdventures.ExitDirections — TypeMenuAdventures.Immediate — TypeMenuAdventures.Inventory — TypeMenuAdventures.Reachable — TypeReachable()Anything the player can reach.
A Domain. Players can't reach through closed containers by default.
MenuAdventures.Visible — TypeVisible()Anything the player can see.
A Domain. By default, players can't see into closed, opaque containers.
Actions
MenuAdventures.Action — Typeabstract type Action endAn Action the player can take.
To create a new action, you will need to add methods for
MenuAdventures.ever_possiblefor abstract possibilitiesMenuAdventures.possible_nowfor concrete possibilitiesMenuAdventures.argument_domainsto specify the domain of the argumentsMenuAdventures.print_sentencefor printing the sentenceMenuAdventures.mention_statusfor mentioning the status of a thing.
Note that the order arguments are printed in need not match the order they are listed. However, the order of arguments for MenuAdventures.argument_domains must match the order of arguments for MenuAdventures.print_sentence.
Most importantly, define:
function (::MyNewAction)(universe, arguments...) -> Boolwhich will conduct the action based on user choices. Return true to end the game, or false to continue onto the next turn. You can overload Action calls for a Noun subtype. Use Core.invoke to avoid replicating the Action machinery.
MenuAdventures.Drop — TypeDrop()Drop something in your Inventory.
MenuAdventures.Go — TypeMenuAdventures.GoInto — TypeMenuAdventures.Leave — TypeMenuAdventures.ListInventory — TypeMenuAdventures.LookAt — TypeMenuAdventures.OpenOrClose — TypeMenuAdventures.PutInto — TypePutInto()Put something from your Inventory into something Reachable.
An Action. By default,
ever_possible(::PutInto, ::Inventory, anything) = truethat is, it is always possible to put something from your inventory into a container, and
ever_possible(::PutInto, ::Reachable, ::AbstractRoom) = truethat is, all rooms act like containers.
MenuAdventures.Take — TypeMenuAdventures.Quit — TypeMenuAdventures.UnlockOrLock — TypeGrammatical persons
MenuAdventures.first_person — Constantfirst_personFirst person (e.g. I).
In games, the narrator is typically the first person.
MenuAdventures.second_person — Constantsecond_personSecond person (e.g. you).
In games, the player is typically the second person.
MenuAdventures.third_person — Constantthird_personThird person (e.g. he, she, it).
In games, everything is typically third person except for the player and narrator.
Miscellaneous
MenuAdventures.string_in_color — Functionstring_in_color(color::Symbol, arguments...)Use ASCII escape codes to add a color to the arguments collected as a string.
MenuAdventures.subject_to_verb — Functionsubject_to_verb(subject, verb)Find the Verb form to agree with a subject.
MenuAdventures.Verb — TypeVerb(base; third_person_singular_present = string(base, "s"))Create an English verb.
Use subject_to_verb to get the form of a verb to agree with a subject. Unexported verbs include MenuAdventures.DO and MenuAdventures.BE.
MenuAdventures.get_children_relationships — Functionget_children_relationships(universe, parent_thing)Get the children of parent_thing in the universe, and the Relationships of parent_thing to them.
MenuAdventures.get_parent_relationship — Functionget_parent_relationship(universe, thing)Get the parent of a thing in the universe, and the parent's Relationship to it.
MenuAdventures.get_exit_directions — Functionget_exit_directions(universe, location)Get the exits from a location, and the direction of those exits (if exits exist)