Pre-release
AdventureJS Docs Downloads Bundler Devlog Score: 0 Moves: 0
Tutorial explaining how to work with locks and keys in AdventureJS. tutorial, keys WorkingWithLocksAndKeys styles

Working With...Locks & Keys

Keys can be used to unlock doors, drawers, or any kind of locked item. We use verb subscriptions to determine what a key can unlock. Keys don't actually have any special properties apart from their verb subscriptions – really, anything can be used to unlock another thing with the right verb subscriptions.

 

Demo Game

// WorkingWithLocksAndKeys.js
/* global AdventureJS A WorkingWithLocksAndKeys G */

var G = (WorkingWithLocksAndKeys = new AdventureJS.Game(
  "WorkingWithLocksAndKeys",
  "WorkingWithLocksAndKeysContainer"
));

// --------------------------------------------------
// Settings
// --------------------------------------------------

G.settings.set({
  display: "inline",
  layout: "classic",
  title: "Working With",
  author: "Ivan Cockrum",
  description: "Today we are Working With Keys",
  version: "0.0.1",

  // This setting prints a room's contents before
  // its exits, which is purely a stylistic choice.
  print_contents_before_exits: true,

  // This setting allows the player to use a key
  // they've never used before without being
  // prompted for a key. Sometimes you don't
  // want to give away a puzzle through inference.
  // Other times prompting is just really annoying.
  allow_inference_on_first_use: true,

  // These settings allow room names to be shown
  // in exit listings, as in
  // "you can go out to the playground"
  print_exits_room_names: true,
  print_exits_room_names_if_known: true,
});

// --------------------------------------------------
// Player
// --------------------------------------------------

G.createAsset({
  class: "Player",
  name: "Explorer",
  place: { in: "Chamber of Keys" },
});

// --------------------------------------------------
// Rooms
// --------------------------------------------------

G.createAsset({
  class: "Room",
  name: "Chamber of Keys",
  description: `
    Welcome to the Locks and Keys demo. 
    Things you'll find in the example code include 
    unlocking with a specific key,
    unlocking with any key,
    unlocking without a key,
    and picking locks with a lockpick.
    <br/><br/>
    The chief feature of this room
    is a ludicrously ornate ormolu desk, on which have been placed two
    small chests and 
    {copper bowl[is]empty[then]an empty copper bowl[else]a copper bowl full of keys}. 
    The desk also has three drawers: left, middle, and right.
  `,
  exits: {
    // look under exits & doors
  },
});

G.createAsset({
  class: "Room",
  name: "North Room",
  description: `This is the North Room. `,
  exits: {
    south: "Chamber of Keys",
  },
});

G.createAsset({
  class: "Room",
  name: "South Room",
  description: `This is the South Room. `,
  exits: {
    north: "Chamber of Keys",
  },
});

G.createAsset({
  class: "Room",
  name: "Courtyard",
  // setting definite_article makes it so that it will appear
  // in lists as "the Courtyard" rather than "Courtyard"
  definite_article: "the",
  description: `This is the small courtyard that lies outside the window. `,
  exits: {
    in: "Chamber of Keys",
  },
});

// --------------------------------------------------
// Exits & Doors
// --------------------------------------------------

// Chamber of Keys North - Exit
G.createAsset({
  class: "Exit",
  direction: "north",
  place: { in: "Chamber of Keys" },
  destination: "North Room",
  descriptions: {
    look: "It looks frosty that way. ",
  },
  aperture: "icy door",
});

// Chamber of Keys North - Icy Door
G.createAsset({
  class: "Door",
  name: "icy door",
  adjectives: "icy, north",
  synonyms: ["icy north door", "lock"],
  is: { closed: true, locked: true },
  dov: {
    open: true,
    close: true,
    unlock: {
      with_assets: ["glass key"],
      with_classes: ["SkeletonKey"],
      with_nothing: false,
    },
    lock: {
      with_assets: ["glass key"],
      with_classes: ["SkeletonKey"],
      with_nothing: false,
    },
  },
  descriptions: `
    The icy north door is 
    { icy door [is] locked [or] unlocked } and 
    { icy door [is] open [or] closed }. {[if icy door is locked]It looks like it takes a special key. }
  `,
});

// Chamber of Keys South - Exit
G.createAsset({
  class: "Exit",
  direction: "south",
  place: { in: "Chamber of Keys" },
  destination: "South Room",
  descriptions: {
    look: "It looks warm that way. ",
  },
  aperture: "warm door",
});

