Pre-release
AdventureJS Docs Downloads Bundler Devlog
Score: 0 Moves: 0
Example game featuring sinks, faucets, drains, and use of the verbs turn on and turn off. sink, faucet, drain, turn on, turn off PlumbingDemo, BasicSink, IntegratedSink, BasicBathtub, IntegratedBathtub, Scorecard

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! ** ]",
    },
  },
});

Source files