Pre-release
AdventureJS Docs Downloads
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

To be written...

 


// PlumbingDemo.js
// ----------
/*global adventurejs PlumbingDemo*/

var PlumbingDemo = new adventurejs.Game(
  "PlumbingDemo",
  "PlumbingDemoDisplay"
).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
  show_room_names_in_exit_descriptions: true,

  // if this is true, lists of exits will
  // only include room names known to player
  show_room_names_in_exit_descriptions_only_when_room_is_known: false,

  // if this is true, lists of exits will only
  // show room names for exits player has used
  show_room_names_in_exit_descriptions_only_after_exit_has_been_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_keywords: { general: true },

  // set this to set a default response to
  // player input that is not understood
  if_parser_has_no_response_print_this: "I have no response to your input. ",

  // set this to provide a default
  // response to blank input
  if_input_is_empty_print_this: "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 
    Drainable 
    (Sink and 
    Bathtub), 
    SubstanceEmitter 
    (Faucet), 
    GraduatedController 
    (FaucetHandle), 
    Drain, and 
    Plug. 
    Try turning things on and off with the verbs 
    turnOn, 
    turnOff and 
    turn (in the case of handles). 
    Visit the other rooms in this demo to find some demonstrations. `,
    brief: `Try verbs. 
    Turn on, 
    turn off, 
    turn. `,
  },

  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: {
    touch: "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 BasicSink.js 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.completeEvent("plug basic sink");
        return "TADA! ";
      },
    },
    unplug: {
      on_first_success: function () {
        PlumbingDemo.scorecard.completeEvent("unplug basic sink");
        return "It's probably less exciting than you were imagining. ";
      },
    },
    turnOn: {
      on_first_success: function () {
        PlumbingDemo.scorecard.completeEvent("turn on basic sink");
        return "Non-interactible water pours from the non-interactible faucet. ";
      },
    },
    turnOff: {
      on_first_success: function () {
        PlumbingDemo.scorecard.completeEvent("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 IntegratedSink.js 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: () =>
      `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/Tangibles_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 doRemoveThatFromThis.
  // 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": () => {
      PlumbingDemo.scorecard.completeEvent("plug integrated sink");
      // scorecard.completeEvent has internal logic
      // to prevent repetition so it's ok to not
      // check whether it's been called already
    },
  },

  doRemoveThatFromThis: {
    "rubber stopper": () => {
      PlumbingDemo.scorecard.completeEvent("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
    // ${native template literals} and AdventureJS {custom templates}.
    // ${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 custom templates 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_CustomTemplates.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 BasicBathtub.js 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: () => {
      PlumbingDemo.scorecard.completeEvent("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 IntegratedBathtub.js 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/Tangibles_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: () => {
      PlumbingDemo.scorecard.completeEvent("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: "Tangible",
  name: "rubber duck",
  place: { in: "Integrated Bathtub" },
  description: "Rubber ducky, you're the one. ",
  dov: { squeeze: true },
  doSqueezeThis: () => {
    PlumbingDemo.appendTurn(`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.

  score_events: {
    "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": {
      points: 1,
      bonus: true,
      // Customize the score message by setting message
      // to a string or array or function.
      message: "[ ** {We} got a bonus point! ** ]",
    },
    "enter basic bathtub": {
      points: 1,
      bonus: true,
      message: "[ ** {We} got a bonus point! ** ]",
    },
  },
});

Source files