// Chamber of Keys South - Warm Door
G.createAsset({
  class: "Door",
  name: "warm door",
  adjectives: "warm, south, melted, lock",
  synonyms: ["warm south door", "melted keyhole"],
  is: { closed: true, locked: true },
  dov: {
    open: true,
    close: true,
    pick: {
      with_classes: ["Lockpick"],
      with_nothing: false,
    },
  },
  description: `
    The warm south door is { warm door [is] open [or] closed }. 
    The door's keyhole appears to be melted out of shape. It
    likely won't take a key, though the lock could probably be picked.
  `,
});

G.createAsset({
  class: "Exit",
  direction: "out",
  place: { in: "Chamber of Keys" },
  destination: "Courtyard",
  descriptions: {
    look: "Beyond the window lies a small courtyard. ",
    travel: "You climb out the window and land in the courtyard. ",
  },
  aperture: "casement window",
});

// Chamber of Keys Out - casement window
G.createAsset({
  class: "Window",
  name: "casement window",

  descriptions: {
    look: `
      The casement window can be locked and unlocked without a key.  
      Beyond the window lies a small courtyard. 
    `,
    out: function () {
      return this.descriptions.through;
    },
    through: "You see the courtyard. ",
  },

  is: { closed: true, locked: true },
  dov: { lock: { with_nothing: true }, unlock: { with_nothing: true } },
});

// The window mentions the courtyard, so we'll create a piece
// of scenery to response to input like "examine courtyard"
G.createAsset({
  class: "Scenery",
  name: "courtyard view",
  description: `There's nothing notable to say about the courtyard beyond the window. `,
  place: "Chamber of Keys",
});

// --------------------------------------------------
// Chests
// --------------------------------------------------

// burned chest
G.createAsset({
  class: "Chest",
  name: "burned chest",
  place: { on: "ormolu desk" },
  description: `
    It's a treasure chest with brass straps, 
    scarred from a fire that blackened its 
    wood, melted its straps, and warped its 
    keyhole to the point where it probably 
    can't even accept a key. It's currently 
    {burned chest [is] open [or] closed}. 
  `,
  adjectives: "treasure, blackened, melted, scarred, brass",
  synonyms: "burned chest lock, lock, straps",
  // Setting listed to false means the asset won't be
  // listed in the room's contents. We're doing that
  // for a number of assets that we've included
  // in the room description.
  is: { locked: true, listed: false },
  dov: {
    pick: { with_classes: "Lockpick" },
  },
});

// Keyed treasure chest
G.createAsset({
  class: "Chest",
  name: "velvet chest",
  place: { on: "ormolu desk" },
  description: `
    The small chest is lined with a luxuriant 
    purple velvet. 
  `,
  adjectives: ["treasure"],
  synonyms: "lock",
  is: { locked: true, listed: false },
  dov: {
    pick: { with_classes: ["SkeletonKey"], with_assets: ["tiny golden key"] },
    unlock: { with_nothing: false },
  },
});

// Chests collection
// We use collections to allow players
// to refer to specific groups of objects
G.createAsset({
  class: "Collection",
  name: "chests",
  place: { attached: "ormolu desk" },
  targets: "velvet chest, burned chest",
  synonyms: ["chests", "two chests"],
  is: { listed: false },
  description: `
    Two small heavy chests dominate the top of the ormolu desk. 
    For the sake of this demo, the left, velvet one is keyed and 
    the right, burned one is pickable.
    The velvet chest is { velvet chest [is] open [or] closed } and 
    the burned chest is { burned chest [is] open [or] closed }. 
  `,
});

// --------------------------------------------------
// Keys bowl
// --------------------------------------------------

G.createAsset({
  class: "Bowl",

  // Normally, authors don't need to provide asset IDs,
  // because asset IDs are generated from asset names.
  // However, we want to use a dynamic name for this
  // copper bowl, to indicate whether it's empty or full
  // when it appears in inventory lists. Dynamic names
  // are allowed, but in that case we MUST also provide an ID.
  name: "{copper bowl[is]empty[then]an empty copper bowl[else]a copper bowl full of keys}",
  id: "copper bowl",
  // Our dynamic name breaks the indefinite article algorithm
  // so we've included indefinite articles in the name and
  // we're going to explicitly set indefinite_article blank.
  indefinite_article: "",

  place: { on: "ormolu desk" },
  description: "It's a copper bowl for dropping keys in. ",

  // The Bowl class is subscribed to verb take,
  // which allows it to be taken. We don't want this
  // particular bowl to be takeable. We could disable
  // its verb subscription, like this...
  // dov: { take: false },

  // ...or we can just set it to fixed. This is a useful
  // property for items that are sometimes takeable
  // and sometimes not.
  is: { fixed: true, listed: false },
});

// --------------------------------------------------
// Keys and lockpicks
// --------------------------------------------------

