Examples:Plumbing Demo
This game demonstrates how to work with verbs and assets related to plumbing, which includes sinks and bathtubs, faucets and drains.
// PlumbingDemo.js
// ----------
/*global AdventureJS PlumbingDemo*/
var PlumbingDemo = new AdventureJS.Game(
"PlumbingDemo",
"PlumbingDemoContainer"
).set({
// title, version, and author are shown in the title bar
title: "Plumbing Demo",
version: "0.0.1",
author: "Ivan Cockrum",
description: "This is my great game! Thanks for playing!",
});
// Let's set some Game settings
PlumbingDemo.settings.set({
// if this is true, lists of exits will
// show the names of rooms they lead to
print_room_names_in_exits: true,
// if this is true, lists of exits will
// only include room names known to player
print_room_names_in_exits_if_known: false,
// if this is true, lists of exits will only
// show room names for exits player has used
print_room_names_in_exits_if_used: false,
// if this is true, verbose room descriptions will be
// shown on first visit regardless of verbosity settings
print_verbose_room_descriptions_on_first_visit: true,
// if this is true, AdventureJS will print
// debug messages to the game display
debug: { general: true },
responses: {
// set this to set a default response to
// player input that is not understood
no_response: "I have no response to your input. ",
// set this to provide a default
// response to blank input
empty_input: "I didn't see any input. ",
},
// alternately, game can be set to print
// the current room description with
// if_input_is_empty_print_room_description: true
});
PlumbingDemo.createAsset({
class: "Room",
name: "Plumbing Demo Foyer",
// setting definite_article to "the" sets it so the room
// name appears as "the Plumbing Demo Foyer" in lists
definite_article: "the",
descriptions: {
look: `Welcome to the Plumbing Demo, where you can try interacting with assets of several
related classes including
<a href="/doc/AdventureJS.Assets.Drainable.html">Drainable</a>
(<a href="/doc/AdventureJS.Assets.Sink.html">Sink</a> and
<a href="/doc/AdventureJS.Assets.Bathtub.html">Bathtub</a>),
<a href="/doc/AdventureJS.Assets.SubstanceEmitter.html">SubstanceEmitter</a>
(<a href="/doc/AdventureJS.Assets.Faucet.html">Faucet</a>),
<a href="/doc/AdventureJS.Assets.GraduatedController.html">GraduatedController</a>
(<a href="/doc/AdventureJS.Assets.FaucetHandle.html">FaucetHandle</a>),
<a href="/doc/AdventureJS.Assets.Drain.html">Drain</a>, and
<a href="/doc/AdventureJS.Assets.Plug.html">Plug</a>.
Try turning things on and off with the verbs
<a href="/doc/turnOn.html">turnOn</a>,
<a href="/doc/turnOff.html">turnOff</a> and
<a href="/doc/turn.html">turn</a> (in the case of handles).
Visit the other rooms in this demo to find some demonstrations. `,
brief: `Try verbs.
<a href="/doc/turnOn.html">Turn on</a>,
<a href="/doc/turnOff.html">turn off</a>,
<a href="/doc/turn.html">turn</a>. `,
},
exits: {
east: "Basic Sink",
west: "Integrated Sink",
north: "Basic Bathtub",
south: "Integrated Bathtub",
},
}); // Plumbing Demo Foyer
PlumbingDemo.createAsset({
class: "Player",
name: "Luigi",
place: { in: "Plumbing Demo Foyer" },
is: { active: true },
pronouns: "second",
});
// Water isn't a given in AdventureJs
// If you want to use it, it has to be defined.
PlumbingDemo.createAsset({
class: "Liquid",
name: "water",
mixwith: {},
descriptions: {
feel: "It's wet. ",
look: { default: "It's ordinary water. " },
in: { default: "You look in the water. " },
},
});
// BasicSink.js
// ----------
/*global AdventureJS A PlumbingDemo*/
PlumbingDemo.createAsset({
class: "Room",
name: "Basic Sink",
descriptions: {
look: `This room contains a basic pedestal sink. Try plugging and unplugging the sink and turning it on and off. See the example code in <a href="/examples/PlumbingDemo/BasicSink.js">BasicSink.js</a> for particulars. `,
brief: "Nothing but a basic sink. ",
},
exits: {
west: "Plumbing Demo Foyer",
},
}); // Basic Sink
PlumbingDemo.createAsset({
class: "Sink",
name: "pedestal sink",
synonyms: ["faucet", "drain"],
place: { in: "Basic Sink" },
is: {
listed: false,
known: true,
seen: true,
},
description: `A simple porcelain pedestal sink. There doesn't appear to be anything important about it. The sink is { pedestal sink [is] plugged [or] unplugged }. The sink's faucet is { pedestal sink [is] on [or] off }. `,
// Here, we want to apply some custom code when the verbs
// plug and unplug are applied to the sink. We're going
// to hook into verb actions for plug and unplug,
// turnOn and turnOff. These are filed under dov because
// they only apply when used as direct object of verb.
// For comparison, see IntegratedSink.js where we hook into
// verb reactions rather than verb actions.
dov: {
plug: {
on_first_success: function () {
PlumbingDemo.scorecard.awardPoint("plug basic sink");
return "TADA! ";
},
},
unplug: {
on_first_success: function () {
PlumbingDemo.scorecard.awardPoint("unplug basic sink");
return "It's probably less exciting than you were imagining. ";
},
},
turnOn: {
on_first_success: function () {
PlumbingDemo.scorecard.awardPoint("turn on basic sink");
return "Non-interactible water pours from the non-interactible faucet. ";
},
},
turnOff: {
on_first_success: function () {
PlumbingDemo.scorecard.awardPoint("turn off basic sink");
return "The faucet dribbles to a stop. ";
},
},
},
});
// IntegratedSink.js
// ----------
/*global AdventureJS A PlumbingDemo*/
PlumbingDemo.createAsset({
class: "Room",
name: "Integrated Sink",
descriptions: {
look: `This classically tiled bathroom contains a modest console with a tasteful basin sink mounted on it. The sink has a faucet, hot and cold water handles, and a drain; all of which are distinct assets that a player can interact with. Though the "proper" way to turn on the water would be to turn the handles, in order to support a range of player input, the parser will accept "turn handle", "turn on handle", "turn on water", "turn on sink", or "turn on faucet". See the example code in <a href="/examples/PlumbingDemo/IntegratedSink.js">IntegratedSink.js</a> for particulars. `,
brief: "Nothing basic 'bout this integrated sink. ",
},
exits: { east: "Plumbing Demo Foyer" },
}); // Integrated Sink
// We mentioned tiles in the room description,
// so we'll create a tiles Scenery asset to provide
// a response for "examine tiles"
PlumbingDemo.createAsset({
class: "Scenery",
name: "tile",
synonyms: ["tiles"],
is: { known: true },
description: "Classic white tiles.",
place: { in: "Integrated Sink" },
});
PlumbingDemo.createAsset({
class: "Table",
name: "console",
synonyms: ["tasteful"],
place: { in: "Integrated Sink" },
description: "A tasteful console table. ",
is: {
listed: false,
known: true,
seen: true,
},
});
PlumbingDemo.createAsset({
class: "Sink",
name: "basin sink",
place: { in: "Integrated Sink" },
is: { listed: false, known: true, seen: true },
descriptions: {
look: function () {
return `A vessel sink with porcelain handles and a stainless steel faucet, seated on a tasteful console table. The drain appears to be ${
PlumbingDemo.$("sink drain").$is("plugged") ? "plugged" : "unplugged"
}. `;
},
},
aspects: {
in: { list_contents_in_room: false },
attached: { know_contents_with_parent: true },
},
// Here's the important piece. The Drainable class supports
// components, which are pre-configured blocks of functionality
// that define how integrated assets work together.
// We're using that here to automatically connect the parts
// of the sink that you would otherwise have to set manually.
// Each item in this list is the name of an asset of a
// particular class. Only a few classes handle components. See
// https://adventurejs.com/doc/Entities_Components.html
// for more info.
components: [
"sink's hot water handle", // FaucetHandle
"sink's cold water handle", // FaucetHandle
"sink faucet", // SubstanceEmitter
"sink drain", // Drain
"rubber stopper", // Plug
],
});
PlumbingDemo.createAsset({
class: "Drain",
name: "sink drain",
synonyms: "sink drain",
// Here, we want to apply some custom code when the verbs
// plug and unplug are applied to the sink drain.
// However, we need to consider the various ways that
// a user might interact with an integrated sink:
// - "plug drain with stopper"
// - "plug sink with stopper"
// equivalent to plug drain, due to integrated handling
// - "put stopper in drain"
// equivalent to plug drain, due to integrated handling
// See how "put stopper in drain" bypasses the verb plug?
// This is a common pattern.
// So, instead of hooking into a verb action, we're going
// to hook into a verb reaction. This is an event that's called
// after a verb has been applied. In this case, we're going
// to hook into doMoveThatToThis and doMoveThatFromThis.
// These are always called when one asset is moved out of
// a second asset and into a third, so they are verb agnostic.
// For comparison, see BasicSink.js for an example that hooks
// into verb actions.
doMoveThatToThis: {
"rubber stopper": function () {
PlumbingDemo.scorecard.awardPoint("plug integrated sink");
// scorecard.awardPoint has internal logic
// to prevent repetition so it's ok to not
// check whether it's been called already
},
},
doMoveThatFromThis: {
"rubber stopper": function () {
PlumbingDemo.scorecard.awardPoint("unplug integrated sink");
},
},
descriptions: {
// You can write dynamic descriptions in several
// different ways, depending on what works best for you.
//
// The code below uses a combination of Javascript
// ${template literals} and AdventureJS {placeholders}.
// ${one} with a dollar sign is a Javascript template literal.
// The {one} without a dollar sign is an AdventureJS custom
// template. These can be used in combination, but there are
// important differences to be aware of.
//
// AdventureJS placeholders are just strings, and they are
// only evaluated right before they are printed to the display.
//
// By contrast, Javascript template literals are essentially
// variables nested inside strings, and they are evaluated by
// the browser immediately when it encounters them. Meaning,
// if it's in a string that is stored as a property, it will be
// evaluated when the game initializes. If you need a template
// literal not be evaluated until it is called, it needs to be
// wrapped in a function that returns it.
//
// See https://adventurejs.com/doc/Scripting_Placeholders.html
// for more info.
look: `A dark drain. Currently { sink drain [is] plugged [or] unplugged }. `,
// You can use a traditional javascript function
// if you want to add your own custom logic.
// look: function () {
// return `A dark drain. Currently ${PlumbingDemo.$("sink drain").$is("plugged") ? "plugged" : "unplugged"}. `;
// },
// You can do the same with an arrow function
// if you prefer that style.
// look: () => {
// return `A dark drain. Currently ${PlumbingDemo.$("sink drain").$is("plugged") ? "plugged" : "unplugged"}. `;
// },
// An arrow function that only returns a string doesn't
// need braces or a return statement.
// look: () =>
// `A dark drain. Currently { sink drain [is] plugged [or] unplugged }. `,
},
});
PlumbingDemo.createAsset({
class: "Plug",
name: "rubber stopper",
synonyms: "plug",
place: { on: "console" },
description: "A rubber stopper for a sink drain. ",
});
PlumbingDemo.createAsset({
class: "Faucet",
name: "sink faucet",
synonyms: ["faucet"],
adjectives: ["stainless steel"],
description: "The sink faucet. ",
substance_id: "water",
max_volume_of_flow_per_turn: 1000,
});
PlumbingDemo.createAsset({
class: "FaucetHandle",
name: "sink's hot water handle",
synonyms: ["handle", "sink handle"],
adjectives: ["hot water", "sink", "porcelain"],
description: "The sink's hot water handle. ",
set_substance_id: "water",
set_substance_temperature: 70, // celsius
control_positions: 3, // off, partway on, fully on
// Normally you would need to set the place and target_id
// properties for a faucet, but because we're using
// a sink with components, those will be set automatically
// by the components integration method.
// If you were building your own without components,
// you would want to set these properties.
// This goes for the other component assets as well.
});
PlumbingDemo.createAsset({
class: "FaucetHandle",
name: "sink's cold water handle",
synonyms: ["handle", "sink handle"],
adjectives: ["cold water", "sink", "porcelain"],
description: "The sink's cold water handle. ",
set_substance_id: "water",
set_substance_temperature: 15, // celsius
control_positions: 3, // off, partway on, fully on
});
// Since the hot and cold handles are distinct objects,
// this Collection provides a way to give a useful response
// should a player input "examine handles"
PlumbingDemo.createAsset({
class: "Collection",
name: "sink handles",
synonyms: ["porcelain handles"],
place: { attached: "basin sink" },
collection: ["sink's hot water handle", "sink's cold water handle"],
is: { listed: false },
description: "Two porcelain sink handles, hot and cold. ",
});
PlumbingDemo.createAsset({
class: "DrinkingGlass",
name: "drinking glass",
place: { on: "console" },
description: "It's a drinking glass. ",
});
PlumbingDemo.createAsset({
class: "Pitcher",
name: "carafe",
place: { on: "console" },
description: "It's a large carafe. ",
aspects: {
in: {
vessel: {
// if you want substance containers filled at start
// you can set initial values for them
volume: 2500,
substance_id: "water",
},
},
},
});
// BasicBathtub.js
// ----------
/*global AdventureJS A PlumbingDemo*/
PlumbingDemo.createAsset({
class: "Room",
name: "Basic Bathtub",
descriptions: {
look: `This drab bathroom is barely punctuated by a standard bathtub. The bathtub doesn't have working elements, but players can turn it on and off, and get in and out of it. See the example code in <a href="/examples/PlumbingDemo/BasicBathtub.js">BasicBathtub.js</a> for particulars. `,
brief: "Nothing but a basic bathtub. ",
},
exits: { south: "Plumbing Demo Foyer" },
}); // Basic Bathtub
PlumbingDemo.createAsset({
class: "Bathtub",
name: "standard bathtub",
synonyms: ["porcelain", "faucet"],
place: { in: "Basic Bathtub" },
is: { listed: false, known: true, seen: true },
description: `A standard porcelain bathtub. There doesn't appear to be anything important about it. The bathtub's faucet appears to be { standard bathtub [is] on [or] off }. `,
// Here, we want to apply some custom code when the player
// enters the bathtub. We're going to use a verb reaction,
// which is a hook that fires at the end of a verb's lifecycle.
// For most assets, we would use doMoveThatToThis. But, when
// assets of class Character enter another asset, we instead
// fire doNestThatToThis. While most assets move into other
// assets, characters always remain within their current room,
// and nest within other assets.
doNestThatToThis: {
Luigi: function () {
PlumbingDemo.scorecard.awardPoint("enter basic bathtub");
},
},
});
// IntegratedBathtub.js
// ----------
/*global AdventureJS A PlumbingDemo*/
PlumbingDemo.createAsset({
class: "Room",
name: "Integrated Bathtub",
descriptions: {
look: `The centerpiece of this baroque and somewhat garish washroom is a gaudy clawfoot tub with ormolu-coated fixtures. The tub has a faucet, hot and cold water handles, and a drain; all of which are distinct assets that a player can interact with. Though the "proper" way to turn on the water would be to turn the handles, in order to support a range of player input, the parser will accept "turn handle", "turn on handle", "turn on water", "turn on bathtub", or "turn on faucet". See the example code in <a href="/examples/PlumbingDemo/IntegratedBathtub.js">IntegratedBathtub.js</a> for particulars. `,
brief: "Nothing basic 'bout this integrated bathtub. ",
},
synonyms: ["washroom", "garish", "garish washroom"],
exits: { north: "Plumbing Demo Foyer" },
}); // Integrated Sink
PlumbingDemo.createAsset({
class: "Bathtub",
name: "clawfoot bathtub",
adjectives: ["gaudy"],
place: { in: "Integrated Bathtub" },
is: { listed: false, known: true, seen: true },
descriptions: {
look: function () {
var msg = `A clawfoot tub with ormolu-coated handles and faucet. Its drain appears to be ${
PlumbingDemo.$("bathtub drain").$is("plugged") ? "plugged" : "unplugged"
}. `;
return msg;
},
},
aspects: {
in: { list_contents_in_room: false },
attached: { know_contents_with_parent: true },
},
// Here's the important piece. The Drainable class supports
// components, which are pre-configured blocks of functionality
// that define how integrated assets work together.
// We're using that here to automatically connect the parts
// of the bathtub that you would otherwise have to set manually.
// Each item in this list is the name of an asset of a
// particular class. Only a few classes handle components. See
// https://adventurejs.com//doc/Entities_Components.html
// for more info.
components: [
"bathtub's hot water handle", // FaucetHandle
"bathtub's cold water handle", // FaucetHandle
"bathtub faucet", // SubstanceEmitter
"bathtub drain", // Drain
"ormolu bung", // Plug
],
// Here, we want to apply some custom code when the player
// enters the bathtub. We're going to use a verb reaction,
// which is a hook that fires at the end of a verb's lifecycle.
// For most assets, we would use doMoveThatToThis. But, when
// assets of class Character enter another asset, we instead
// fire doNestThatToThis. While most assets move into other
// assets, characters always remain within their current room,
// and nest within other assets.
doNestThatToThis: {
Luigi: function () {
PlumbingDemo.scorecard.awardPoint("enter integrated bathtub");
},
},
});
// we mentioned the clawfeet in the room description,
// so we'll create a Scenery asset to provide
// a response for "examine feet"
PlumbingDemo.createAsset({
class: "Scenery",
name: "feet",
synonyms: ["clawfoot", "clawfeet", "foot"],
is: { known: true },
description: "Four ormolu-coated clawfeet.",
place: { attached: "clawfoot bathtub" },
});
PlumbingDemo.createAsset({
class: "Drain",
name: "bathtub drain",
synonyms: "tub drain",
descriptions: {
look: function () {
var msg = "A dark drain. Currently ";
msg += PlumbingDemo.$("bathtub drain").is.closed
? "plugged"
: "unplugged";
msg += ". ";
return msg;
},
},
});
PlumbingDemo.createAsset({
class: "Plug",
name: "ormolu bung",
indefinite_article: "an",
synonyms: ["bung"],
adjectives: ["ormolu"],
place: { in: "Integrated Bathtub" },
description: "A ormolu-coated bung for a bathtub drain. ",
});
PlumbingDemo.createAsset({
class: "Faucet",
name: "bathtub faucet",
synonyms: ["faucet"],
adjectives: ["ormolu"],
description: "The bathtub faucet. ",
substance_id: "water",
max_volume_of_flow_per_turn: 5000,
});
PlumbingDemo.createAsset({
class: "FaucetHandle",
name: "bathtub's hot water handle",
synonyms: ["handle", "bathtub handle"],
adjectives: ["hot water", "bathtub", "ormolu"],
description: "The bathtub's hot water handle. ",
set_substance_id: "water",
set_substance_temperature: 50,
control_positions: 2, // off or on
});
PlumbingDemo.createAsset({
class: "FaucetHandle",
name: "bathtub's cold water handle",
synonyms: ["handle", "bathtub handle"],
adjectives: ["cold water", "bathtub", "ormolu"],
description: "The bathtub's cold water handle. ",
set_substance_id: "water",
set_substance_temperature: 10, // celsius
control_positions: 2, // off or on
});
PlumbingDemo.createAsset({
class: "Collection",
name: "bathtub handles",
synonyms: ["ormolu handles"],
place: { attached: "clawfoot bathtub" },
collection: "bathtub's hot water handle, bathtub's cold water handle",
is: { listed: false },
description:
"Two ormolu-coated bathtub handles, one for hot and one for cold. ",
});
PlumbingDemo.createAsset({
class: "Entity",
name: "rubber duck",
place: { in: "Integrated Bathtub" },
description: "Rubber ducky, you're the one. ",
dov: { squeeze: true },
doSqueezeThis: function () {
PlumbingDemo.appendOutput(`Quack! `);
},
});
// Scorecard.js
// ----------
/*global AdventureJS PlumbingDemo*/
// Scoring
PlumbingDemo.scorecard.set({
// This is how you set score events for your game.
// You can add as few or as many as you like,
// and set points to whatever number you like.
// The names are up to you, so set them however you like.
// See https://adventurejs.com/doc/Scripting_Scorecard.html
// for more info.
points: {
"plug integrated sink": 1,
"unplug integrated sink": 1,
"plug basic sink": 1,
"unplug basic sink": 1,
"turn on basic sink": 1,
"turn off basic sink": 1,
// You can also assign "bonus" points. These won't be
// counted in the total, allowing player to earn a
// score like 110/100.
"enter integrated bathtub": {
value: 1,
bonus: true,
// Customize the score message by setting message
// to a string or array or function.
award_text: "[ ** {We} got a bonus point! ** ]",
},
"enter basic bathtub": {
value: 1,
bonus: true,
award_text: "[ ** {We} got a bonus point! ** ]",
},
},
});