Examples:Sandstorm Demo
This game plunges the player into a disorienting desert sandstorm. This demo uses one room that pretends to be many rooms, to create an "endless" maze. It uses some advanced techniques to present randomized sights and sounds of the storm so that each "room" looks different. We want to mention a number of things that the player might see or hear, without actually creating objects for each of them, so we create several scenery assets that do multiple duties, and additionally write several ad hoc Javascript functions to provide randomized travel messages. This demo doesn't keep track of dropped inventory or the number of "rooms" the player has traveled through, but it could easily be extended to track those things.
Demo Game
// SandstormDemo.js
/* global AdventureJS A SandstormDemo G */
var G = (SandstormDemo = new AdventureJS.Game(
"SandstormDemo",
"SandstormDemoContainer"
));
// --------------------------------------------------
// Settings
// --------------------------------------------------
G.settings.set({
display: "inline",
layout: "classic",
title: "Working With Scenery",
author: "Ivan Cockrum",
description: "Today we are Working With Scenery ",
version: "0.0.1",
// We're using settings.must_be_messages to customize
// the response the game gives any time the player
// refers to an asset that isn't yet known.
must_be_messages: {
known: function () {
if (G.room.name === "Sandstorm") {
return `If there is any such thing, {we} won't find it in this sandstorm. `;
} else {
return `{We} {don't} know what that is. `;
}
},
},
debug: {
general: true,
},
log: {
all: true,
},
});
// doStartGame is an optional function that's called on
// game start. We're using it to add a CSS class to the
// game display that will make the compass rose weave
// as if it's windblown.
G.doStartGame = function () {
G.display.displayEl.classList.add("lost");
};
// --------------------------------------------------
// Player
// --------------------------------------------------
G.createAsset({
class: "Player",
name: "Explorer",
place: { in: "Sandstorm" },
});
// --------------------------------------------------
// Rooms
// --------------------------------------------------
G.createAsset({
class: "Room",
name: "Sandstorm",
full_name: "Lost in a Sandstorm",
// shortcut to fill a room with sand
contains: "sand",
// Our room exits are all fake, so there's
// no need to print a list of them.
print_room_exits: false,
descriptions: {
// firstlook is only printed the first time
firstlook: function () {
let msg = `
Within a few short steps, the ravening winds have
buffeted {us} and blinded {us} and deafened {us},
pushed {us} from the relative safety of the camel
string and stripped {us} of any sense of direction.
If {we} don't rejoin the caravan quickly {we} will
surely be lost.
<br/><hr>
Welcome to the Sandstorm Demo. This is a fairly
elaborate example. We're telling the player that
they're lost in a desert and presenting them with
imaginary visions. We want to mention a number of
things that the player <em>might</em> see or
hear without actually creating objects for each
of them, so we're going to create several scenery
assets that do multiple duties. Try traveling
in any direction, and looking at or listening to
the things mentioned in descriptions.
`;
return msg;
},
look: function () {
return A.FX.pickRandom([
`The ferocious winds continue to buffet, blind and deafen {us}, pushing {us} deeper from safety. Is there some clearing to the ${randomDirection()}? `,
`The storm hits {us} in the face like an open blast furnace. {We} squint into the heat but struggle to make out any features. Is that the shadow of a camel to the ${randomDirection()}? `,
`Sheets of blown sand obscure any possible landmarks. What little you can see vibrates with heat haze. Is there a figure shimmering to the ${randomDirection()}? `,
`{We} can barely see through the rimes of sand encrusting {our} eyelids. {We} squint hard: is that an opening to the ${randomDirection()}? `,
`{We} can't make out any features of the landscape through the falling sheets of sand. Is there an opening in the storm to the ${randomDirection()}? `,
`Churning clouds of sand sweep the desert floor, obscuring the landscape and denying {us} any sense of orientation. Is that a gap in the clouds to the ${randomDirection()}? `,
`Darkening clouds of sand pull a blanket of shadow across the desert landscape. Is that a hint of light breaking through to the ${randomDirection()}? `,
]);
},
listen: function () {
return A.FX.pickRandom([
`The blood-curdling shriek of the wind overwhelms {us}. `,
`All {we} can hear is the rough growl of the winds. `,
`The roar of the devouring winds fills {us} with dread. `,
`{Our} bones and teeth vibrate with the thunderous timpani of the sandstorm. `,
`The storm beats like a mallet against the desert floor, loud enough to make the sands jump. `,
`The storm beats an irregular percussion against the desert floor, filling {us} with dread. `,
`The storm's ferocious howl cuts through {us} like broken glass. `,
]);
},
},
// All of our exits are false exits, meaning that they
// will only print a message if player tries to use them.
exits: {
// Up and down are consistent.
up: {
descriptions: {
look: `Heavy, towering clouds loom darkly over {us}, raining down a constant barrage of sand. `,
travel: `Would that {we} could flee the storm on an ebony horse like Prince Achmed. Alas, {we} are firmly grounded. `,
},
},
down: {
descriptions: {
look: `The desert floor appears to be in constant motion, as the wind drags sand across the ground in tiny waves of jumping particles. `,
travel: `{We} try digging a scrape to lay in against the storm, but the winds blow the sands back at us faster than {we} can dig. `,
},
},
// All of the cardinal directions are going to return
// randomized text if the player looks at them or uses them.
east: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("east");
},
travel: function () {
return stagger("east");
},
},
},
west: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("west");
},
travel: function () {
return stagger("west");
},
},
},
north: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("north");
},
travel: function () {
return stagger("north");
},
},
},
northeast: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("northeast");
},
travel: function () {
return stagger("northeast");
},
},
},
northwest: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("northwest");
},
travel: function () {
return stagger("northwest");
},
},
},
south: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("south");
},
travel: function () {
return stagger("south");
},
},
},
southeast: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("southeast");
},
travel: function () {
return stagger("southeast");
},
},
},
southwest: {
destination: "Sandstorm",
descriptions: {
look: function () {
return look("southwest");
},
travel: function () {
return stagger("southwest");
},
},
},
},
// We're using some randomized area_events. Each turn
// has a 50% chance of printing one of these strings.
area_events: {
chance: 0.5,
randomize: true,
array: [
`The harsh winds buffet {us}, nearly pushing {us} from {our} feet. `,
`{We} stagger beneath a fresh blow from the crushing winds. `,
`The bitter winds whip at {us}. `,
`A sudden gust whips sand up into {our} robes. `,
`{Our} eyes burn with grit. `,
`The wind shrieks like a ghūl in {our} ears. `,
`{Our} ears are congested with fine sand. Did {we} really hear a child's cry? `,
`Was that a bray {we} heard, before the wind whipped it away? `,
`Is that a person yelling in the storm? Or is that {our} own voice, screaming? `,
`{We} choke on the thick air, and wrap a fold of {our} robe across {our} mouth. `,
`The deafening wind fills {our} ears. Or is that the roar of {our} own blood? `,
],
},
// We're using some area scenery. These are different than
// scenery assets. They take advantage of built-in scenery
// objects.
area_scenery: {
sun: {
enabled: true,
description: `It is hidden behind dark swirling clouds of sand. `,
},
sky: {
enabled: true,
description: `It appears that the earth and sky have been crushed together, as if pressed beneath the sandy palms of al-ilāh. `,
},
wind: {
enabled: true,
descriptions: {
feel: {
randomize: true,
array: [
`The wind scours the little bit of exposed skin on {our} face. `,
`The wind stings every part of {us} that it can reach. `,
`The wind burns and abraids {our} exposed skin. `,
],
},
look: {
randomize: true,
array: [
`The wind blows sheets of sand across {our} view. `,
`All {we} can see is the sand whipping around {us}. `,
`The windblown sand obscures all visibility. `,
],
},
listen: {
randomize: true,
array: [
`The wind shrieks like a ghūl. `,
`The wind roars in {our} ears. `,
`The wind growls and rumbles like a jungle full of cats. `,
],
},
},
},
sound: {
enabled: true,
description: {
randomize: true,
array: [
`The wind's brutal growl is almost deafening. `,
`The harsh screech of the wind fills {our} ears. `,
`{We} only wish {we} could shut {our} ears to it. `,
`It's a terrifying, relentless howl. `,
],
},
},
},
});
// --------------------------------------------------
// Scenery Objects
// --------------------------------------------------
// The sandstorm is our first scenery asset.
// It returns a randomized description each time
// a player looks at it or listens to it.
G.createAsset({
class: "Scenery",
name: "storm",
synonyms: ["sandstorm", "cloud", "clouds", "shadow", "blanket"],
place: { in: "Sandstorm" },
is: { known: true, listed: false },
// This property sets it so that whatever word(s)
// the player used to refer to this asset, are the
// same words that will be printed back to them.
name_returns_input: true,
descriptions: {
look: function () {
return A.FX.pickRandom([
`The sandstorm swirls around {us}, obliterating the desert's features with its mindless ferocity. `,
`The sandstorm blots out the sky with layer upon layer of swirling dust, stirred as if by the hands of gods. `,
`The storm showers its wrath upon {us}, scouring the world with abrasive sheets of sand. `,
`The storm vibrates the desert floor and carries the sands from the land to the sky before raining them down upon {us} again. `,
`The storm pounds {us} relentlessly with a downpouring of desert sand. `,
`The storm, in its ravening hunger, has swallowed the sun, leaving the world dark. `,
]);
},
listen: function () {
return A.FX.pickRandom([
`The storm howls like a choir of enraged djinn. `,
`The storm growls like a pride of furious lions. `,
`The storm shrieks like a flock of terrified peacocks. `,
`The storm crashes like an avalanche of mighty boulders. `,
`The storm blasts {us} with a deep rumbling bass that vibrates the desert floor. `,
`The storm crashes like cymbals against {our} ears. `,
]);
},
},
});
G.createAsset({
class: "Scenery",
name: "mirage",
place: { in: "Sandstorm" },
synonyms: [
"clearing",
"gap",
"gaps",
"break",
"breaks",
"shadow",
"shadows",
"opening",
"openings",
"silhouette",
"silhouettes",
"tent",
"tents",
"light",
"lights",
"figure",
"figures",
// "shelter",
// "shelters",
],
is: { listed: false },
// We're using this scenery asset in a sneaky way,
// as a catchall for any implied-but-not-real assets
// suggested by the randomized room description.
// This property sets it so that whatever word(s)
// the player used to refer to this asset, are the
// same words that will be printed back to them.
// So if player input "x figure", this mirage will
// be referred to as "figure" in the response.
name_returns_input: true,
description: function () {
// Get the name the player used to refer to this.
let n = this.name;
// Some of this asset's synonyms are plural, but we've
// written all our responses for singular words,
// so we're stripping off the s.
if (n.endsWith("s")) {
n = n.slice(0, n.length - 1);
}
return A.FX.pickRandom([
`Now that {we're} looking, {we're} not sure there even was a ${n}, or which way it may lie. Perhaps to the ${randomDirection()}? `,
`{We} no longer see any ${n}, if it even was one. Was it ${randomDirection()}? `,
`{We've} completely lost sight of anything that might have been a ${n}. ${randomDirection()}? `,
`Was it a ${n}? {We} see nothing but sands now. Was that a ${n} to the ${randomDirection()}? `,
`Now {we} can't see any ${n} through the darkening sands. Did it move ${randomDirection()}? `,
`No, {we've} lost it. Whatever ${n} {we} thought {we} saw has disappeared again into the storm. Maybe to the ${randomDirection()}? `,
`A sudden gust blows a fresh blast of sand into {our} eyes, stealing away any view of the ${n}. Did it move to the ${randomDirection()}? `,
`A dust devil forms suddenly around {us}, spinning {us} in place and stealing away {our} brief glimpse of the ${n}. Could it be to the ${randomDirection()}? `,
`A wave of darkness crashes down on {us}, obscuring any sight of a ${n}. Is there a break in the storm to the ${randomDirection()}? `,
`A vortex of sand swirls around {us}, spinning {us} away from the ${n}. It's lost to {us}. Was it off to the ${randomDirection()}? `,
]);
},
// print_on_verb_failure provides a string to be printed
// for all verb failures.
print_on_verb_failure: function () {
return A.FX.pickRandom([
`{We} take a step into the blowing sands. Stop. Pivot. Peer into the storm. ${this.article_name.endsWith("s") ? "Were" : "Was"} ${this.article_name} that way? No. Whatever ${this.article_name.endsWith("s") ? "they were" : "it was"}, {we've} lost ${this.article_name.endsWith("s") ? "them" : "it"}. `,
`{We} dart into the storm in the direction of ${this.article_name}, until a heavy gust stops {us} short and spins {us} around, leaving {us} as lost as ever. `,
`How to find ${this.article_name}? {We} can barely find {ourself}. `,
`{We} struggle against the currents of the storm, desperately trying to find ${this.article_name}, though it does not reappear. `,
`{We} try, but {we} can't find ${this.article_name} among the swirling clouds of sand. `,
`{We} hunt through the swirling clouds of sand for ${this.article_name}, but fail to sight it again. `,
`{We} lean into the wind and try to find the ${this.name} and only get more disoriented. `,
]);
},
});
// We mention phantom sounds in some of our messages.
// This object exists to catch the player's efforts
// to listen to the sounds that were mentioned.
// The player can't interact with them, but should be
// able at least to refer to them and get a response.
const acousma = G.createAsset({
class: "Scenery",
name: "acousma",
place: { in: "Sandstorm" },
synonyms: [
"child's cry",
"child",
"cry",
"bray",
"person yelling",
"person",
"yelling",
],
is: { listed: false },
// we're using this scenery asset in a sneaky way,
// as a catchall for any implied-but-not-real assets
// suggested by the randomized room description
// so its description has to use the word that was
// entered by the player.
name_returns_input: true,
dov: { listen: true },
descriptions: {
look: `{We} peer into the storm but can't see anything that might have made any such sound. `,
listen: function () {
return A.FX.pickRandom([
`Did {we} hear ${this.indefinite_name}? The overwhelming wind swallows all sounds. `,
`Now {we're} not even certain we heard ${this.indefinite_name}, in the chaotic storm, or where the sound might have come from. `,
`If indeed {we} heard ${this.indefinite_name}, {we} can no longer pick it out of the raging storm. `,
`The howling storm makes it impossible to pinpoint any ${this.name}, or even really say for sure that {we} heard it. `,
]);
},
},
tryFind: function (action) {
if (this.getRoom().name === "Sandstorm") {
const msg = A.FX.pickRandom([
`{We} can't pick out the source of the sound, nor even be certain of what {we} heard, inside the cacophony of screaming winds. `,
]);
this.game.print(msg);
return false; // <= stops the default message from being printed
}
},
tryFollow: function (action) {
return this.tryFind();
},
});
G.createAsset({
class: "Scenery",
name: "camel string",
place: { in: "Sandstorm" },
synonyms: ["camels", "caravan", "camp", "shelter", "shelters"],
is: { listed: false },
name_returns_input: true,
// The player can never reach the camel string.
// We only offer it as a thing that the player
// can attempt to find.
// By setting print_on_verb_failure,
// all failed verb attempts
// will return a random response from this list
// instead of default verb errors. This feels like
// a better more diegetic response than
// "you can't verb the camel string".
print_on_verb_failure: function () {
if (this.getRoom().name === "Sandstorm") {
return A.FX.pickRandom([
`{We} desperately cast about for the camel string but can find no trace of it. Is it to the ${randomDirection()}? `,
`{We} battle the oncoming waves of sand in an effort to relocate the camel string, and only succeed in becoming further disoriented. Is that a a shadow of the caravan to the ${randomDirection()}? `,
`{We} stand still – as still as {we} can against the forceful winds – and try to visualize the last location of the caravan, but to no avail, as the wind has already spun {us} about. Is that the silhouette of a camel to the ${randomDirection()}? `,
`The camel string must be only steps away and yet {we} can see no sign of it in the blowing sands. Can {we} see some sign of a tent to the ${randomDirection()}? `,
`Was it here? Was it there? {We} turn in place, trying to find any sign of it, but can see nothing through the blinding sands. Is there some shadow to the ${randomDirection()}? `,
`{We} panic, and plunge into the winds looking for the camel string, and only succeed in getting more lost. Did {we} glimpse something off to the ${randomDirection()}? `,
`{We} fear {we've} become hopelessly disoriented and lost the caravan entirely. {We} won't last long on {our} own. Was there some motion to the ${randomDirection()}? `,
`{We} fear {we} won't survive outside the relative safety of the camel string. {We} stagger and lurch in the direction it seems {we} last saw the caravan, and find nothing but stinging sands. Was there something to the ${randomDirection()}? `,
`The sandstorm lashes {us} with whip-like ferocity as {we} hunt fruitlessly for the lost camel string. Did {we} sense some object to the ${randomDirection()}? `,
]);
}
},
description: function () {
// We're going to reuse the print_on_verb_failure message
// as our description.
return this.print_on_verb_failure();
},
});
// --------------------------------------------------
// Sand
// --------------------------------------------------
G.createAsset({
class: "Solid",
name: "sand",
mixwith: {},
descriptions: {
look: function () {
if (G.room.name === "Sandstorm") {
let msg = A.FX.pickRandom([
`The pale desert sand is inescapable, whipping {us} raw and piling in heavy drifts. `,
`The sands swirl through the air in great gouts, blotting out all sight. `,
`Sand dances across the desert floor, whipped into constant motion by the erratic winds. `,
`The choking sands fill the atmosphere from ground to sky. `,
`The omnipresent sand sweeps through the desert, blurring the line between earth and sky. `,
`{We} can hardly see anything BUT sand. `,
`The wind-blown sands engulf the entire environment. `,
`Sand fills the air as if the desert floor had been raised up and tossed into the sky. `,
`The fine grained beige desert sand is in constant motion, swept across the ground and whipped into the air by the chaotic winds. `,
]);
// By default, the verb "look" will print a small preamble
// such as "you see some sand here" before our custom
// description, so we're using overrideOutput(),
// which completely overrides any default output.
this.game.overrideOutput(msg);
// since we're overriding output, we don't
// need to return any particular value
return;
} else {
// for every other case, we'll allow the preamble to print
let msg = A.FX.pickRandom([
`The sand here is a fine red powder. `,
`The sand has a strong red cast, indicating fertile conditions. `,
`The sand here is a ruddy red in color. `,
`The red sands here present a warmer tone than the desert sands to which {we} are accustomed. `,
`The warm red tones of the sands here make for a calming environment. `,
]);
return msg;
}
},
},
doFind: function (action) {
if (G.room.name === "Sandstorm") {
G.print(`There's certainly no shortage of it here.`);
return false;
}
},
doDig: function (action) {
if (G.room.name === "Sandstorm") {
G.print(`{We} dig fruitlessly in the sand. `);
}
},
doThrow: function (action) {
if (G.room.name === "Sandstorm") {
G.print(`{We} throw some sand. `);
}
},
dov: {
eat: true,
},
});
// --------------------------------------------------
// Functions
// --------------------------------------------------
// randomDirection is an ad hoc function, a
// shortcut we use to get a random direction.
// It calls A.FX.pickRandom() which is a
// general AdventureJS utility function
const randomDirection = function () {
return A.FX.pickRandom([
"north",
"northeast",
"northwest",
"east",
"west",
"south",
"southeast",
"southwest",
]);
};
// randomLostPhrase is another ad hoc function.
// We're nesting one randomization call inside
// another to create variety.
const randomLostPhrase = function () {
return A.FX.pickRandom([
`It's all but impossible to pinpoint a direction. Is there some clearing to the ${randomDirection()}? `,
`{We} can't get {our} bearing in the storm. Is that the shadow of a camel to the ${randomDirection()}? `,
`{We're} completely disoriented. Is there a figure to the ${randomDirection()}? `,
]);
};
// Here are more ad hoc random string functions
// with nested random directions. This sandstorm
// is only one room but we want to provide variety.
const stagger = function (direction) {
// don't say "a easterly"
const a = ["east"].includes(direction) ? "an" : "a";
// randomly print from our collection of responses
return A.FX.pickRandom([
`{We} stagger forth into the devouring winds, in what {we} hope is ${a} ${direction}erly direction. {We} fail to find anything new. Perhaps it was a bit further ${direction}? `,
`{We} creep forward in what seems to be ${a} ${direction}erly direction but fail to reach camp. Maybe just a bit further ${direction}? `,
`{We} hunch into the disorienting wind in ${a} ${direction}erly direction, to no avail. Can {we} make it a few more steps ${direction}? `,
`{We} struggle against the winds in what may be ${a} ${direction}erly direction, and find no safe harbor. {We} try to muster the strength to keep moving ${direction}. `,
`{We} slide in the sands, trying to move in ${a} ${direction}erly direction, but find nothing. {We} struggle to find the energy to crawl a few more steps ${direction}. `,
`{We} stumble, and fall, and catch {ourself}, and keep going, hopefully in ${a} ${direction}erly direction, and fail to find shelter. Can we crawl a bit further ${direction}? `,
`{We} trudge ${direction}, or what seems to be ${direction}, until the wind pushes us back. {We} look for the strength to fight further ${direction}. `,
`{We} inch forward, scoured by swirling sands, fighting to go ${direction}, until the winds push {us} almost flat to the ground. Can {we} even crawl any further ${direction}? `,
]);
};
const look = function (direction) {
// randomly print from our collection of responses
return A.FX.pickRandom([
`{We} shield {our} eyes with {our} hand and try to look ${direction}, but can make out nothing. `,
`{We} scrape sand our of {our} eyes and try to face ${direction}, but it's impossible to pinpoint any direction. `,
`The buffeting wind has {us} so disoriented that {we} can hardly tell which way is ${direction}. `,
`A rain of sand obscures {our} vision to the ${direction}, or what {we} think might be ${direction}. `,
`The turbulent winds spill gouts of sand upon {us}, obscuring east from west and north from south. `,
`Is there some shadow of shelter to the ${direction}? `,
`Is there perhaps a slight clearing to the ${direction}? `,
`A jagged slice of sand sears our eyes, preventing us from looking ${direction}. `,
`The ${direction} seems hidden behind a veil of pouring sand. `,
`Great dark gouts of sand obscure all visibility to the ${direction}. `,
`The ${direction} is as invisible as the moon and stars. `,
]);
};
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