// Glass key - unlocks icy door
G.createAsset({
  class: "Key",
  name: "glass key",
  place: { in: "copper bowl" },
  adjectives: "",
  description: `
    This glass key looks exceptionally brittle. 
    It's a single use key that will self-destruct
    after one use.
  `,
  // We're going to set this key to self-destruct
  // after one use.
  // There is a class named SingleUseKey which
  // already has this set, and that's the only
  // difference from the regular Key class.
  iov: {
    unlock: {
      then_destroy: true,
      on_destroy: "The glass key shatters into pieces. ",
    },
  },
});

// Brass key
G.createAsset({
  class: "Key",
  name: "melted brass key",
  place: { in: "copper bowl" },
  adjectives: "",
});

// Tiny golden key - velvet chest
G.createAsset({
  class: "Key",
  name: "tiny golden key",
  place: { in: "copper bowl" },
  description:
    "It's a tiny golden key with a thin ribbon of purple velvet tied to the head. ",
  synonyms: ["ribbon"],
  adjectives: ["gold"],
});

// Bobby pin - warm door and middle drawer
G.createAsset({
  class: "Lockpick",
  name: "bobby pin",
  place: { in: "copper bowl" },
  // We're going to set this lockpick to self-destruct
  // after one use.
  // There is a class named SingleUseLockpick which
  // already has this set, and that's the only
  // difference from the regular Lockpick class.
  iov: {
    unlock: {
      then_destroy: true,
      on_destroy: "The bobby pin snaps in half. ",
    },
    pick: {
      then_destroy: true,
      on_destroy: "The bobby pin snaps in half. ",
    },
  },
  description: `
    The hairpin is a squiggly little piece of black metal, 
    bent in the middle. It's a single use lockpick that will 
    self-destruct after one use.
  `,
  adjectives: "",
});

// lockpick set - warm door and middle drawer
G.createAsset({
  class: "Lockpick",
  name: "lockpick set",
  place: { in: "copper bowl" },
  description: `
    It's a beefy looking, industrially machined lockpick set. 
  `,
  adjectives: "beefy, industrial, machined",
});

// --------------------------------------------------
// Desk and drawers
// --------------------------------------------------

G.createAsset({
  class: "Desk",
  name: "ormolu desk",
  place: { in: "Chamber of Keys" },
  description: `
    It's a ludicrously ornate ormolu desk, with three drawers: 
    left, middle, and right. The desktop is dominated by two 
    small chests: a velvet chest, and a burned chest.
  `,
  adjectives: "ludicrously, ornate",
  is: { listed: false },
});

// Left drawer
G.createAsset({
  class: "Drawer",
  name: "left drawer",
  synonyms: "left desk drawer, left drawer lock, lock",
  description: "The left drawer is { left drawer [is] open [or] closed }. ",
  adjectives: "desk",
  place: { attached: "ormolu desk" },
  is: { closed: true, locked: true, listed: false },
  dov: {
    unlock: { with_classes: ["SkeletonKey"] },
  },
});

// Middle drawer
G.createAsset({
  class: "Drawer",
  name: "middle drawer",
  synonyms: "middle desk drawer, lock",
  description: `
    The middle drawer is { middle drawer [is] open [or] closed } 
    and { middle drawer [is] locked [or] unlocked }{
      [middle drawer is locked], 
      but looks like it can be picked with a lockpick }.
  `,
  adjectives: "desk",
  place: { attached: "ormolu desk" },
  is: { closed: true, locked: true, listed: false },
  dov: {
    pick: { with_classes: ["Lockpick"] },
    // setting unlock.with_nothing = false
    // stops "unlock drawer" from working
    // without disabling the unlock verb subscription
    unlock: { with_nothing: false },
  },
});

// Right drawer
G.createAsset({
  class: "Drawer",
  name: "right drawer",
  synonyms: "right desk drawer, lock",
  description: `
    The right drawer is { right drawer [is] locked [or] unlocked } and { right drawer [is] open [or] closed }. 
  `,
  adjectives: "desk",
  place: { attached: "ormolu desk" },
  is: { closed: true, locked: true, listed: false },
  dov: {
    unlock: { with_classes: ["SkeletonKey"] },
  },
});

// Drawers collection
// We use collections to allow players
// to refer to specific groups of objects
G.createAsset({
  class: "Collection",
  name: "desk drawers",
  place: { attached: "ormolu desk" },
  targets: "left drawer, middle drawer, right drawer",
  synonyms: ["drawers", "three drawers"],
  is: { listed: false },
  description: `
    The left drawer is { left drawer [is] open [or] closed }, 
    the middle drawer is { middle drawer [is] open [or] closed }, 
    and the right drawer is { right drawer [is] open [or] closed }. 
  `,
});

Demo Source files

Right-click and choose "Save as..." to download source files.

