Public interface
MenuAdventures
— ModuleMenuAdventures
A 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
true
MenuAdventures
MenuAdventures.first_person
MenuAdventures.second_person
MenuAdventures.third_person
MenuAdventures.AbstractDoor
MenuAdventures.AbstractRoom
MenuAdventures.AbstractUniverse
MenuAdventures.Action
MenuAdventures.Answer
MenuAdventures.Carrying
MenuAdventures.Containing
MenuAdventures.Direction
MenuAdventures.Domain
MenuAdventures.Drop
MenuAdventures.East
MenuAdventures.ExitDirections
MenuAdventures.Go
MenuAdventures.GoInto
MenuAdventures.Immediate
MenuAdventures.Inventory
MenuAdventures.Leave
MenuAdventures.ListInventory
MenuAdventures.Location
MenuAdventures.LookAt
MenuAdventures.North
MenuAdventures.Noun
MenuAdventures.OpenOrClose
MenuAdventures.PutInto
MenuAdventures.Quit
MenuAdventures.Reachable
MenuAdventures.Relationship
MenuAdventures.Sentence
MenuAdventures.South
MenuAdventures.Take
MenuAdventures.UnlockOrLock
MenuAdventures.Verb
MenuAdventures.Visible
MenuAdventures.West
MenuAdventures.get_children_relationships
MenuAdventures.get_exit_directions
MenuAdventures.get_parent_relationship
MenuAdventures.string_in_color
MenuAdventures.subject_to_verb
MenuAdventures.turn!
MenuAdventures.@noun
MenuAdventures.@universe
Core interface
MenuAdventures.AbstractDoor
— Typeabstract type AbstractDoor <: Location end
An abstract door
MenuAdventures.AbstractRoom
— Typeabstract type AbstractRoom <: Location end
An 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 end
Contains 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 Location
s connected by Direction
s. Each location is the root of a tree of Noun
s connected by Relationship
s.
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] = relationship
You 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, direction
By 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 end
Direction
s show the relationships between Location
s.
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.opposite
of the direction.
MenuAdventures.Domain
— Typeabstract type Domain end
A 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 end
A location (room or door)
MenuAdventures.Noun
— Typeabstract type Noun end
You 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_possible
MenuAdventures.is_shining
MenuAdventures.is_transparent
MenuAdventures.is_vehicle
The following IOContext
components will be respected when showing nouns:
:capitalize::Bool => false
:known::Bool => true
, set tofalse
to include theindefinite article
if 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_definition
Automatically add MenuAdventures.NOUN_FIELDS
to a Noun
struct definition, including sane defaults.
Adds the following fields and defaults:
name::String
plural::Bool = false
grammatical_person::GrammaticalPerson = third_person
, seethird_person
indefinite_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 end
Relationships show the relationships between Noun
s.
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 Answer
s. 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_definition
Automatically 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 = terminal
introduction::String = ""
relationships_graph = MetaGraph(DiGraph(), Label = Noun, EdgeMeta = Relationship))
, theRelationship
s betweenNoun
s.directions_graph = MetaGraph(DiGraph(), Label = Location, EdgeMeta = Direction))
, theDirection
s betweenLocation
s.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 end
An Action
the player can take.
To create a new action, you will need to add methods for
MenuAdventures.ever_possible
for abstract possibilitiesMenuAdventures.possible_now
for concrete possibilitiesMenuAdventures.argument_domains
to specify the domain of the argumentsMenuAdventures.print_sentence
for printing the sentenceMenuAdventures.mention_status
for 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...) -> Bool
which 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) = true
that is, it is always possible to put something from your inventory into a container, and
ever_possible(::PutInto, ::Reachable, ::AbstractRoom) = true
that is, all rooms act like containers.
MenuAdventures.Take
— TypeMenuAdventures.Quit
— TypeMenuAdventures.UnlockOrLock
— TypeGrammatical persons
MenuAdventures.first_person
— Constantfirst_person
First person (e.g. I).
In games, the narrator is typically the first person.
MenuAdventures.second_person
— Constantsecond_person
Second person (e.g. you).
In games, the player is typically the second person.
MenuAdventures.third_person
— Constantthird_person
Third 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 Relationship
s 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)