Notes:

  • Demo games are set to a small size to fit doc pages. They can easily be enlarged via display settings.
  • Demo games all use the same theme. Colors and fonts can be changed via custom CSS.
  • Demo games don't include the core AdventureJS code. To run downloaded games locally, you'll need to also download copies of the core JS and CSS and set up this local folder structure.
AdventureJS
└── js
|   └── adventure.js
└── css
|   └── adventurejs.css
└── games
    ├── demogame01
    ├── demogame02
    └── demogame03

 

Example Code

To specify that one thing unlocks another thing, we just need to create a relationship between the two assets via a verb subscription. Verb relationships are two-way, but only need to be specified for one of the objects in the relationship. In this example, we'll set the verb subscription on the chest, but we could alternately set one on the key.

MyGame.createAsset({
  class: "Chest",
  name: "treasure chest",
  description: `
    It's a heavy wooden chest with brass straps, 
    currently {treasure chest [is] open [or] closed}. 
  `,
  dov: {
    // Lock and unlock are separate verb subscriptions.
    // It's a little extra work, but it gives more control.
    unlock: with_assets:[ "brass key" ],
    lock: with_assets:[ "brass key" ],
  },
});

MyGame.createAsset({
  class: "Key",
  name: "brass key",
  description: `It's a key made of brass. `,
  // We've already established a relationship using
  // the chest's dov, so this isn't necessary, but 
  // here is how we would set the relationship on
  // the key instead, if we wanted to. Note that the
  // key uses iov instead of dov, because the key is
  // always the indirect object in "unlock chest with key"
  // iov: {
  //   unlock: with_assets:[ "brass key" ],
  //   lock: with_assets:[ "brass key" ],
  // },
});

Single Use Keys

SingleUseKey is a class of key that self-destructs on use, though in fact, any key can be set to self-destruct after a single use. Here's all it takes to make a single use key.

MyGame.createAsset({
  class: "SingleUseKey",
  name: "rusty key",
  description: `
    This key is so flaked with rust that {we} 
    fear it may crumble before {we} can use it. 
  `,
});

You can make any key a single use key by setting its unlock verb subscription.

MyGame.createAsset({
  class: "Key",
  name: "rusty key",
  description: `
    This key is so flaked with rust that {we} 
    fear it may crumble before {we} can use it. 
  `,
  iov: { // adjust its indirect object subscription
    unlock: {
      then_destroy: true,
      on_destroy: "The rusty key crumbles to bits {we} use it. ",
    },
  },
});

Unlock With Skeleton Key

Locks can be made unlockable with a skeleton key by setting their verb subscription.

MyGame.createAsset({
  class: "Door",
  name: "cabinet door",
  description: `
    This cabinet door has a fairly standard lock.
  `,
  // we set the direct object verb subscription for lock and unlock
  // to with_classes: "Key" and any key will be accepted
  dov: { 
    lock: { with_classes: "SkeletonKey" }, 
    unlock: { with_classes: "SkeletonKey" } 
  },
});

This is a useful feature of verb subscriptions that can be applied to other circumstances as well. For instance, imagine a special type of paper that can only be written on with a special kind of ink; or a hot item that can only be handled with a special kind of tong; or an object that can only be cut with a special kind of blade.

Unlock With Any Key

Using the same method as our skeleton key example, locks can be made unlockable with any key by setting their verb subscription.

MyGame.createAsset({
  class: "Door",
  name: "cabinet door",
  description: `
    This cabinet door has a janky old lock that looks like it could be opened with any key.
  `,
  // we set the direct object verb subscription for lock and unlock
  // to with_classes: "Key" and any key will be accepted
  dov: { 
    lock: { with_classes: "Key" }, 
    unlock: { with_classes: "Key" } 
  },
});

Unlock With No Key

Locks can be made unlockable without using a key by setting their verb subscription.

MyGame.createAsset({
  class: "Door",
  name: "cabinet door",
  description: `
    This cabinet door is locked with a simple thumb latch that can easily be opened.
  `,
  // we set the direct object verb subscription for lock and unlock
  // to with_nothing: true
  dov: { 
    lock: { with_nothing: true }, 
    unlock: { with_nothing: true } 
  },
});

Picking Locks

Locks can be made pickable by setting their verb subscription.

MyGame.createAsset({
  class: "Door",
  name: "cabinet door",
  description: `
    This cabinet door is locked with a simple lock.
  `,
  // we set the direct object verb subscription for lock and unlock
  // to with_nothing: true
  dov: { 
    pick: { with_assets: ["bobby pin"] }, 
  },
});

The verb unlock will automatically redirect to the verb pick if context indicates that it should, so a player who uses the verb "unlock" instead of "pick" won't be frustrated with a message like "you can't unlock the cabinet door".

Further Reference