From 6ec06c690bce6719e893e7bf6c5befe83c5ec948 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Mon, 21 Feb 2022 14:41:15 -0600 Subject: [PATCH 01/46] replace engine save system with command replay system from ur-dead --- game-disks/ur-dead.js | 127 --------------------------------------- index.js | 137 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 113 insertions(+), 151 deletions(-) diff --git a/game-disks/ur-dead.js b/game-disks/ur-dead.js index dfad8f8..ecaefcf 100644 --- a/game-disks/ur-dead.js +++ b/game-disks/ur-dead.js @@ -837,134 +837,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, }, ], methods: { - utils: { - // saves text from memory to disk - saveFile: (content, filename) => { - const a = document.createElement('a'); - const file = new Blob([content], {type: 'text/plain'}); - - a.href = URL.createObjectURL(file); - a.download = filename; - a.click(); - - URL.revokeObjectURL(a.href); - }, - // creates input element to open file prompt (allows user to load exported game from disk) - openFile: () => { - const input = document.createElement('input'); - input.setAttribute('type', 'file'); - input.click(); - - return input; - }, - // asserts the command is not save, load, import or export, nor blank (could use a better name...) - isNotMeta: (cmd) => !cmd.toLowerCase().startsWith('save') - && !cmd.toLowerCase().startsWith('load') - && !cmd.toLowerCase().startsWith('export') - && !cmd.toLowerCase().startsWith('import') - && cmd !== '', - // applies string representing an array of input strings (used for loading saved games) - applyInputs(string) { - let ins = []; - - // parse, filtering out the save/load commands & empty strings - try { - ins = JSON.parse(string).filter(disk.methods.utils.isNotMeta); - } catch(err) { - println(`An error occurred. See error console for more details.`); - console.error(`An error occurred while attempting to parse text-engine inputs. - Inputs: ${string} - Error: ${err}`); - return; - } - - while (ins.length) { - applyInput(ins.shift()); - } - }, - }, commands: { - // override help to include import/export - help() { - const instructions = `The following commands are available: - LOOK: 'look at key' - TAKE: 'take book' - GO: 'go north' - USE: 'use door' - TALK: 'talk to mary' - ITEMS: list items in the room - INV: list inventory items - SAVE/LOAD: save current game, or load a saved game (in memory) - IMPORT/EXPORT: save current game, or load a saved game (on disk) - HELP: this help menu - `; - println(instructions); - }, - // overridden save command stores player input history - // (optionally accepts a name for the save) - save: (name = 'save') => { - localStorage.setItem(name, JSON.stringify(inputs)); - const line = name.length ? `Game saved as "${name}".` : `Game saved.`; - println(line); - }, - // overridden load command reapplies inputs from saved game - // (optionally accepts a name for the save) - load: (name = 'save') => { - if (inputs.filter(disk.methods.utils.isNotMeta).length > 2) { - println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); - return; - } - - let save = localStorage.getItem(name); - - if (!save) { - println(`Save file not found.`); - return; - } - - disk.methods.utils.applyInputs(save); - - const line = name.length ? `Game "${name}" was loaded.` : `Game loaded.`; - println(line); - }, - // export current game to disk (optionally accepts a filename) - export(name) { - const filename = `${name.length ? name : 'urdead'}.txt`; - disk.methods.utils.saveFile(JSON.stringify(inputs), filename); - println(`Game exported to "${filename}".`); - }, - // import a previously exported game from disk - import() { - if (inputs.filter(disk.methods.utils.isNotMeta).length > 2) { - println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **IMPORT** command again.`); - return; - } - - const input = disk.methods.utils.openFile(); - input.onchange = () => { - const fr = new FileReader(); - const file = input.files[0]; - - // register file loaded callback - fr.onload = () => { - // load the game - disk.methods.utils.applyInputs(fr.result); - println(`Game "${file.name}" was loaded.`); - input.remove(); - }; - - // register error handling - fr.onerror = (event) => { - println(`An error occured loading ${file.name}. See console for more information.`); - console.error(`Reader error: ${fr.error} - Reader error event: ${event}`); - input.remove(); - }; - - // attempt to load the text from the selected file - fr.readAsText(file); - }; - }, play: () => println(`You're already playing a game.`), // set player's name name: (arg) => { diff --git a/index.js b/index.js index 3eb3250..c133964 100644 --- a/index.js +++ b/index.js @@ -71,36 +71,121 @@ let setup = () => { }); }; -// convert the disk to JSON and store it +// store player input history // (optionally accepts a name for the save) -let save = (name) => { - const save = JSON.stringify(disk, (key, value) => typeof value === 'function' ? value.toString() : value); - localStorage.setItem(name, save); +let save = (name = 'save') => { + localStorage.setItem(name, JSON.stringify(inputs)); const line = name.length ? `Game saved as "${name}".` : `Game saved.`; println(line); }; -// restore the disk from storage +// reapply inputs from saved game // (optionally accepts a name for the save) -let load = (name) => { - const save = localStorage.getItem(name); +let load = (name = 'save') => { + if (inputs.filter(isNotMeta).length > 2) { + println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); + return; + } + + let save = localStorage.getItem(name); if (!save) { println(`Save file not found.`); return; } - disk = JSON.parse(save, (key, value) => { - try { - return eval(value); - } catch (error) { - return value; - } - }); + applyInputs(save); const line = name.length ? `Game "${name}" was loaded.` : `Game loaded.`; println(line); - enterRoom(disk.roomId); +}; + +// export current game to disk (optionally accepts a filename) +let exportSave = (name) => { + const filename = `${name.length ? name : 'text-engine-save'}.txt`; + saveFile(JSON.stringify(inputs), filename); + println(`Game exported to "${filename}".`); +}; + +// import a previously exported game from disk +let importSave = () => { + if (inputs.filter(isNotMeta).length > 2) { + println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **IMPORT** command again.`); + return; + } + + const input = openFile(); + input.onchange = () => { + const fr = new FileReader(); + const file = input.files[0]; + + // register file loaded callback + fr.onload = () => { + // load the game + applyInputs(fr.result); + println(`Game "${file.name}" was loaded.`); + input.remove(); + }; + + // register error handling + fr.onerror = (event) => { + println(`An error occured loading ${file.name}. See console for more information.`); + console.error(`Reader error: ${fr.error} + Reader error event: ${event}`); + input.remove(); + }; + + // attempt to load the text from the selected file + fr.readAsText(file); + }; +}; + +// saves text from memory to disk +let saveFile = (content, filename) => { + const a = document.createElement('a'); + const file = new Blob([content], {type: 'text/plain'}); + + a.href = URL.createObjectURL(file); + a.download = filename; + a.click(); + + URL.revokeObjectURL(a.href); +}; + +// creates input element to open file prompt (allows user to load exported game from disk) +let openFile = () => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.click(); + + return input; +}; + +// asserts the command is not save, load, import or export, nor blank (could use a better name...) +let isNotMeta = (cmd) => !cmd.toLowerCase().startsWith('save') + && !cmd.toLowerCase().startsWith('load') + && !cmd.toLowerCase().startsWith('export') + && !cmd.toLowerCase().startsWith('import') + && cmd !== ''; + +// applies string representing an array of input strings (used for loading saved games) +let applyInputs = (string) => { + let ins = []; + + // parse, filtering out the save/load commands & empty strings + try { + ins = JSON.parse(string).filter(isNotMeta); + } catch(err) { + println(`An error occurred. See error console for more details.`); + console.error(`An error occurred while attempting to parse text-engine inputs. + Inputs: ${string} + Error: ${err}`); + return; + } + + while (ins.length) { + applyInput(ins.shift()); + } }; // list player inventory @@ -515,15 +600,15 @@ let chars = () => { // display help menu let help = () => { const instructions = `The following commands are available: - LOOK: 'look at key' - TAKE: 'take book' - GO: 'go north' - USE: 'use door' - TALK: 'talk to mary' - ITEMS: list items in the room - INV: list inventory items - SAVE: save the current game - LOAD: load the last saved game + LOOK: 'look at key' + TAKE: 'take book' + GO: 'go north' + USE: 'use door' + TALK: 'talk to mary' + ITEMS: list items in the room + INV: list inventory items + SAVE/LOAD: save current game, or load a saved game (in memory) + IMPORT/EXPORT: save current game, or load a saved game (on disk) HELP: this help menu `; println(instructions); @@ -574,6 +659,8 @@ let commands = [ save, load, restore: load, + export: exportSave, + import: importSave, }, // one argument (e.g. "go north", "take book") { @@ -588,6 +675,8 @@ let commands = [ restore: x => load(x), x: x => lookAt([null, x]), // IF standard shortcut for look at t: x => talkToOrAboutX('to', x), // IF standard shortcut for talk + export: exportSave, + import: importSave, // (ignores the argument) }, // two+ arguments (e.g. "look at key", "talk to mary") { From f7ad483e9e8e3a0b86a59f3d4450e99efd445995 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Mon, 21 Feb 2022 14:48:01 -0600 Subject: [PATCH 02/46] update Unlimited Adventure to use newer onUse method --- game-disks/unlimited-adventure.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/game-disks/unlimited-adventure.js b/game-disks/unlimited-adventure.js index 9101419..04f7b3d 100644 --- a/game-disks/unlimited-adventure.js +++ b/game-disks/unlimited-adventure.js @@ -79,7 +79,7 @@ WWWWW/\\| / \\|'/\\|/"\\ console.log('Entered', disk.roomId); // Logs "Entered endOfTheWorld" }, items: [ - { name: 'key', desc: 'It looks like a key.', isTakeable: true, use: ({disk, println, getRoom}) => { + { name: 'key', desc: 'It looks like a key.', isTakeable: true, onUse: ({disk, println, getRoom}) => { // This method gets run when the user types "use key". const room = getRoom(disk.roomId); const door = room.items.find(item => item.name === 'door'); @@ -91,7 +91,7 @@ WWWWW/\\| / \\|'/\\|/"\\ println('There\'s nothing to use the key on.'); } }}, - { name: 'book', desc: 'It appears to contain some sort of encantation, or perhaps... code.', isTakeable: true, use: ({disk, println, getRoom}) => { + { name: 'book', desc: 'It appears to contain some sort of encantation, or perhaps... code.', isTakeable: true, onUse: ({disk, println, getRoom}) => { const room = getRoom(disk.roomId); const door = room.items.find(item => item.name === 'door'); @@ -101,7 +101,7 @@ WWWWW/\\| / \\|'/\\|/"\\ } println('A door has appeared from nothing! It seems to go nowhere...'); - room.items.push({ name: 'door', desc: 'It seems to go nowhere...', isOpen: false, use: ({disk, println, enterRoom}) => { + room.items.push({ name: 'door', desc: 'It seems to go nowhere...', isOpen: false, onUse: ({disk, println, enterRoom}) => { const door = room.items.find(item => item.name === 'door'); if (door.isOpen) { enterRoom('gameReallyOver'); From 48edf9ca9aa4b2521f9efa824336555762a852b7 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Mon, 21 Feb 2022 14:51:49 -0600 Subject: [PATCH 03/46] exclude meta commands when saving/exporting game state --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index c133964..0ccdba4 100644 --- a/index.js +++ b/index.js @@ -74,7 +74,7 @@ let setup = () => { // store player input history // (optionally accepts a name for the save) let save = (name = 'save') => { - localStorage.setItem(name, JSON.stringify(inputs)); + localStorage.setItem(name, JSON.stringify(inputs.filter(isNotMeta))); const line = name.length ? `Game saved as "${name}".` : `Game saved.`; println(line); }; @@ -103,7 +103,7 @@ let load = (name = 'save') => { // export current game to disk (optionally accepts a filename) let exportSave = (name) => { const filename = `${name.length ? name : 'text-engine-save'}.txt`; - saveFile(JSON.stringify(inputs), filename); + saveFile(JSON.stringify(inputs.filter(isNotMeta)), filename); println(`Game exported to "${filename}".`); }; From c53ba949af51666acbb96e27e8ebd04073ab5d50 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 13:39:07 -0500 Subject: [PATCH 04/46] disks are now disk factories (functions that return disks) --- game-disks/demo-disk.js | 844 ++++++++++++++++++++-------------------- index.js | 48 +-- 2 files changed, 448 insertions(+), 444 deletions(-) diff --git a/game-disks/demo-disk.js b/game-disks/demo-disk.js index 6852a6d..c484929 100644 --- a/game-disks/demo-disk.js +++ b/game-disks/demo-disk.js @@ -1,489 +1,493 @@ -const demoDisk = { - roomId: 'foyer', // the ID of the room the player starts in - rooms: [ - { - id: 'foyer', // unique ID for this room - name: 'The Foyer', // room name (shown when player enters the room) - // room description (shown when player first enters the room) - desc: `Welcome to the **TEXT-ENGINE** demo disk! This disk is a text adventure game designed to introduce you to the features available to you in **text-engine**. Using this engine, you can make a text game of your own. - - Type **LOOK** to have a look around.`, - // optional callback when player issues the LOOK command - // here, we use it to change the foyer's description - onLook: () => { - const room = getRoom('foyer'); - room.desc = `You are currently standing in the foyer. There's a huge **MONSTERA** plant to your right, and a massive **WINDOW** to your left bathing the room in natural light. Both the **PLANT** and the **WINDOW** stretch to the ceiling, which must be at least 25 feet high. +const demoDisk = () => { + const disk = { + roomId: 'foyer', // the ID of the room the player starts in + rooms: [ + { + id: 'foyer', // unique ID for this room + name: 'The Foyer', // room name (shown when player enters the room) + // room description (shown when player first enters the room) + desc: `Welcome to the **TEXT-ENGINE** demo disk! This disk is a text adventure game designed to introduce you to the features available to you in **text-engine**. Using this engine, you can make a text game of your own. + + Type **LOOK** to have a look around.`, + // optional callback when player issues the LOOK command + // here, we use it to change the foyer's description + onLook: () => { + const room = getRoom('foyer'); + room.desc = `You are currently standing in the foyer. There's a huge **MONSTERA** plant to your right, and a massive **WINDOW** to your left bathing the room in natural light. Both the **PLANT** and the **WINDOW** stretch to the ceiling, which must be at least 25 feet high. + + ***Rooms** form the foundation of the engine's design. At any given time, your player will be standing in one of the rooms you built for them. These can be literal rooms like the foyer you find yourself in now, or metaphorical rooms like **The End of Time** or **Purgatory**. + + Each room you create should have a description. (That's what you're reading now!) + + Rooms can have **exits** that take you to other rooms. For instance, to the **NORTH** is the **RECEPTION DESK**. + + Rooms can also contain **items**. Sometimes the player can **TAKE** or **USE** items. + + Type **ITEMS** to see a list of items in the foyer. Or type **HELP** to see what else you can do!`; + }, + // optional list of items in the room + items: [ + { + name: 'tall window', // the item's name + desc: `All you can see are puffy white clouds over a blue sky.`, // description shown when player looks at the item + }, + { + name: ['monstera', 'plant', 'swiss cheese'], // player can refer to this item by any of these names + desc: `Sometimes called a Swiss Cheese plant, no office is complete without one. It has lovely, large leaves. This is the biggest you\'ve ever seen. + + There's **SOMETHING SHINY** in the pot.`, + block: `It's far too large for you to carry.`, // optional reason player cannot pick up this item + // when player looks at the plant, they discover a shiny object which turns out to be a key + onLook: () => { + if (getItemInRoom('shiny', 'foyer') || getItemInInventory('shiny')) { + // the key is already in the pot or the player's inventory + return; + } - ***Rooms** form the foundation of the engine's design. At any given time, your player will be standing in one of the rooms you built for them. These can be literal rooms like the foyer you find yourself in now, or metaphorical rooms like **The End of Time** or **Purgatory**. + const foyer = getRoom('foyer'); + + // put the silver key in the pot + foyer.items.push({ + name: ['shiny thing', 'something shiny', 'pot'], + onUse: () => { + const room = getRoom(disk.roomId); + if (room.id === 'foyer') { + println(`There's nothing to unlock in the foyer.`); + } else if (room.id === 'reception') { + println(`You unlock the door to the **EAST**!`); + // remove the block + const exit = getExit('east', room.exits); + delete exit.block; + // this item can only be used once + const key = getItemInInventory('shiny'); + key.onUse = () => println(`The lab has already been unlocked.`); + } else { + println(`There's nothing to unlock here.`); + } + }, + desc: `It's a silver **KEY**!`, + onLook: () => { + const key = getItemInInventory('shiny') || getItemInRoom('shiny', 'foyer'); + + // now that we know it's a key, place that name first so the engine calls it by that name + key.name.unshift('silver key'); + + // let's also update the description + key.desc = `It has a blue cap with the word "LAB" printed on it.`; + + // remove this method (we don't need it anymore) + delete key.onLook; + }, + isTakeable: true, + onTake: () => { + println(`You took it.`); + // update the monstera's description, removing everything starting at the line break + const plant = getItemInRoom('plant', 'foyer'); + plant.desc = plant.desc.slice(0, plant.desc.indexOf('\n')); + }, + }); + }, + }, + { + name: 'dime', + desc: `Wow, ten cents.`, + isTakeable: true, // allow the player to TAKE this item + onTake: () => println(`You bend down and pick up the tiny, shiny coin. + + *Now it's in your **inventory**, and you can use it at any time, in any room. (Don't spend it all in one place!) + + Type **INV** to see a list of items in your inventory.*`), + // using the dime randomly prints HEADS or TAILS + onUse: () => { + const side = Math.random() > 0.5 ? 'HEADS' : 'TAILS'; + println(`You flip the dime. It lands on ${side}.`); + }, + } + ], + // places the player can go from this room + exits: [ + // GO NORTH command leads to the Reception Desk + {dir: 'north', id: 'reception'}, + ], + }, + { + id: 'reception', + name: 'Reception Desk', + desc: `**BENJI** is here. I'm sure he'd be happy to tell you more about the features available in **text-engine**. - Each room you create should have a description. (That's what you're reading now!) + *You can speak with characters using the **TALK** command.* - Rooms can have **exits** that take you to other rooms. For instance, to the **NORTH** is the **RECEPTION DESK**. + To the **EAST** is a closed **DOOR**. - Rooms can also contain **items**. Sometimes the player can **TAKE** or **USE** items. + To the **SOUTH** is the foyer where you started your adventure. - Type **ITEMS** to see a list of items in the foyer. Or type **HELP** to see what else you can do!`; - }, - // optional list of items in the room - items: [ - { - name: 'tall window', // the item's name - desc: `All you can see are puffy white clouds over a blue sky.`, // description shown when player looks at the item - }, - { - name: ['monstera', 'plant', 'swiss cheese'], // player can refer to this item by any of these names - desc: `Sometimes called a Swiss Cheese plant, no office is complete without one. It has lovely, large leaves. This is the biggest you\'ve ever seen. - - There's **SOMETHING SHINY** in the pot.`, - block: `It's far too large for you to carry.`, // optional reason player cannot pick up this item - // when player looks at the plant, they discover a shiny object which turns out to be a key - onLook: () => { - if (getItemInRoom('shiny', 'foyer') || getItemInInventory('shiny')) { - // the key is already in the pot or the player's inventory - return; - } - - const foyer = getRoom('foyer'); - - // put the silver key in the pot - foyer.items.push({ - name: ['shiny thing', 'something shiny', 'pot'], - onUse: () => { - const room = getRoom(disk.roomId); - if (room.id === 'foyer') { - println(`There's nothing to unlock in the foyer.`); - } else if (room.id === 'reception') { - println(`You unlock the door to the **EAST**!`); - // remove the block - const exit = getExit('east', room.exits); - delete exit.block; - // this item can only be used once - const key = getItemInInventory('shiny'); - key.onUse = () => println(`The lab has already been unlocked.`); - } else { - println(`There's nothing to unlock here.`); - } - }, - desc: `It's a silver **KEY**!`, - onLook: () => { - const key = getItemInInventory('shiny') || getItemInRoom('shiny', 'foyer'); - - // now that we know it's a key, place that name first so the engine calls it by that name - key.name.unshift('silver key'); - - // let's also update the description - key.desc = `It has a blue cap with the word "LAB" printed on it.`; - - // remove this method (we don't need it anymore) - delete key.onLook; - }, - isTakeable: true, - onTake: () => { - println(`You took it.`); - // update the monstera's description, removing everything starting at the line break - const plant = getItemInRoom('plant', 'foyer'); - plant.desc = plant.desc.slice(0, plant.desc.indexOf('\n')); - }, - }); - }, - }, - { - name: 'dime', - desc: `Wow, ten cents.`, - isTakeable: true, // allow the player to TAKE this item - onTake: () => println(`You bend down and pick up the tiny, shiny coin. - - *Now it's in your **inventory**, and you can use it at any time, in any room. (Don't spend it all in one place!) - - Type **INV** to see a list of items in your inventory.*`), - // using the dime randomly prints HEADS or TAILS - onUse: () => { - const side = Math.random() > 0.5 ? 'HEADS' : 'TAILS'; - println(`You flip the dime. It lands on ${side}.`); - }, - } - ], - // places the player can go from this room - exits: [ - // GO NORTH command leads to the Reception Desk - {dir: 'north', id: 'reception'}, - ], - }, - { - id: 'reception', - name: 'Reception Desk', - desc: `**BENJI** is here. I'm sure he'd be happy to tell you more about the features available in **text-engine**. - - *You can speak with characters using the **TALK** command.* - - To the **EAST** is a closed **DOOR**. - - To the **SOUTH** is the foyer where you started your adventure. - - Next to the **DESK** are **STAIRS** leading **UP**.`, - items: [ - { - name: 'desk', - }, - { - name: 'door', - desc: `There are 4" metal letters nailed to the door. They spell out: "RESEARCH LAB".`, - onUse: () => { - const reception = getRoom('reception'); - const exit = getExit('east', reception.exits); - if (exit.block) { - println(`It's locked.`); - } else { - goDir('east'); - } + Next to the **DESK** are **STAIRS** leading **UP**.`, + items: [ + { + name: 'desk', }, - }, - { - name: 'gate', - desc: `The guilded gate is blocking the way to the **STAIRS**.`, - }, - { - name: ['stairs', 'staircase'], - desc: `They lead up to a door. If you squint, you can make out the word "ADVANCED" on the door.`, - onUse: () => println(`Try typing GO UPSTAIRS (once you've unlocked the gate).`), - }, - ], - exits: [ - // exits with a BLOCK cannot be used, but print a message instead - {dir: 'east', id: 'lab', block: `The door is locked.`}, - {dir: ['upstairs', 'up'], id: 'advanced', block: `There's a locked GATE blocking your path.`}, - {dir: 'south', id: 'foyer'}, - ], - }, - { - id: 'lab', - name: 'Research Lab', - desc: `There is a **BLUE ROBOT** hovering silently in the center of a white void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) - - To the **WEST** is the door to the Reception Desk.`, - exits: [ - {dir: 'west', id: 'reception'}, - ], - }, - { - id: 'advanced', - name: 'Advanced Research Lab', - desc: `There is a **RED ROBOT** hovering silently in the center of a black void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) - - **DOWNSTAIRS** is the Reception Desk.`, - exits: [ - {dir: ['downstairs', 'down'], id: 'reception'}, - ], - }, - ], - characters: [ - { - name: ['Benji', 'Benj', 'receptionist'], - roomId: 'reception', - desc: 'He looks... helpful!', // printed when the player looks at the character - // optional callback, run when the player talks to this character - onTalk: () => println(`"Hi," he says, "How can I help you?"`), - // things the player can discuss with the character - topics: [ - { - option: 'How can I change the visual **STYLE** of the game?', - removeOnRead: true, - // optional callback, run when the player selects this option - onSelected() { - println(`**BENJI** pulls a strange-looking *item* out of a desk drawer. - "Here, take this." he says. "Try typing **USE STYLE-CHANGER**. That should give you some ideas."`) - - // add a special item to the player's inventory - disk.inventory.push({ - name: 'style-changer', - desc: `This is a magical item. Type **USE STYLE-CHANGER** to try it out!`, - onUse: () => { - const currentStylesheet = document.getElementById('styles').getAttribute('href'); - const newName = currentStylesheet.includes('modern') ? 'retro' : 'modern'; - println(`You changed the stylesheet to ${newName.toUpperCase()}.css. - Since **text-engine** is built with HTML, you can use Cascading Stylesheets (CSS) to make your game look however you want!`); - selectStylesheet(`styles/${newName}.css`); + { + name: 'door', + desc: `There are 4" metal letters nailed to the door. They spell out: "RESEARCH LAB".`, + onUse: () => { + const reception = getRoom('reception'); + const exit = getExit('east', reception.exits); + if (exit.block) { + println(`It's locked.`); + } else { + goDir('east'); } - }); + }, }, - }, - { - option: 'How can I use **RICH** text?', - line: `"The text in the game is actually HTML, so you can use tags like <b> for bold, <i> for italic, and <u> for underline. - - "There's also support for Markdown-like syntax: - - • Wrapping some text in asterisks like *this* will *italicize* it. - • Double-asterisks like **this** will make it **bold**. - • Triple-asterisks like ***this*** will make it ***italic bold***. - • Double-underscores like __this__ will __underline__ it."`, - removeOnRead: true, - }, - { - option: `Tell me about **EXITS**`, - // text printed when the player selects this option by typing the keyword (EXITS) - line: `"Sure! It looks like you've already figured out you can type **GO NORTH** to use an exit to the north. But did you know you can just type **GO** to get a list of exits from the room? If an exit leads you to a room you've been to before, it will even tell you the room's name. - - "There are also some shortcuts to make getting where you're going easier. Instead of typing **GO NORTH**, you can just type **NORTH** instead. Actually, for cardinal directions, you can shorten it to simply **N**. - - "Sometimes you'll want to temporarily prevent players from using an **exit**. You can use *blocks* for this. Try going **EAST** from here to see what I mean. You'll find the **DOOR** is locked. You'll need to find the **KEY** to get inside. - - "These **STAIRS** are also blocked by a locked **GATE**. There isn't a key to the gate, so if you want to see what's up there, you'll have to find another way to get past it."`, - // instruct the engine to remove this option once the player has selected it - removeOnRead: true, - }, - { - option: `Remind me what's up with that **DOOR** to the east...`, - line: `"The exit has a *block*. Specifically, the **DOOR** it locked. You'll need to find a **KEY** to open it."`, - prereqs: ['exits'], // optional list of prerequisite topics that must be discussed before this option is available - }, - { - option: `Remind me what's up with these **STAIRS**...`, - line: `"The **STAIRS** are blocked by a locked **GATE**. There isn't a key, so you need to find another way to unlock it."`, - prereqs: ['exits'], - }, - { - option: `How do I use **AUTOCOMPLETE**?`, - line: `"If you type a few letters and press TAB, the engine will guess what you're trying to say."`, - removeOnRead: true, - }, - { - option: `If I want to **REPEAT** a command, do I have to type it again?`, - line: `"Wow, it's almost like you're reading my mind. No, you can just press the UP ARROW to see commands you've previously entered."`, - removeOnRead: true, - }, - ], - }, - { - name: 'blue robot', - roomId: 'lab', - onTalk: () => println(`"I can tell you about making games with text-engine," they explain. "What would you like to know?"`), - topics: [ - { - option: `What is **TEXT-ENGINE**?`, - line: `**text-engine** is a a JavaScript REPL-style, text-based adventure game engine. It's small and easy to use with no dependencies. - - The engine uses a disk metaphor for the data which represents your game, like the floppy disks of yore.` - }, - { - option: `How do I get **STARTED**?`, - line: `To create your own adventure, you can use an existing game disk as a template. You will find the disk you're playing now as well as others in the folder called "game-disks". You can edit these in any text or code editor. + { + name: 'gate', + desc: `The guilded gate is blocking the way to the **STAIRS**.`, + }, + { + name: ['stairs', 'staircase'], + desc: `They lead up to a door. If you squint, you can make out the word "ADVANCED" on the door.`, + onUse: () => println(`Try typing GO UPSTAIRS (once you've unlocked the gate).`), + }, + ], + exits: [ + // exits with a BLOCK cannot be used, but print a message instead + {dir: 'east', id: 'lab', block: `The door is locked.`}, + {dir: ['upstairs', 'up'], id: 'advanced', block: `There's a locked GATE blocking your path.`}, + {dir: 'south', id: 'foyer'}, + ], + }, + { + id: 'lab', + name: 'Research Lab', + desc: `There is a **BLUE ROBOT** hovering silently in the center of a white void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) + + To the **WEST** is the door to the Reception Desk.`, + exits: [ + {dir: 'west', id: 'reception'}, + ], + }, + { + id: 'advanced', + name: 'Advanced Research Lab', + desc: `There is a **RED ROBOT** hovering silently in the center of a black void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) + + **DOWNSTAIRS** is the Reception Desk.`, + exits: [ + {dir: ['downstairs', 'down'], id: 'reception'}, + ], + }, + ], + characters: [ + { + name: ['Benji', 'Benj', 'receptionist'], + roomId: 'reception', + desc: 'He looks... helpful!', // printed when the player looks at the character + // optional callback, run when the player talks to this character + onTalk: () => println(`"Hi," he says, "How can I help you?"`), + // things the player can discuss with the character + topics: [ + { + option: 'How can I change the visual **STYLE** of the game?', + removeOnRead: true, + // optional callback, run when the player selects this option + onSelected() { + println(`**BENJI** pulls a strange-looking *item* out of a desk drawer. + "Here, take this." he says. "Try typing **USE STYLE-CHANGER**. That should give you some ideas."`) + + // add a special item to the player's inventory + disk.inventory.push({ + name: 'style-changer', + desc: `This is a magical item. Type **USE STYLE-CHANGER** to try it out!`, + onUse: () => { + const currentStylesheet = document.getElementById('styles').getAttribute('href'); + const newName = currentStylesheet.includes('modern') ? 'retro' : 'modern'; + println(`You changed the stylesheet to ${newName.toUpperCase()}.css. + Since **text-engine** is built with HTML, you can use Cascading Stylesheets (CSS) to make your game look however you want!`); + selectStylesheet(`styles/${newName}.css`); + } + }); + }, + }, + { + option: 'How can I use **RICH** text?', + line: `"The text in the game is actually HTML, so you can use tags like <b> for bold, <i> for italic, and <u> for underline. - Include the 'game disk' (JSON data) in index.html and load it with loadDisk(myGameData). You can look at the included index.html file for an example. + "There's also support for Markdown-like syntax: - You are welcome to ask me about any topic you like, but you might find it easier just to browse a few and then dive in to making something of your own. You can return to ask me questions at any time.` - }, - { - option: `What is a **DISK**?`, - line: `A disk is a JavaScript object which describes your game. At minimum, it must have these two top-level properties: + • Wrapping some text in asterisks like *this* will *italicize* it. + • Double-asterisks like **this** will make it **bold**. + • Triple-asterisks like ***this*** will make it ***italic bold***. + • Double-underscores like __this__ will __underline__ it."`, + removeOnRead: true, + }, + { + option: `Tell me about **EXITS**`, + // text printed when the player selects this option by typing the keyword (EXITS) + line: `"Sure! It looks like you've already figured out you can type **GO NORTH** to use an exit to the north. But did you know you can just type **GO** to get a list of exits from the room? If an exit leads you to a room you've been to before, it will even tell you the room's name. - **roomId** (*string*) - This is a reference to the room the player currently occupies. Set this to the **ID** of the room the player should start in. + "There are also some shortcuts to make getting where you're going easier. Instead of typing **GO NORTH**, you can just type **NORTH** instead. Actually, for cardinal directions, you can shorten it to simply **N**. - **rooms** (*array*) - List of rooms in the game. + "Sometimes you'll want to temporarily prevent players from using an **exit**. You can use *blocks* for this. Try going **EAST** from here to see what I mean. You'll find the **DOOR** is locked. You'll need to find the **KEY** to get inside. - There are other properties you can choose to include if you like: + "These **STAIRS** are also blocked by a locked **GATE**. There isn't a key to the gate, so if you want to see what's up there, you'll have to find another way to get past it."`, + // instruct the engine to remove this option once the player has selected it + removeOnRead: true, + }, + { + option: `Remind me what's up with that **DOOR** to the east...`, + line: `"The exit has a *block*. Specifically, the **DOOR** it locked. You'll need to find a **KEY** to open it."`, + prereqs: ['exits'], // optional list of prerequisite topics that must be discussed before this option is available + }, + { + option: `Remind me what's up with these **STAIRS**...`, + line: `"The **STAIRS** are blocked by a locked **GATE**. There isn't a key, so you need to find another way to unlock it."`, + prereqs: ['exits'], + }, + { + option: `How do I use **AUTOCOMPLETE**?`, + line: `"If you type a few letters and press TAB, the engine will guess what you're trying to say."`, + removeOnRead: true, + }, + { + option: `If I want to **REPEAT** a command, do I have to type it again?`, + line: `"Wow, it's almost like you're reading my mind. No, you can just press the UP ARROW to see commands you've previously entered."`, + removeOnRead: true, + }, + ], + }, + { + name: 'blue robot', + roomId: 'lab', + onTalk: () => println(`"I can tell you about making games with text-engine," they explain. "What would you like to know?"`), + topics: [ + { + option: `What is **TEXT-ENGINE**?`, + line: `**text-engine** is a a JavaScript REPL-style, text-based adventure game engine. It's small and easy to use with no dependencies. + + The engine uses a disk metaphor for the data which represents your game, like the floppy disks of yore.` + }, + { + option: `How do I get **STARTED**?`, + line: `To create your own adventure, you can use an existing game disk as a template. You will find the disk you're playing now as well as others in the folder called "game-disks". You can edit these in any text or code editor. - **inventory** (*array*) - List of items in the player's inventory. + Include the 'game disk' (JSON data) in index.html and load it with loadDisk(myGameData). You can look at the included index.html file for an example. - **characters** (*array*) - List of characters in the game. + You are welcome to ask me about any topic you like, but you might find it easier just to browse a few and then dive in to making something of your own. You can return to ask me questions at any time.` + }, + { + option: `What is a **DISK**?`, + line: `A disk is a JavaScript object which describes your game. At minimum, it must have these two top-level properties: - You can also attach any arbitrary data you wish. For instance, you could have a number called "health" that you use to keep track of your player's condition.` - }, - { - option: `What is a **ROOM**?`, - line: `A room is a JavaScript object. You usually want a room to have the following properties: + **roomId** (*string*) - This is a reference to the room the player currently occupies. Set this to the **ID** of the room the player should start in. - **name** (*string*) - The name of the room will be displayed each time it is entered. + **rooms** (*array*) - List of rooms in the game. - **id** (*string*) - Unique identifier for this room. Can be anything. + There are other properties you can choose to include if you like: - **desc** (*string*) - Description of the room, displayed when it is first entered, and also when the player issues the **LOOK** command. + **inventory** (*array*) - List of items in the player's inventory. - **exits** (*array*) - List of paths from this room. + **characters** (*array*) - List of characters in the game. - Rooms can have these other optional properties as well: + You can also attach any arbitrary data you wish. For instance, you could have a number called "health" that you use to keep track of your player's condition.` + }, + { + option: `What is a **ROOM**?`, + line: `A room is a JavaScript object. You usually want a room to have the following properties: - **img** (*string*) - Graphic to be displayed each time the room is entered. (This is intended to be ASCII art.) + **name** (*string*) - The name of the room will be displayed each time it is entered. - **items** (*string*) - List of items in this room. Items can be interacted with by the player. + **id** (*string*) - Unique identifier for this room. Can be anything. - **onEnter** (*function*) - Function to be called when the player enters this room. + **desc** (*string*) - Description of the room, displayed when it is first entered, and also when the player issues the **LOOK** command. - **onLook** (*function*) - Function to be called when the player issues the **LOOK** command in this room.` - }, - { - option: `What is an **EXIT**?`, - line: `An exit is a JavaScript object with the following properties: + **exits** (*array*) - List of paths from this room. - **dir** (*string*) - The direction the player must go to leave via this exit (e.g. "north"). + Rooms can have these other optional properties as well: - **id** (*string*) - The ID of the room this exit leads to. + **img** (*string*) - Graphic to be displayed each time the room is entered. (This is intended to be ASCII art.) - An exit can optionally have a *block* as well: + **items** (*string*) - List of items in this room. Items can be interacted with by the player. - **block** (*string*) - Line to be printed if the player tries to use this exit. If this property exists, the player cannot use the exit.` - }, - { - option: `What is an **ITEM**?`, - line: `An item is a JavaScript object with a name: + **onEnter** (*function*) - Function to be called when the player enters this room. - **name** (*string* or *array*) - How the item is referred to by the game and the player. Using an array allows you to define multiple string names for the item. You might do this if you expect the player may call it by more than one name. For instance ['basketball', 'ball']. When listing items in a room, the engine will always use the first name in the list. + **onLook** (*function*) - Function to be called when the player issues the **LOOK** command in this room.` + }, + { + option: `What is an **EXIT**?`, + line: `An exit is a JavaScript object with the following properties: - Items can have these other optional properties as well: + **dir** (*string*) - The direction the player must go to leave via this exit (e.g. "north"). - **desc** (*string* or *array*) - Description. Text displayed when the player looks at the item. If multiple descriptions are provided, one will be chosen at random. + **id** (*string*) - The ID of the room this exit leads to. - **isTakeable** (*boolean*) - Whether the player can pick up this item (if it's in a room). Defaults to false. + An exit can optionally have a *block* as well: - **onUse** (*function*) - Function to be called when the player uses the item. + **block** (*string*) - Line to be printed if the player tries to use this exit. If this property exists, the player cannot use the exit.` + }, + { + option: `What is an **ITEM**?`, + line: `An item is a JavaScript object with a name: - **onLook** (*function*) - Function to be called when the player looks at the item. + **name** (*string* or *array*) - How the item is referred to by the game and the player. Using an array allows you to define multiple string names for the item. You might do this if you expect the player may call it by more than one name. For instance ['basketball', 'ball']. When listing items in a room, the engine will always use the first name in the list. - **onTake** (*function*) - Function to be called when the player takes the item.` - }, - { - option: `What is a **CHARACTER**?`, - line: `You're talking to one! A character is a JavaScript object with the following properties: + Items can have these other optional properties as well: - **name** (*string or array*) - How the character is referred to by the game and the player. Using an array allows you to define multiple string names for the character. You might do this if you expect the player may call them by more than one name. For instance ['Steve', 'waiter', 'garçon']. When listing characters in a room, the engine will always use the first name in the list. + **desc** (*string* or *array*) - Description. Text displayed when the player looks at the item. If multiple descriptions are provided, one will be chosen at random. - **roomId** (*string*) - The ID of the room the character is currently in. The player can only talk to characters in the room with them. + **isTakeable** (*boolean*) - Whether the player can pick up this item (if it's in a room). Defaults to false. - Characters can have these other optional properties as well: + **onUse** (*function*) - Function to be called when the player uses the item. - **desc** (*string* or *array*) - Description. Text displayed when the player looks at the character. If multiple descriptions are provided, one will be chosen at random. + **onLook** (*function*) - Function to be called when the player looks at the item. - **topics** (*string* or *array*) - If a string is provided, it will be printed when the player talks to this character. Otherwise, this should be a list of topics for use in the conversation with the character. + **onTake** (*function*) - Function to be called when the player takes the item.` + }, + { + option: `What is a **CHARACTER**?`, + line: `You're talking to one! A character is a JavaScript object with the following properties: - **onTalk** (*function*) - Function to be called when the player talks to the character. + **name** (*string or array*) - How the character is referred to by the game and the player. Using an array allows you to define multiple string names for the character. You might do this if you expect the player may call them by more than one name. For instance ['Steve', 'waiter', 'garçon']. When listing characters in a room, the engine will always use the first name in the list. - **onLook** (*function*) - Function to be called when the player looks at the character.` - }, - { - option: `What is a **TOPIC**?`, - line: `A topic is something you can talk to a character about, and as you may have guessed, is a JavaScript object. A topic requires an *option*, and either a *line* or an *onSelected* function, or both: + **roomId** (*string*) - The ID of the room the character is currently in. The player can only talk to characters in the room with them. - **option** (*string*) - The choice presented to the player, with a KEYWORD the player can type to select it. If the keyword is written in uppercase, the engine can identify it automatically. (Otherwise, you'll need to specify the keyword in a separate property.) The option can be just the keyword itself, or any string containing the keyword. + Characters can have these other optional properties as well: - **line** (*string*) - The text to display when the player types the keyword to select the option. + **desc** (*string* or *array*) - Description. Text displayed when the player looks at the character. If multiple descriptions are provided, one will be chosen at random. - **onSelected** (*function*) - Function to be called when the player types the keyword to select the option. + **topics** (*string* or *array*) - If a string is provided, it will be printed when the player talks to this character. Otherwise, this should be a list of topics for use in the conversation with the character. - Topics can have these other optional properties as well: + **onTalk** (*function*) - Function to be called when the player talks to the character. - **removeOnRead** (*boolean*) - Whether this option should no longer be available to the player after it has been selected once. + **onLook** (*function*) - Function to be called when the player looks at the character.` + }, + { + option: `What is a **TOPIC**?`, + line: `A topic is something you can talk to a character about, and as you may have guessed, is a JavaScript object. A topic requires an *option*, and either a *line* or an *onSelected* function, or both: - **prereqs** (*array*) - Array of keyword strings representing the prerequisite topics a player must have selected before this one will appear. (When topics are selected, their keywords go into an array on the character called "chatLog".) + **option** (*string*) - The choice presented to the player, with a KEYWORD the player can type to select it. If the keyword is written in uppercase, the engine can identify it automatically. (Otherwise, you'll need to specify the keyword in a separate property.) The option can be just the keyword itself, or any string containing the keyword. - **keyword** (*string*) - The word the player must type to select this option. This property is only required if the option itself does not contain a keyword written in uppercase.` - }, - { - option: `Can you unlock the **GATE** to the stairs by the reception desk?`, - line: `Actually, you can do that yourself! This disk happens to have a secret, custom **UNLOCK** command. This powerful command will remove blocks on any exit. Just type **UNLOCK** to use it.`, - }, - ], - }, - { - name: 'red robot', - roomId: 'advanced', - onTalk: () => println(`"I can tell you about the JavaScript functions available to you when you use text-engine," they explain. "What would you like to know about?"`), - topics: [ - { - option: `Tell me about **FUNCTIONS**`, - line: `Functions are reuseable bits of JavaScript code. **text-engine** provides several of these which you can use, for instance in callbacks like onUse, onLook, onEnter, etc.` - }, - { - option: `Tell me about **COMMANDS**`, - line: `Every command a player can issue in the game has a corresponding function in **text-engine**. + **line** (*string*) - The text to display when the player types the keyword to select the option. - For instance, there's a function called go that gets called when the player types **GO**. + **onSelected** (*function*) - Function to be called when the player types the keyword to select the option. - You can add your own custom commands, like the **UNLOCK** command you used to get access to this room. And if existing commands don't work how you want them to, you can ever override them by reassigning them to your own function code.`, - }, - { - option: `Tell me about **PRINTLN**`, - line: `println is a function you can use to print a line of text to the console. It takes up to two arguments: + Topics can have these other optional properties as well: - **line** (*string*) - The text to be printed. + **removeOnRead** (*boolean*) - Whether this option should no longer be available to the player after it has been selected once. - **className** (*string*) - Optional. The name of a CSS class to apply to the line. You can use this to style the text.` - }, - { - option: `Tell me about **PICKONE**`, - line: `pickOne is a function you can use to get a random item from an array. It takes one argument: + **prereqs** (*array*) - Array of keyword strings representing the prerequisite topics a player must have selected before this one will appear. (When topics are selected, their keywords go into an array on the character called "chatLog".) - **arr** (*array*) - The array with the items to pick from.` - }, - { - option: `Tell me about **GETROOM**`, - line: `getRoom is a function you can use to get a reference to a room by its ID. It takes one argument: + **keyword** (*string*) - The word the player must type to select this option. This property is only required if the option itself does not contain a keyword written in uppercase.` + }, + { + option: `Can you unlock the **GATE** to the stairs by the reception desk?`, + line: `Actually, you can do that yourself! This disk happens to have a secret, custom **UNLOCK** command. This powerful command will remove blocks on any exit. Just type **UNLOCK** to use it.`, + }, + ], + }, + { + name: 'red robot', + roomId: 'advanced', + onTalk: () => println(`"I can tell you about the JavaScript functions available to you when you use text-engine," they explain. "What would you like to know about?"`), + topics: [ + { + option: `Tell me about **FUNCTIONS**`, + line: `Functions are reuseable bits of JavaScript code. **text-engine** provides several of these which you can use, for instance in callbacks like onUse, onLook, onEnter, etc.` + }, + { + option: `Tell me about **COMMANDS**`, + line: `Every command a player can issue in the game has a corresponding function in **text-engine**. - **id** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **ENTERROOM**`, - line: `enterRoom is a function you can use to move the player to particular room. It takes one argument: + For instance, there's a function called go that gets called when the player types **GO**. - **id** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETCHARACTER**`, - line: `getCharacter is a function you can use to get a reference to a character. It takes up to two arguments: + You can add your own custom commands, like the **UNLOCK** command you used to get access to this room. And if existing commands don't work how you want them to, you can ever override them by reassigning them to your own function code.`, + }, + { + option: `Tell me about **PRINTLN**`, + line: `println is a function you can use to print a line of text to the console. It takes up to two arguments: - **name** (*string*) - The character's name. + **line** (*string*) - The text to be printed. - **chars** (*array*) - Optional. The array of characters to search. Defaults to searching all characters on the disk.` - }, - { - option: `Tell me about **GETCHARACTERSINROOM**`, - line: `getCharactersInRoom is a function you can use to get an array containing references to each character in a particular room. It takes one argument: + **className** (*string*) - Optional. The name of a CSS class to apply to the line. You can use this to style the text.` + }, + { + option: `Tell me about **PICKONE**`, + line: `pickOne is a function you can use to get a random item from an array. It takes one argument: - **roomId** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETITEMINROOM**`, - line: `getItemInRoom is a function you can use to get a reference to an item in a particular room. It takes two arguments: + **arr** (*array*) - The array with the items to pick from.` + }, + { + option: `Tell me about **GETROOM**`, + line: `getRoom is a function you can use to get a reference to a room by its ID. It takes one argument: - **itemName** (*string*) - The name of the item. + **id** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **ENTERROOM**`, + line: `enterRoom is a function you can use to move the player to particular room. It takes one argument: - **roomId** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETITEMININVENTORY**`, - line: `getItemInInventory is a function you can use to get a reference to an item in the player's inventory. It takes one argument: + **id** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETCHARACTER**`, + line: `getCharacter is a function you can use to get a reference to a character. It takes up to two arguments: - **name** (*string*) - The name of the item.` - }, - { - option: `Tell me about **OTHER** functions`, - line: `There are several other functions available in the engine! Feel free to take a peek at the source code (index.js). It's designed to be open and simple to use and to customize.` - }, - ], - }, - ], -}; + **name** (*string*) - The character's name. -// custom functions used by this disk -// change the CSS stylesheet to the one with the passed name -const selectStylesheet = filename => document.getElementById('styles').setAttribute('href', filename); + **chars** (*array*) - Optional. The array of characters to search. Defaults to searching all characters on the disk.` + }, + { + option: `Tell me about **GETCHARACTERSINROOM**`, + line: `getCharactersInRoom is a function you can use to get an array containing references to each character in a particular room. It takes one argument: -// override commands to include custom UNLOCK command -// create the unlock function -const unlock = () => { - disk.rooms.forEach(room => { - if (!room.exits) { - return; - } + **roomId** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETITEMINROOM**`, + line: `getItemInRoom is a function you can use to get a reference to an item in a particular room. It takes two arguments: - // unblock all blocked exits in the room - room.exits.forEach(exit => delete exit.block); - }); + **itemName** (*string*) - The name of the item. - // update the description of the gate - getItemInRoom('gate', 'reception').desc = `The guilded gate leads to the staircase.`; + **roomId** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETITEMININVENTORY**`, + line: `getItemInInventory is a function you can use to get a reference to an item in the player's inventory. It takes one argument: - println(`All **exits** have been unblocked!`); + **name** (*string*) - The name of the item.` + }, + { + option: `Tell me about **OTHER** functions`, + line: `There are several other functions available in the engine! Feel free to take a peek at the source code (index.js). It's designed to be open and simple to use and to customize.` + }, + ], + }, + ], + }; + + // custom functions used by this disk + // change the CSS stylesheet to the one with the passed name + const selectStylesheet = filename => document.getElementById('styles').setAttribute('href', filename); + + // override commands to include custom UNLOCK command + // create the unlock function + const unlock = () => { + disk.rooms.forEach(room => { + if (!room.exits) { + return; + } + + // unblock all blocked exits in the room + room.exits.forEach(exit => delete exit.block); + }); + + // update the description of the gate + getItemInRoom('gate', 'reception').desc = `The guilded gate leads to the staircase.`; + + println(`All **exits** have been unblocked!`); + }; + + // attach it to the zero-argument commands object on the disk + commands[0] = Object.assign(commands[0], {unlock}); + + return disk; }; - -// attach it to the zero-argument commands object on the disk -commands[0] = Object.assign(commands[0], {unlock}); diff --git a/index.js b/index.js index 0ccdba4..c771c26 100644 --- a/index.js +++ b/index.js @@ -1,8 +1,9 @@ // global properties, assigned with let for easy overriding by the user +let diskFactory; let disk; // store user input history -let inputs = ['']; +let inputs = []; let inputsPos = 0; // define list style @@ -74,7 +75,7 @@ let setup = () => { // store player input history // (optionally accepts a name for the save) let save = (name = 'save') => { - localStorage.setItem(name, JSON.stringify(inputs.filter(isNotMeta))); + localStorage.setItem(name, JSON.stringify(inputs)); const line = name.length ? `Game saved as "${name}".` : `Game saved.`; println(line); }; @@ -82,10 +83,9 @@ let save = (name = 'save') => { // reapply inputs from saved game // (optionally accepts a name for the save) let load = (name = 'save') => { - if (inputs.filter(isNotMeta).length > 2) { - println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); - return; - } + inputs = []; + inputsPos = 0; + loadDisk(); let save = localStorage.getItem(name); @@ -103,17 +103,12 @@ let load = (name = 'save') => { // export current game to disk (optionally accepts a filename) let exportSave = (name) => { const filename = `${name.length ? name : 'text-engine-save'}.txt`; - saveFile(JSON.stringify(inputs.filter(isNotMeta)), filename); + saveFile(JSON.stringify(inputs), filename); println(`Game exported to "${filename}".`); }; // import a previously exported game from disk let importSave = () => { - if (inputs.filter(isNotMeta).length > 2) { - println(`At present, you cannot load in the middle of the game. Please reload the browser, then run the **IMPORT** command again.`); - return; - } - const input = openFile(); input.onchange = () => { const fr = new FileReader(); @@ -161,20 +156,13 @@ let openFile = () => { return input; }; -// asserts the command is not save, load, import or export, nor blank (could use a better name...) -let isNotMeta = (cmd) => !cmd.toLowerCase().startsWith('save') - && !cmd.toLowerCase().startsWith('load') - && !cmd.toLowerCase().startsWith('export') - && !cmd.toLowerCase().startsWith('import') - && cmd !== ''; - // applies string representing an array of input strings (used for loading saved games) let applyInputs = (string) => { let ins = []; // parse, filtering out the save/load commands & empty strings try { - ins = JSON.parse(string).filter(isNotMeta); + ins = JSON.parse(string); } catch(err) { println(`An error occurred. See error console for more details.`); console.error(`An error occurred while attempting to parse text-engine inputs. @@ -693,8 +681,16 @@ let commands = [ // process user input & update game state (bulk of the engine) // accepts optional string input; otherwise grabs it from the input element let applyInput = (input) => { + // asserts the command is not save, load, import or export, nor blank (could use a better name...) + let isNotMeta = (cmd) => !cmd.toLowerCase().startsWith('save') + && !cmd.toLowerCase().startsWith('load') + && !cmd.toLowerCase().startsWith('export') + && !cmd.toLowerCase().startsWith('import') + && cmd !== ''; + input = input || getInput(); inputs.push(input); + inputs = ['', ...inputs.filter(isNotMeta)]; inputsPos = inputs.length; println(`> ${input}`); @@ -882,6 +878,7 @@ let autocomplete = () => { // select previously entered commands // string -> nothing let navigateHistory = (dir) => { + console.log('navigate', dir); if (dir === 'prev') { inputsPos--; if (inputsPos < 0) { @@ -1034,15 +1031,18 @@ let endConversation = () => { // load the passed disk and start the game // disk -> nothing let loadDisk = (uninitializedDisk) => { + if (uninitializedDisk) { + diskFactory = uninitializedDisk; + // start listening for user input + setup(); + } + // initialize the disk - disk = init(uninitializedDisk); + disk = init(diskFactory()); // start the game enterRoom(disk.roomId); - // start listening for user input - setup(); - // focus on the input input.focus(); }; From 3737f6f394906aa5f3d1181016f8b99054e72db7 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 14:32:22 -0500 Subject: [PATCH 05/46] Additional work on replay save system * add support for original object-based disk format (requires browser refresh to load saves) * add getDisk method to retrieve active disk from memory * update demo disk to utilize getDisk method (fixes e.g. broken key) --- game-disks/demo-disk.js | 6 +++--- index.html | 4 ++-- index.js | 11 ++++++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/game-disks/demo-disk.js b/game-disks/demo-disk.js index c484929..0e9bf0f 100644 --- a/game-disks/demo-disk.js +++ b/game-disks/demo-disk.js @@ -50,7 +50,7 @@ const demoDisk = () => { foyer.items.push({ name: ['shiny thing', 'something shiny', 'pot'], onUse: () => { - const room = getRoom(disk.roomId); + const room = getRoom(getDisk().roomId); if (room.id === 'foyer') { println(`There's nothing to unlock in the foyer.`); } else if (room.id === 'reception') { @@ -195,7 +195,7 @@ const demoDisk = () => { "Here, take this." he says. "Try typing **USE STYLE-CHANGER**. That should give you some ideas."`) // add a special item to the player's inventory - disk.inventory.push({ + getDisk().inventory.push({ name: 'style-changer', desc: `This is a magical item. Type **USE STYLE-CHANGER** to try it out!`, onUse: () => { @@ -471,7 +471,7 @@ const demoDisk = () => { // override commands to include custom UNLOCK command // create the unlock function const unlock = () => { - disk.rooms.forEach(room => { + getDisk().rooms.forEach(room => { if (!room.exits) { return; } diff --git a/index.html b/index.html index 8b473ac..c81aba8 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - + - + diff --git a/index.js b/index.js index c771c26..761dd0e 100644 --- a/index.js +++ b/index.js @@ -83,6 +83,12 @@ let save = (name = 'save') => { // reapply inputs from saved game // (optionally accepts a name for the save) let load = (name = 'save') => { + // if the disk provided is an object rather than a factory function, the game state must be reset by reloading + if (typeof diskFactory !== 'function' && inputs.length > 2) { + println(`You cannot load this disk in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); + return; + } + inputs = []; inputsPos = 0; loadDisk(); @@ -1038,7 +1044,8 @@ let loadDisk = (uninitializedDisk) => { } // initialize the disk - disk = init(diskFactory()); + // (although we expect the disk to be a factory function, we still support the old object format) + disk = init(typeof diskFactory === 'function' ? diskFactory() : diskFactory); // start the game enterRoom(disk.roomId); @@ -1047,6 +1054,8 @@ let loadDisk = (uninitializedDisk) => { input.focus(); }; +let getDisk = () => disk; + // npm support if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = loadDisk; From 0936c44c0ca912171c5f6b0198e2d997920e44af Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 14:41:44 -0500 Subject: [PATCH 06/46] stop appending empty strings to inputs --- index.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.js b/index.js index 761dd0e..a5848e9 100644 --- a/index.js +++ b/index.js @@ -687,16 +687,15 @@ let commands = [ // process user input & update game state (bulk of the engine) // accepts optional string input; otherwise grabs it from the input element let applyInput = (input) => { - // asserts the command is not save, load, import or export, nor blank (could use a better name...) + // asserts the command is not save, load, import or export (could use a better name...) let isNotMeta = (cmd) => !cmd.toLowerCase().startsWith('save') && !cmd.toLowerCase().startsWith('load') && !cmd.toLowerCase().startsWith('export') - && !cmd.toLowerCase().startsWith('import') - && cmd !== ''; + && !cmd.toLowerCase().startsWith('import'); input = input || getInput(); inputs.push(input); - inputs = ['', ...inputs.filter(isNotMeta)]; + inputs = inputs.filter(isNotMeta); inputsPos = inputs.length; println(`> ${input}`); From 8e91eba050fc9833ca53e15dbd3c145f421ee74b Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 14:43:38 -0500 Subject: [PATCH 07/46] convert new disk template to a disk factory --- game-disks/new-disk-template.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game-disks/new-disk-template.js b/game-disks/new-disk-template.js index 4e5282c..1ad473b 100644 --- a/game-disks/new-disk-template.js +++ b/game-disks/new-disk-template.js @@ -1,6 +1,6 @@ // This simple game disk can be used as a starting point to create a new adventure. // Change anything you want, add new rooms, etc. -const newDiskTemplate = { +const newDiskTemplate = () => ({ roomId: 'start', // Set this to the ID of the room you want the player to start in. rooms: [ { @@ -55,4 +55,4 @@ const newDiskTemplate = { ], } ], -}; +}); From bd6593124479dfe97668ac09bd9b7f3f3f41520a Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 14:58:27 -0500 Subject: [PATCH 08/46] Additional work on replay save system * update import method to reload disk from factory * update game-in-progress check (inputs.length > 0) * add in-line documentation for getDisk method * add generic getItem method which will look in your inventory, then in the room --- index.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index a5848e9..d9f4019 100644 --- a/index.js +++ b/index.js @@ -84,7 +84,7 @@ let save = (name = 'save') => { // (optionally accepts a name for the save) let load = (name = 'save') => { // if the disk provided is an object rather than a factory function, the game state must be reset by reloading - if (typeof diskFactory !== 'function' && inputs.length > 2) { + if (typeof diskFactory !== 'function' && inputs.length) { println(`You cannot load this disk in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); return; } @@ -115,6 +115,12 @@ let exportSave = (name) => { // import a previously exported game from disk let importSave = () => { + // if the disk provided is an object rather than a factory function, the game state must be reset by reloading + if (typeof diskFactory !== 'function' && inputs.length) { + println(`You cannot load this disk in the middle of the game. Please reload the browser, then run the **LOAD** command again.`); + return; + } + const input = openFile(); input.onchange = () => { const fr = new FileReader(); @@ -123,6 +129,9 @@ let importSave = () => { // register file loaded callback fr.onload = () => { // load the game + inputs = []; + inputsPos = 0; + loadDisk(); applyInputs(fr.result); println(`Game "${file.name}" was loaded.`); input.remove(); @@ -982,6 +991,10 @@ let getItemInRoom = (itemName, roomId) => { // string -> item let getItemInInventory = (name) => disk.inventory.find(item => objectHasName(item, name)); +// get item by name +// string -> item +let getItem = (name) => getItemInInventory(name) || getItemInRoom(name, disk.roomId) + // retrieves a keyword from a topic // topic -> string let getKeywordFromTopic = (topic) => { @@ -1053,6 +1066,8 @@ let loadDisk = (uninitializedDisk) => { input.focus(); }; +// get the active disk from memory +// nothing -> disk let getDisk = () => disk; // npm support From c4b99c12daba5947163f92b1d9e1794822142e63 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 14:59:17 -0500 Subject: [PATCH 09/46] disk template updates axe description on use --- game-disks/new-disk-template.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/game-disks/new-disk-template.js b/game-disks/new-disk-template.js index 1ad473b..e158078 100644 --- a/game-disks/new-disk-template.js +++ b/game-disks/new-disk-template.js @@ -29,6 +29,9 @@ const newDiskTemplate = () => ({ if (exit.block) { delete exit.block; println(`You cut through the vines, unblocking the door to the NORTH.`); + + // Update the axe's description. + getItem('axe').desc = `You USED it to cut the VINES, unblocking the door.`; } else { println(`There is nothing to use the axe on.`); } From 032b6edf25d02b0951c92e974a17397cc2c44b27 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 15:21:28 -0500 Subject: [PATCH 10/46] convert ur dead to a disk factory --- game-disks/ur-dead.js | 65 ++++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 32 deletions(-) diff --git a/game-disks/ur-dead.js b/game-disks/ur-dead.js index ecaefcf..95f36f4 100644 --- a/game-disks/ur-dead.js +++ b/game-disks/ur-dead.js @@ -1,6 +1,6 @@ // NOTE: This game is a work in progress! -const urDead = { +const urDead = () => ({ roomId: 'title', todo: [{id: 0, desc: `Figure out where you are.`}], inventory: [ @@ -100,7 +100,7 @@ const urDead = { room.desc = `You see a couple of skeletons. You get the feeling they don't care for you.`; println(`One of the skeletons performs an elaborate dance to set up their shot, dribbling out a steady beat. They are clearly banking on the other forgetting one of the many the steps, and thus adding an 'O' to their 'H'. They're so swept up in their routine that you're able to step in and swipe the ball on a down beat. - The skeletons don't look happy. (Later, you will confoundedly try to remember how you could TELL they looked uphappy.)`); + The skeletons don't look happy. (Later, you will confoundedly try to remember how you could TELL they looked unhappy.)`); item.onUse = () => println(`It's a bit hard to dribble on the uneven floor, but you manage to do so awkwardly.`); } @@ -837,33 +837,6 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, }, ], methods: { - commands: { - play: () => println(`You're already playing a game.`), - // set player's name - name: (arg) => { - if (!arg.length) { - println(`Type NAME followed by the name you wish to choose.`); - return; - } - - disk.playerName = (Array.isArray(arg) ? arg.join(' ') : arg).toUpperCase(); - const nametag = disk.inventory.find(i => i.name === 'nametag'); - - if (!nametag) { - println(`You don't have a nametag.`); - return; - } - - nametag.desc = `It says ${disk.playerName}.`; - - // update Fran's greeting - const fran = getCharacter('fran'); - fran.onTalk = () => println(`"Hello there, ${disk.playerName}."`); - - // confirm the change - println(`Your name is now ${disk.playerName}.`); - }, - }, // cross an item off player's to-do list crossOff: (id) => { disk.todo.find(item => item.id === id).done = true; @@ -899,9 +872,37 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, } }, }, +}); + +const customCommands = { + play: () => println(`You're already playing a game.`), + // set player's name + name: (arg) => { + if (!arg.length) { + println(`Type NAME followed by the name you wish to choose.`); + return; + } + + disk.playerName = (Array.isArray(arg) ? arg.join(' ') : arg).toUpperCase(); + const nametag = disk.inventory.find(i => i.name === 'nametag'); + + if (!nametag) { + println(`You don't have a nametag.`); + return; + } + + nametag.desc = `It says ${disk.playerName}.`; + + // update Fran's greeting + const fran = getCharacter('fran'); + fran.onTalk = () => println(`"Hello there, ${disk.playerName}."`); + + // confirm the change + println(`Your name is now ${disk.playerName}.`); + }, }; // override commands to include custom commands -commands[0] = Object.assign(commands[0], urDead.methods.commands); -commands[1] = Object.assign(commands[1], urDead.methods.commands); -commands[2] = Object.assign(commands[2], {play: urDead.methods.commands.play, name: urDead.methods.commands.name}); +commands[0] = Object.assign(commands[0], customCommands); +commands[1] = Object.assign(commands[1], customCommands); +commands[2] = Object.assign(commands[2], {play: customCommands.play, name: customCommands.name}); From 3d41d2664353c487c6f7d2f8799267b747d94c8a Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 15:21:50 -0500 Subject: [PATCH 11/46] remove getDisk method, which wasn't needed after all --- game-disks/demo-disk.js | 844 ++++++++++++++++++++-------------------- index.js | 4 - 2 files changed, 420 insertions(+), 428 deletions(-) diff --git a/game-disks/demo-disk.js b/game-disks/demo-disk.js index 0e9bf0f..05c68f5 100644 --- a/game-disks/demo-disk.js +++ b/game-disks/demo-disk.js @@ -1,493 +1,489 @@ -const demoDisk = () => { - const disk = { - roomId: 'foyer', // the ID of the room the player starts in - rooms: [ - { - id: 'foyer', // unique ID for this room - name: 'The Foyer', // room name (shown when player enters the room) - // room description (shown when player first enters the room) - desc: `Welcome to the **TEXT-ENGINE** demo disk! This disk is a text adventure game designed to introduce you to the features available to you in **text-engine**. Using this engine, you can make a text game of your own. - - Type **LOOK** to have a look around.`, - // optional callback when player issues the LOOK command - // here, we use it to change the foyer's description - onLook: () => { - const room = getRoom('foyer'); - room.desc = `You are currently standing in the foyer. There's a huge **MONSTERA** plant to your right, and a massive **WINDOW** to your left bathing the room in natural light. Both the **PLANT** and the **WINDOW** stretch to the ceiling, which must be at least 25 feet high. - - ***Rooms** form the foundation of the engine's design. At any given time, your player will be standing in one of the rooms you built for them. These can be literal rooms like the foyer you find yourself in now, or metaphorical rooms like **The End of Time** or **Purgatory**. - - Each room you create should have a description. (That's what you're reading now!) - - Rooms can have **exits** that take you to other rooms. For instance, to the **NORTH** is the **RECEPTION DESK**. - - Rooms can also contain **items**. Sometimes the player can **TAKE** or **USE** items. - - Type **ITEMS** to see a list of items in the foyer. Or type **HELP** to see what else you can do!`; - }, - // optional list of items in the room - items: [ - { - name: 'tall window', // the item's name - desc: `All you can see are puffy white clouds over a blue sky.`, // description shown when player looks at the item - }, - { - name: ['monstera', 'plant', 'swiss cheese'], // player can refer to this item by any of these names - desc: `Sometimes called a Swiss Cheese plant, no office is complete without one. It has lovely, large leaves. This is the biggest you\'ve ever seen. - - There's **SOMETHING SHINY** in the pot.`, - block: `It's far too large for you to carry.`, // optional reason player cannot pick up this item - // when player looks at the plant, they discover a shiny object which turns out to be a key - onLook: () => { - if (getItemInRoom('shiny', 'foyer') || getItemInInventory('shiny')) { - // the key is already in the pot or the player's inventory - return; - } +const demoDisk = () => ({ + roomId: 'foyer', // the ID of the room the player starts in + rooms: [ + { + id: 'foyer', // unique ID for this room + name: 'The Foyer', // room name (shown when player enters the room) + // room description (shown when player first enters the room) + desc: `Welcome to the **TEXT-ENGINE** demo disk! This disk is a text adventure game designed to introduce you to the features available to you in **text-engine**. Using this engine, you can make a text game of your own. - const foyer = getRoom('foyer'); - - // put the silver key in the pot - foyer.items.push({ - name: ['shiny thing', 'something shiny', 'pot'], - onUse: () => { - const room = getRoom(getDisk().roomId); - if (room.id === 'foyer') { - println(`There's nothing to unlock in the foyer.`); - } else if (room.id === 'reception') { - println(`You unlock the door to the **EAST**!`); - // remove the block - const exit = getExit('east', room.exits); - delete exit.block; - // this item can only be used once - const key = getItemInInventory('shiny'); - key.onUse = () => println(`The lab has already been unlocked.`); - } else { - println(`There's nothing to unlock here.`); - } - }, - desc: `It's a silver **KEY**!`, - onLook: () => { - const key = getItemInInventory('shiny') || getItemInRoom('shiny', 'foyer'); - - // now that we know it's a key, place that name first so the engine calls it by that name - key.name.unshift('silver key'); - - // let's also update the description - key.desc = `It has a blue cap with the word "LAB" printed on it.`; - - // remove this method (we don't need it anymore) - delete key.onLook; - }, - isTakeable: true, - onTake: () => { - println(`You took it.`); - // update the monstera's description, removing everything starting at the line break - const plant = getItemInRoom('plant', 'foyer'); - plant.desc = plant.desc.slice(0, plant.desc.indexOf('\n')); - }, - }); - }, - }, - { - name: 'dime', - desc: `Wow, ten cents.`, - isTakeable: true, // allow the player to TAKE this item - onTake: () => println(`You bend down and pick up the tiny, shiny coin. - - *Now it's in your **inventory**, and you can use it at any time, in any room. (Don't spend it all in one place!) - - Type **INV** to see a list of items in your inventory.*`), - // using the dime randomly prints HEADS or TAILS - onUse: () => { - const side = Math.random() > 0.5 ? 'HEADS' : 'TAILS'; - println(`You flip the dime. It lands on ${side}.`); - }, - } - ], - // places the player can go from this room - exits: [ - // GO NORTH command leads to the Reception Desk - {dir: 'north', id: 'reception'}, - ], - }, - { - id: 'reception', - name: 'Reception Desk', - desc: `**BENJI** is here. I'm sure he'd be happy to tell you more about the features available in **text-engine**. + Type **LOOK** to have a look around.`, + // optional callback when player issues the LOOK command + // here, we use it to change the foyer's description + onLook: () => { + const room = getRoom('foyer'); + room.desc = `You are currently standing in the foyer. There's a huge **MONSTERA** plant to your right, and a massive **WINDOW** to your left bathing the room in natural light. Both the **PLANT** and the **WINDOW** stretch to the ceiling, which must be at least 25 feet high. - *You can speak with characters using the **TALK** command.* + ***Rooms** form the foundation of the engine's design. At any given time, your player will be standing in one of the rooms you built for them. These can be literal rooms like the foyer you find yourself in now, or metaphorical rooms like **The End of Time** or **Purgatory**. - To the **EAST** is a closed **DOOR**. + Each room you create should have a description. (That's what you're reading now!) - To the **SOUTH** is the foyer where you started your adventure. + Rooms can have **exits** that take you to other rooms. For instance, to the **NORTH** is the **RECEPTION DESK**. - Next to the **DESK** are **STAIRS** leading **UP**.`, - items: [ - { - name: 'desk', - }, - { - name: 'door', - desc: `There are 4" metal letters nailed to the door. They spell out: "RESEARCH LAB".`, - onUse: () => { - const reception = getRoom('reception'); - const exit = getExit('east', reception.exits); - if (exit.block) { - println(`It's locked.`); - } else { - goDir('east'); - } - }, - }, - { - name: 'gate', - desc: `The guilded gate is blocking the way to the **STAIRS**.`, - }, - { - name: ['stairs', 'staircase'], - desc: `They lead up to a door. If you squint, you can make out the word "ADVANCED" on the door.`, - onUse: () => println(`Try typing GO UPSTAIRS (once you've unlocked the gate).`), - }, - ], - exits: [ - // exits with a BLOCK cannot be used, but print a message instead - {dir: 'east', id: 'lab', block: `The door is locked.`}, - {dir: ['upstairs', 'up'], id: 'advanced', block: `There's a locked GATE blocking your path.`}, - {dir: 'south', id: 'foyer'}, - ], - }, - { - id: 'lab', - name: 'Research Lab', - desc: `There is a **BLUE ROBOT** hovering silently in the center of a white void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) - - To the **WEST** is the door to the Reception Desk.`, - exits: [ - {dir: 'west', id: 'reception'}, - ], - }, - { - id: 'advanced', - name: 'Advanced Research Lab', - desc: `There is a **RED ROBOT** hovering silently in the center of a black void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) - - **DOWNSTAIRS** is the Reception Desk.`, - exits: [ - {dir: ['downstairs', 'down'], id: 'reception'}, - ], + Rooms can also contain **items**. Sometimes the player can **TAKE** or **USE** items. + + Type **ITEMS** to see a list of items in the foyer. Or type **HELP** to see what else you can do!`; }, - ], - characters: [ - { - name: ['Benji', 'Benj', 'receptionist'], - roomId: 'reception', - desc: 'He looks... helpful!', // printed when the player looks at the character - // optional callback, run when the player talks to this character - onTalk: () => println(`"Hi," he says, "How can I help you?"`), - // things the player can discuss with the character - topics: [ - { - option: 'How can I change the visual **STYLE** of the game?', - removeOnRead: true, - // optional callback, run when the player selects this option - onSelected() { - println(`**BENJI** pulls a strange-looking *item* out of a desk drawer. - "Here, take this." he says. "Try typing **USE STYLE-CHANGER**. That should give you some ideas."`) - - // add a special item to the player's inventory - getDisk().inventory.push({ - name: 'style-changer', - desc: `This is a magical item. Type **USE STYLE-CHANGER** to try it out!`, - onUse: () => { - const currentStylesheet = document.getElementById('styles').getAttribute('href'); - const newName = currentStylesheet.includes('modern') ? 'retro' : 'modern'; - println(`You changed the stylesheet to ${newName.toUpperCase()}.css. - Since **text-engine** is built with HTML, you can use Cascading Stylesheets (CSS) to make your game look however you want!`); - selectStylesheet(`styles/${newName}.css`); + // optional list of items in the room + items: [ + { + name: 'tall window', // the item's name + desc: `All you can see are puffy white clouds over a blue sky.`, // description shown when player looks at the item + }, + { + name: ['monstera', 'plant', 'swiss cheese'], // player can refer to this item by any of these names + desc: `Sometimes called a Swiss Cheese plant, no office is complete without one. It has lovely, large leaves. This is the biggest you\'ve ever seen. + + There's **SOMETHING SHINY** in the pot.`, + block: `It's far too large for you to carry.`, // optional reason player cannot pick up this item + // when player looks at the plant, they discover a shiny object which turns out to be a key + onLook: () => { + if (getItemInRoom('shiny', 'foyer') || getItemInInventory('shiny')) { + // the key is already in the pot or the player's inventory + return; + } + + const foyer = getRoom('foyer'); + + // put the silver key in the pot + foyer.items.push({ + name: ['shiny thing', 'something shiny', 'pot'], + onUse: () => { + const room = getRoom(disk.roomId); + if (room.id === 'foyer') { + println(`There's nothing to unlock in the foyer.`); + } else if (room.id === 'reception') { + println(`You unlock the door to the **EAST**!`); + // remove the block + const exit = getExit('east', room.exits); + delete exit.block; + // this item can only be used once + const key = getItemInInventory('shiny'); + key.onUse = () => println(`The lab has already been unlocked.`); + } else { + println(`There's nothing to unlock here.`); } - }); - }, + }, + desc: `It's a silver **KEY**!`, + onLook: () => { + const key = getItemInInventory('shiny') || getItemInRoom('shiny', 'foyer'); + + // now that we know it's a key, place that name first so the engine calls it by that name + key.name.unshift('silver key'); + + // let's also update the description + key.desc = `It has a blue cap with the word "LAB" printed on it.`; + + // remove this method (we don't need it anymore) + delete key.onLook; + }, + isTakeable: true, + onTake: () => { + println(`You took it.`); + // update the monstera's description, removing everything starting at the line break + const plant = getItemInRoom('plant', 'foyer'); + plant.desc = plant.desc.slice(0, plant.desc.indexOf('\n')); + }, + }); + }, + }, + { + name: 'dime', + desc: `Wow, ten cents.`, + isTakeable: true, // allow the player to TAKE this item + onTake: () => println(`You bend down and pick up the tiny, shiny coin. + + *Now it's in your **inventory**, and you can use it at any time, in any room. (Don't spend it all in one place!) + + Type **INV** to see a list of items in your inventory.*`), + // using the dime randomly prints HEADS or TAILS + onUse: () => { + const side = Math.random() > 0.5 ? 'HEADS' : 'TAILS'; + println(`You flip the dime. It lands on ${side}.`); + }, + } + ], + // places the player can go from this room + exits: [ + // GO NORTH command leads to the Reception Desk + {dir: 'north', id: 'reception'}, + ], + }, + { + id: 'reception', + name: 'Reception Desk', + desc: `**BENJI** is here. I'm sure he'd be happy to tell you more about the features available in **text-engine**. + + *You can speak with characters using the **TALK** command.* + + To the **EAST** is a closed **DOOR**. + + To the **SOUTH** is the foyer where you started your adventure. + + Next to the **DESK** are **STAIRS** leading **UP**.`, + items: [ + { + name: 'desk', + }, + { + name: 'door', + desc: `There are 4" metal letters nailed to the door. They spell out: "RESEARCH LAB".`, + onUse: () => { + const reception = getRoom('reception'); + const exit = getExit('east', reception.exits); + if (exit.block) { + println(`It's locked.`); + } else { + goDir('east'); + } }, - { - option: 'How can I use **RICH** text?', - line: `"The text in the game is actually HTML, so you can use tags like <b> for bold, <i> for italic, and <u> for underline. + }, + { + name: 'gate', + desc: `The guilded gate is blocking the way to the **STAIRS**.`, + }, + { + name: ['stairs', 'staircase'], + desc: `They lead up to a door. If you squint, you can make out the word "ADVANCED" on the door.`, + onUse: () => println(`Try typing GO UPSTAIRS (once you've unlocked the gate).`), + }, + ], + exits: [ + // exits with a BLOCK cannot be used, but print a message instead + {dir: 'east', id: 'lab', block: `The door is locked.`}, + {dir: ['upstairs', 'up'], id: 'advanced', block: `There's a locked GATE blocking your path.`}, + {dir: 'south', id: 'foyer'}, + ], + }, + { + id: 'lab', + name: 'Research Lab', + desc: `There is a **BLUE ROBOT** hovering silently in the center of a white void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) + + To the **WEST** is the door to the Reception Desk.`, + exits: [ + {dir: 'west', id: 'reception'}, + ], + }, + { + id: 'advanced', + name: 'Advanced Research Lab', + desc: `There is a **RED ROBOT** hovering silently in the center of a black void. They appear to be awaiting instructions. (Type **TALK** to speak to the robot.) + + **DOWNSTAIRS** is the Reception Desk.`, + exits: [ + {dir: ['downstairs', 'down'], id: 'reception'}, + ], + }, + ], + characters: [ + { + name: ['Benji', 'Benj', 'receptionist'], + roomId: 'reception', + desc: 'He looks... helpful!', // printed when the player looks at the character + // optional callback, run when the player talks to this character + onTalk: () => println(`"Hi," he says, "How can I help you?"`), + // things the player can discuss with the character + topics: [ + { + option: 'How can I change the visual **STYLE** of the game?', + removeOnRead: true, + // optional callback, run when the player selects this option + onSelected() { + println(`**BENJI** pulls a strange-looking *item* out of a desk drawer. + "Here, take this." he says. "Try typing **USE STYLE-CHANGER**. That should give you some ideas."`) + + // add a special item to the player's inventory + disk.inventory.push({ + name: 'style-changer', + desc: `This is a magical item. Type **USE STYLE-CHANGER** to try it out!`, + onUse: () => { + const currentStylesheet = document.getElementById('styles').getAttribute('href'); + const newName = currentStylesheet.includes('modern') ? 'retro' : 'modern'; + println(`You changed the stylesheet to ${newName.toUpperCase()}.css. + Since **text-engine** is built with HTML, you can use Cascading Stylesheets (CSS) to make your game look however you want!`); + selectStylesheet(`styles/${newName}.css`); + } + }); + }, + }, + { + option: 'How can I use **RICH** text?', + line: `"The text in the game is actually HTML, so you can use tags like <b> for bold, <i> for italic, and <u> for underline. - "There's also support for Markdown-like syntax: + "There's also support for Markdown-like syntax: - • Wrapping some text in asterisks like *this* will *italicize* it. - • Double-asterisks like **this** will make it **bold**. - • Triple-asterisks like ***this*** will make it ***italic bold***. - • Double-underscores like __this__ will __underline__ it."`, - removeOnRead: true, - }, - { - option: `Tell me about **EXITS**`, - // text printed when the player selects this option by typing the keyword (EXITS) - line: `"Sure! It looks like you've already figured out you can type **GO NORTH** to use an exit to the north. But did you know you can just type **GO** to get a list of exits from the room? If an exit leads you to a room you've been to before, it will even tell you the room's name. + • Wrapping some text in asterisks like *this* will *italicize* it. + • Double-asterisks like **this** will make it **bold**. + • Triple-asterisks like ***this*** will make it ***italic bold***. + • Double-underscores like __this__ will __underline__ it."`, + removeOnRead: true, + }, + { + option: `Tell me about **EXITS**`, + // text printed when the player selects this option by typing the keyword (EXITS) + line: `"Sure! It looks like you've already figured out you can type **GO NORTH** to use an exit to the north. But did you know you can just type **GO** to get a list of exits from the room? If an exit leads you to a room you've been to before, it will even tell you the room's name. - "There are also some shortcuts to make getting where you're going easier. Instead of typing **GO NORTH**, you can just type **NORTH** instead. Actually, for cardinal directions, you can shorten it to simply **N**. + "There are also some shortcuts to make getting where you're going easier. Instead of typing **GO NORTH**, you can just type **NORTH** instead. Actually, for cardinal directions, you can shorten it to simply **N**. - "Sometimes you'll want to temporarily prevent players from using an **exit**. You can use *blocks* for this. Try going **EAST** from here to see what I mean. You'll find the **DOOR** is locked. You'll need to find the **KEY** to get inside. + "Sometimes you'll want to temporarily prevent players from using an **exit**. You can use *blocks* for this. Try going **EAST** from here to see what I mean. You'll find the **DOOR** is locked. You'll need to find the **KEY** to get inside. - "These **STAIRS** are also blocked by a locked **GATE**. There isn't a key to the gate, so if you want to see what's up there, you'll have to find another way to get past it."`, - // instruct the engine to remove this option once the player has selected it - removeOnRead: true, - }, - { - option: `Remind me what's up with that **DOOR** to the east...`, - line: `"The exit has a *block*. Specifically, the **DOOR** it locked. You'll need to find a **KEY** to open it."`, - prereqs: ['exits'], // optional list of prerequisite topics that must be discussed before this option is available - }, - { - option: `Remind me what's up with these **STAIRS**...`, - line: `"The **STAIRS** are blocked by a locked **GATE**. There isn't a key, so you need to find another way to unlock it."`, - prereqs: ['exits'], - }, - { - option: `How do I use **AUTOCOMPLETE**?`, - line: `"If you type a few letters and press TAB, the engine will guess what you're trying to say."`, - removeOnRead: true, - }, - { - option: `If I want to **REPEAT** a command, do I have to type it again?`, - line: `"Wow, it's almost like you're reading my mind. No, you can just press the UP ARROW to see commands you've previously entered."`, - removeOnRead: true, - }, - ], - }, - { - name: 'blue robot', - roomId: 'lab', - onTalk: () => println(`"I can tell you about making games with text-engine," they explain. "What would you like to know?"`), - topics: [ - { - option: `What is **TEXT-ENGINE**?`, - line: `**text-engine** is a a JavaScript REPL-style, text-based adventure game engine. It's small and easy to use with no dependencies. - - The engine uses a disk metaphor for the data which represents your game, like the floppy disks of yore.` - }, - { - option: `How do I get **STARTED**?`, - line: `To create your own adventure, you can use an existing game disk as a template. You will find the disk you're playing now as well as others in the folder called "game-disks". You can edit these in any text or code editor. + "These **STAIRS** are also blocked by a locked **GATE**. There isn't a key to the gate, so if you want to see what's up there, you'll have to find another way to get past it."`, + // instruct the engine to remove this option once the player has selected it + removeOnRead: true, + }, + { + option: `Remind me what's up with that **DOOR** to the east...`, + line: `"The exit has a *block*. Specifically, the **DOOR** it locked. You'll need to find a **KEY** to open it."`, + prereqs: ['exits'], // optional list of prerequisite topics that must be discussed before this option is available + }, + { + option: `Remind me what's up with these **STAIRS**...`, + line: `"The **STAIRS** are blocked by a locked **GATE**. There isn't a key, so you need to find another way to unlock it."`, + prereqs: ['exits'], + }, + { + option: `How do I use **AUTOCOMPLETE**?`, + line: `"If you type a few letters and press TAB, the engine will guess what you're trying to say."`, + removeOnRead: true, + }, + { + option: `If I want to **REPEAT** a command, do I have to type it again?`, + line: `"Wow, it's almost like you're reading my mind. No, you can just press the UP ARROW to see commands you've previously entered."`, + removeOnRead: true, + }, + ], + }, + { + name: 'blue robot', + roomId: 'lab', + onTalk: () => println(`"I can tell you about making games with text-engine," they explain. "What would you like to know?"`), + topics: [ + { + option: `What is **TEXT-ENGINE**?`, + line: `**text-engine** is a a JavaScript REPL-style, text-based adventure game engine. It's small and easy to use with no dependencies. + + The engine uses a disk metaphor for the data which represents your game, like the floppy disks of yore.` + }, + { + option: `How do I get **STARTED**?`, + line: `To create your own adventure, you can use an existing game disk as a template. You will find the disk you're playing now as well as others in the folder called "game-disks". You can edit these in any text or code editor. - Include the 'game disk' (JSON data) in index.html and load it with loadDisk(myGameData). You can look at the included index.html file for an example. + Include the 'game disk' (JSON data) in index.html and load it with loadDisk(myGameData). You can look at the included index.html file for an example. - You are welcome to ask me about any topic you like, but you might find it easier just to browse a few and then dive in to making something of your own. You can return to ask me questions at any time.` - }, - { - option: `What is a **DISK**?`, - line: `A disk is a JavaScript object which describes your game. At minimum, it must have these two top-level properties: + You are welcome to ask me about any topic you like, but you might find it easier just to browse a few and then dive in to making something of your own. You can return to ask me questions at any time.` + }, + { + option: `What is a **DISK**?`, + line: `A disk is a JavaScript object which describes your game. At minimum, it must have these two top-level properties: - **roomId** (*string*) - This is a reference to the room the player currently occupies. Set this to the **ID** of the room the player should start in. + **roomId** (*string*) - This is a reference to the room the player currently occupies. Set this to the **ID** of the room the player should start in. - **rooms** (*array*) - List of rooms in the game. + **rooms** (*array*) - List of rooms in the game. - There are other properties you can choose to include if you like: + There are other properties you can choose to include if you like: - **inventory** (*array*) - List of items in the player's inventory. + **inventory** (*array*) - List of items in the player's inventory. - **characters** (*array*) - List of characters in the game. + **characters** (*array*) - List of characters in the game. - You can also attach any arbitrary data you wish. For instance, you could have a number called "health" that you use to keep track of your player's condition.` - }, - { - option: `What is a **ROOM**?`, - line: `A room is a JavaScript object. You usually want a room to have the following properties: + You can also attach any arbitrary data you wish. For instance, you could have a number called "health" that you use to keep track of your player's condition.` + }, + { + option: `What is a **ROOM**?`, + line: `A room is a JavaScript object. You usually want a room to have the following properties: - **name** (*string*) - The name of the room will be displayed each time it is entered. + **name** (*string*) - The name of the room will be displayed each time it is entered. - **id** (*string*) - Unique identifier for this room. Can be anything. + **id** (*string*) - Unique identifier for this room. Can be anything. - **desc** (*string*) - Description of the room, displayed when it is first entered, and also when the player issues the **LOOK** command. + **desc** (*string*) - Description of the room, displayed when it is first entered, and also when the player issues the **LOOK** command. - **exits** (*array*) - List of paths from this room. + **exits** (*array*) - List of paths from this room. - Rooms can have these other optional properties as well: + Rooms can have these other optional properties as well: - **img** (*string*) - Graphic to be displayed each time the room is entered. (This is intended to be ASCII art.) + **img** (*string*) - Graphic to be displayed each time the room is entered. (This is intended to be ASCII art.) - **items** (*string*) - List of items in this room. Items can be interacted with by the player. + **items** (*string*) - List of items in this room. Items can be interacted with by the player. - **onEnter** (*function*) - Function to be called when the player enters this room. + **onEnter** (*function*) - Function to be called when the player enters this room. - **onLook** (*function*) - Function to be called when the player issues the **LOOK** command in this room.` - }, - { - option: `What is an **EXIT**?`, - line: `An exit is a JavaScript object with the following properties: + **onLook** (*function*) - Function to be called when the player issues the **LOOK** command in this room.` + }, + { + option: `What is an **EXIT**?`, + line: `An exit is a JavaScript object with the following properties: - **dir** (*string*) - The direction the player must go to leave via this exit (e.g. "north"). + **dir** (*string*) - The direction the player must go to leave via this exit (e.g. "north"). - **id** (*string*) - The ID of the room this exit leads to. + **id** (*string*) - The ID of the room this exit leads to. - An exit can optionally have a *block* as well: + An exit can optionally have a *block* as well: - **block** (*string*) - Line to be printed if the player tries to use this exit. If this property exists, the player cannot use the exit.` - }, - { - option: `What is an **ITEM**?`, - line: `An item is a JavaScript object with a name: + **block** (*string*) - Line to be printed if the player tries to use this exit. If this property exists, the player cannot use the exit.` + }, + { + option: `What is an **ITEM**?`, + line: `An item is a JavaScript object with a name: - **name** (*string* or *array*) - How the item is referred to by the game and the player. Using an array allows you to define multiple string names for the item. You might do this if you expect the player may call it by more than one name. For instance ['basketball', 'ball']. When listing items in a room, the engine will always use the first name in the list. + **name** (*string* or *array*) - How the item is referred to by the game and the player. Using an array allows you to define multiple string names for the item. You might do this if you expect the player may call it by more than one name. For instance ['basketball', 'ball']. When listing items in a room, the engine will always use the first name in the list. - Items can have these other optional properties as well: + Items can have these other optional properties as well: - **desc** (*string* or *array*) - Description. Text displayed when the player looks at the item. If multiple descriptions are provided, one will be chosen at random. + **desc** (*string* or *array*) - Description. Text displayed when the player looks at the item. If multiple descriptions are provided, one will be chosen at random. - **isTakeable** (*boolean*) - Whether the player can pick up this item (if it's in a room). Defaults to false. + **isTakeable** (*boolean*) - Whether the player can pick up this item (if it's in a room). Defaults to false. - **onUse** (*function*) - Function to be called when the player uses the item. + **onUse** (*function*) - Function to be called when the player uses the item. - **onLook** (*function*) - Function to be called when the player looks at the item. + **onLook** (*function*) - Function to be called when the player looks at the item. - **onTake** (*function*) - Function to be called when the player takes the item.` - }, - { - option: `What is a **CHARACTER**?`, - line: `You're talking to one! A character is a JavaScript object with the following properties: + **onTake** (*function*) - Function to be called when the player takes the item.` + }, + { + option: `What is a **CHARACTER**?`, + line: `You're talking to one! A character is a JavaScript object with the following properties: - **name** (*string or array*) - How the character is referred to by the game and the player. Using an array allows you to define multiple string names for the character. You might do this if you expect the player may call them by more than one name. For instance ['Steve', 'waiter', 'garçon']. When listing characters in a room, the engine will always use the first name in the list. + **name** (*string or array*) - How the character is referred to by the game and the player. Using an array allows you to define multiple string names for the character. You might do this if you expect the player may call them by more than one name. For instance ['Steve', 'waiter', 'garçon']. When listing characters in a room, the engine will always use the first name in the list. - **roomId** (*string*) - The ID of the room the character is currently in. The player can only talk to characters in the room with them. + **roomId** (*string*) - The ID of the room the character is currently in. The player can only talk to characters in the room with them. - Characters can have these other optional properties as well: + Characters can have these other optional properties as well: - **desc** (*string* or *array*) - Description. Text displayed when the player looks at the character. If multiple descriptions are provided, one will be chosen at random. + **desc** (*string* or *array*) - Description. Text displayed when the player looks at the character. If multiple descriptions are provided, one will be chosen at random. - **topics** (*string* or *array*) - If a string is provided, it will be printed when the player talks to this character. Otherwise, this should be a list of topics for use in the conversation with the character. + **topics** (*string* or *array*) - If a string is provided, it will be printed when the player talks to this character. Otherwise, this should be a list of topics for use in the conversation with the character. - **onTalk** (*function*) - Function to be called when the player talks to the character. + **onTalk** (*function*) - Function to be called when the player talks to the character. - **onLook** (*function*) - Function to be called when the player looks at the character.` - }, - { - option: `What is a **TOPIC**?`, - line: `A topic is something you can talk to a character about, and as you may have guessed, is a JavaScript object. A topic requires an *option*, and either a *line* or an *onSelected* function, or both: + **onLook** (*function*) - Function to be called when the player looks at the character.` + }, + { + option: `What is a **TOPIC**?`, + line: `A topic is something you can talk to a character about, and as you may have guessed, is a JavaScript object. A topic requires an *option*, and either a *line* or an *onSelected* function, or both: - **option** (*string*) - The choice presented to the player, with a KEYWORD the player can type to select it. If the keyword is written in uppercase, the engine can identify it automatically. (Otherwise, you'll need to specify the keyword in a separate property.) The option can be just the keyword itself, or any string containing the keyword. + **option** (*string*) - The choice presented to the player, with a KEYWORD the player can type to select it. If the keyword is written in uppercase, the engine can identify it automatically. (Otherwise, you'll need to specify the keyword in a separate property.) The option can be just the keyword itself, or any string containing the keyword. - **line** (*string*) - The text to display when the player types the keyword to select the option. + **line** (*string*) - The text to display when the player types the keyword to select the option. - **onSelected** (*function*) - Function to be called when the player types the keyword to select the option. + **onSelected** (*function*) - Function to be called when the player types the keyword to select the option. - Topics can have these other optional properties as well: + Topics can have these other optional properties as well: - **removeOnRead** (*boolean*) - Whether this option should no longer be available to the player after it has been selected once. + **removeOnRead** (*boolean*) - Whether this option should no longer be available to the player after it has been selected once. - **prereqs** (*array*) - Array of keyword strings representing the prerequisite topics a player must have selected before this one will appear. (When topics are selected, their keywords go into an array on the character called "chatLog".) + **prereqs** (*array*) - Array of keyword strings representing the prerequisite topics a player must have selected before this one will appear. (When topics are selected, their keywords go into an array on the character called "chatLog".) - **keyword** (*string*) - The word the player must type to select this option. This property is only required if the option itself does not contain a keyword written in uppercase.` - }, - { - option: `Can you unlock the **GATE** to the stairs by the reception desk?`, - line: `Actually, you can do that yourself! This disk happens to have a secret, custom **UNLOCK** command. This powerful command will remove blocks on any exit. Just type **UNLOCK** to use it.`, - }, - ], - }, - { - name: 'red robot', - roomId: 'advanced', - onTalk: () => println(`"I can tell you about the JavaScript functions available to you when you use text-engine," they explain. "What would you like to know about?"`), - topics: [ - { - option: `Tell me about **FUNCTIONS**`, - line: `Functions are reuseable bits of JavaScript code. **text-engine** provides several of these which you can use, for instance in callbacks like onUse, onLook, onEnter, etc.` - }, - { - option: `Tell me about **COMMANDS**`, - line: `Every command a player can issue in the game has a corresponding function in **text-engine**. + **keyword** (*string*) - The word the player must type to select this option. This property is only required if the option itself does not contain a keyword written in uppercase.` + }, + { + option: `Can you unlock the **GATE** to the stairs by the reception desk?`, + line: `Actually, you can do that yourself! This disk happens to have a secret, custom **UNLOCK** command. This powerful command will remove blocks on any exit. Just type **UNLOCK** to use it.`, + }, + ], + }, + { + name: 'red robot', + roomId: 'advanced', + onTalk: () => println(`"I can tell you about the JavaScript functions available to you when you use text-engine," they explain. "What would you like to know about?"`), + topics: [ + { + option: `Tell me about **FUNCTIONS**`, + line: `Functions are reuseable bits of JavaScript code. **text-engine** provides several of these which you can use, for instance in callbacks like onUse, onLook, onEnter, etc.` + }, + { + option: `Tell me about **COMMANDS**`, + line: `Every command a player can issue in the game has a corresponding function in **text-engine**. - For instance, there's a function called go that gets called when the player types **GO**. + For instance, there's a function called go that gets called when the player types **GO**. - You can add your own custom commands, like the **UNLOCK** command you used to get access to this room. And if existing commands don't work how you want them to, you can ever override them by reassigning them to your own function code.`, - }, - { - option: `Tell me about **PRINTLN**`, - line: `println is a function you can use to print a line of text to the console. It takes up to two arguments: + You can add your own custom commands, like the **UNLOCK** command you used to get access to this room. And if existing commands don't work how you want them to, you can ever override them by reassigning them to your own function code.`, + }, + { + option: `Tell me about **PRINTLN**`, + line: `println is a function you can use to print a line of text to the console. It takes up to two arguments: - **line** (*string*) - The text to be printed. + **line** (*string*) - The text to be printed. - **className** (*string*) - Optional. The name of a CSS class to apply to the line. You can use this to style the text.` - }, - { - option: `Tell me about **PICKONE**`, - line: `pickOne is a function you can use to get a random item from an array. It takes one argument: + **className** (*string*) - Optional. The name of a CSS class to apply to the line. You can use this to style the text.` + }, + { + option: `Tell me about **PICKONE**`, + line: `pickOne is a function you can use to get a random item from an array. It takes one argument: - **arr** (*array*) - The array with the items to pick from.` - }, - { - option: `Tell me about **GETROOM**`, - line: `getRoom is a function you can use to get a reference to a room by its ID. It takes one argument: + **arr** (*array*) - The array with the items to pick from.` + }, + { + option: `Tell me about **GETROOM**`, + line: `getRoom is a function you can use to get a reference to a room by its ID. It takes one argument: - **id** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **ENTERROOM**`, - line: `enterRoom is a function you can use to move the player to particular room. It takes one argument: + **id** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **ENTERROOM**`, + line: `enterRoom is a function you can use to move the player to particular room. It takes one argument: - **id** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETCHARACTER**`, - line: `getCharacter is a function you can use to get a reference to a character. It takes up to two arguments: + **id** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETCHARACTER**`, + line: `getCharacter is a function you can use to get a reference to a character. It takes up to two arguments: - **name** (*string*) - The character's name. + **name** (*string*) - The character's name. - **chars** (*array*) - Optional. The array of characters to search. Defaults to searching all characters on the disk.` - }, - { - option: `Tell me about **GETCHARACTERSINROOM**`, - line: `getCharactersInRoom is a function you can use to get an array containing references to each character in a particular room. It takes one argument: + **chars** (*array*) - Optional. The array of characters to search. Defaults to searching all characters on the disk.` + }, + { + option: `Tell me about **GETCHARACTERSINROOM**`, + line: `getCharactersInRoom is a function you can use to get an array containing references to each character in a particular room. It takes one argument: - **roomId** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETITEMINROOM**`, - line: `getItemInRoom is a function you can use to get a reference to an item in a particular room. It takes two arguments: + **roomId** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETITEMINROOM**`, + line: `getItemInRoom is a function you can use to get a reference to an item in a particular room. It takes two arguments: - **itemName** (*string*) - The name of the item. + **itemName** (*string*) - The name of the item. - **roomId** (*string*) - The unique identifier for the room.` - }, - { - option: `Tell me about **GETITEMININVENTORY**`, - line: `getItemInInventory is a function you can use to get a reference to an item in the player's inventory. It takes one argument: + **roomId** (*string*) - The unique identifier for the room.` + }, + { + option: `Tell me about **GETITEMININVENTORY**`, + line: `getItemInInventory is a function you can use to get a reference to an item in the player's inventory. It takes one argument: - **name** (*string*) - The name of the item.` - }, - { - option: `Tell me about **OTHER** functions`, - line: `There are several other functions available in the engine! Feel free to take a peek at the source code (index.js). It's designed to be open and simple to use and to customize.` - }, - ], - }, - ], - }; - - // custom functions used by this disk - // change the CSS stylesheet to the one with the passed name - const selectStylesheet = filename => document.getElementById('styles').setAttribute('href', filename); - - // override commands to include custom UNLOCK command - // create the unlock function - const unlock = () => { - getDisk().rooms.forEach(room => { - if (!room.exits) { - return; - } - - // unblock all blocked exits in the room - room.exits.forEach(exit => delete exit.block); - }); - - // update the description of the gate - getItemInRoom('gate', 'reception').desc = `The guilded gate leads to the staircase.`; - - println(`All **exits** have been unblocked!`); - }; - - // attach it to the zero-argument commands object on the disk - commands[0] = Object.assign(commands[0], {unlock}); - - return disk; + **name** (*string*) - The name of the item.` + }, + { + option: `Tell me about **OTHER** functions`, + line: `There are several other functions available in the engine! Feel free to take a peek at the source code (index.js). It's designed to be open and simple to use and to customize.` + }, + ], + }, + ], +}); + +// custom functions used by this disk +// change the CSS stylesheet to the one with the passed name +const selectStylesheet = filename => document.getElementById('styles').setAttribute('href', filename); + +// override commands to include custom UNLOCK command +// create the unlock function +const unlock = () => { + disk.rooms.forEach(room => { + if (!room.exits) { + return; + } + + // unblock all blocked exits in the room + room.exits.forEach(exit => delete exit.block); + }); + + // update the description of the gate + getItemInRoom('gate', 'reception').desc = `The guilded gate leads to the staircase.`; + + println(`All **exits** have been unblocked!`); }; + +// attach it to the zero-argument commands object on the disk +commands[0] = Object.assign(commands[0], {unlock}); diff --git a/index.js b/index.js index d9f4019..a5f1705 100644 --- a/index.js +++ b/index.js @@ -1066,10 +1066,6 @@ let loadDisk = (uninitializedDisk) => { input.focus(); }; -// get the active disk from memory -// nothing -> disk -let getDisk = () => disk; - // npm support if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = loadDisk; From 92d9e9f949aa6c50146d1ec85807723c5216d22a Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 15:29:49 -0500 Subject: [PATCH 12/46] remove screen creatures disk --- game-disks/screen-creatures.js | 79 ---------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 game-disks/screen-creatures.js diff --git a/game-disks/screen-creatures.js b/game-disks/screen-creatures.js deleted file mode 100644 index 70936df..0000000 --- a/game-disks/screen-creatures.js +++ /dev/null @@ -1,79 +0,0 @@ -// a game by okaybenji & 23dogsinatrenchcoat -// inspired by the famicase of the same name: http://famicase.com/20/softs/080.html - -const $ = query => document.querySelector(query); -const $$ = query => document.querySelectorAll(query); -const randomIntBetween = (min, max) => Math.floor(Math.random() * (max - min)) + min; -const randomColor = () => { - const minBrightness = 33; - const maxBrightness = 67; - const minSaturation = 75; - - let color = 'hsl('; - color += randomIntBetween(0, 360) + ','; - color += randomIntBetween(minSaturation, 100) + '%,'; - color += randomIntBetween(minBrightness, maxBrightness) + '%)'; - return color; -}; - -const screenCreatures = { - roomId: 'start', - rooms: [ - { - name: 'Living Room', - id: 'start', - img: ` - - - o o - \\ | - \\.|-. - (\\| ) - .==================. - | .--------------. | - | |--.__.--.__.--| | - | |--.__.--.__.--| | - | |--.__.--.__.--| | - | |--.__.--.__.--| | - | |--.__.--.__.--| | - | '--------------'o| - | """""""" o| - '==================' - `, - desc: `You feel the glow of the television washing over you. You haven't yet dared to look directly at the screen.`, - items: [ - { - name: ['tv', 'television', 'screen'], - desc: [ - `You feel something tug at you from the inside of your chest as you look at the screen. You feel an invisible hand pass though your skin as if you were clay and wrap its cold fingers around your lungs. You can’t breathe. -The screen is filled with so many colors, swirling around in maddening patterns with no rhyme or reason. With each second you stare, it becomes harder and harder to look away. You swear you can see another pair of eyes looking back at you.`, - `The colors begin to pour out of the screen until they envelop you like a cocoon. You feel the colors press against you, wrapping themselves around you like a second skin. The colors are suddenly seared away by a blinding white light. Yet still you see those eyes, wide and unblinking, seared on to the back of your eyelids. The light dissipates, and the world begins to come back into focus. -You dare not close your eyes, for you know that if you do, it will be staring back at you.`], - look({getRoom}) { - // Get the TV ASCII art. - const tv = $('.img'); - // Reset the innerHTML so this will work each time the player looks at the TV. - tv.innerHTML = getRoom('start').img; - // Add the scanline class to each line on the TV screen. - tv.innerHTML = tv.innerHTML.replaceAll('--.__.--.__.--', `--.__.--.__.--`); - // Set each element of the screen to a random color. - const scanlines = $$('.scanline') - const colorElements = char => { - scanlines.forEach(scanline => scanline.innerHTML = scanline.innerHTML.replaceAll(char, `${char}`)); - }; - ['-', '.', '_'].forEach(colorElements); - $$('.randomColor').forEach(e => e.style = `color: ${randomColor()}`); - // Oscillate the waves. - $$('.randomColor').forEach(e => { - if (e.innerText === '_') { - e.classList.add('oscillateUp'); - } else if (e.innerText === '-') { - e.classList.add('oscillateDown'); - } - }); - }, - }, - ], - }, - ], -}; From e6e4cf9ecd0c3720127e837970ed5f3ffc42dd8a Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 15:31:46 -0500 Subject: [PATCH 13/46] convert unlimited adventure to a disk factory --- game-disks/unlimited-adventure.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/game-disks/unlimited-adventure.js b/game-disks/unlimited-adventure.js index 04f7b3d..7962396 100644 --- a/game-disks/unlimited-adventure.js +++ b/game-disks/unlimited-adventure.js @@ -15,7 +15,7 @@ commands[0].help = help; // switch to the retro style document.getElementById('styles').setAttribute('href', 'styles/retro.css'); -const unlimitedAdventure = { +const unlimitedAdventure = () => ({ roomId: 'gameOver', // The room the player is currently in. Set this to the room you want the player to start in. inventory: [], // You can add any items you want the player to start with here. rooms: [ @@ -122,4 +122,4 @@ WWWWW/\\| / \\|'/\\|/"\\ `, }, ], -}; +}); From 4226facc7334c3a9426e2f0823c24407ee11923e Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 16:56:45 -0500 Subject: [PATCH 14/46] update demo disk & ur dead to use getItem function --- game-disks/demo-disk.js | 16 +++++++++++----- game-disks/ur-dead.js | 14 +++++++------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/game-disks/demo-disk.js b/game-disks/demo-disk.js index 05c68f5..7b73d14 100644 --- a/game-disks/demo-disk.js +++ b/game-disks/demo-disk.js @@ -38,7 +38,7 @@ const demoDisk = () => ({ block: `It's far too large for you to carry.`, // optional reason player cannot pick up this item // when player looks at the plant, they discover a shiny object which turns out to be a key onLook: () => { - if (getItemInRoom('shiny', 'foyer') || getItemInInventory('shiny')) { + if (getItem('shiny')) { // the key is already in the pot or the player's inventory return; } @@ -58,7 +58,7 @@ const demoDisk = () => ({ const exit = getExit('east', room.exits); delete exit.block; // this item can only be used once - const key = getItemInInventory('shiny'); + const key = getItem('shiny'); key.onUse = () => println(`The lab has already been unlocked.`); } else { println(`There's nothing to unlock here.`); @@ -66,7 +66,7 @@ const demoDisk = () => ({ }, desc: `It's a silver **KEY**!`, onLook: () => { - const key = getItemInInventory('shiny') || getItemInRoom('shiny', 'foyer'); + const key = getItem('shiny'); // now that we know it's a key, place that name first so the engine calls it by that name key.name.unshift('silver key'); @@ -81,7 +81,7 @@ const demoDisk = () => ({ onTake: () => { println(`You took it.`); // update the monstera's description, removing everything starting at the line break - const plant = getItemInRoom('plant', 'foyer'); + const plant = getItem('plant'); plant.desc = plant.desc.slice(0, plant.desc.indexOf('\n')); }, }); @@ -442,7 +442,7 @@ const demoDisk = () => ({ }, { option: `Tell me about **GETITEMINROOM**`, - line: `getItemInRoom is a function you can use to get a reference to an item in a particular room. It takes two arguments: + line: `getItemInRoom is a function you can use to get a reference to an item in any room. It takes two arguments: **itemName** (*string*) - The name of the item. @@ -454,6 +454,12 @@ const demoDisk = () => ({ **name** (*string*) - The name of the item.` }, + { + option: `Tell me about **GETITEM**`, + line: `getItem is a function you can use to get a reference to an item in the player's inventory or in the current room. It takes one argument: + + **name** (*string*) - The name of the item.` + }, { option: `Tell me about **OTHER** functions`, line: `There are several other functions available in the engine! Feel free to take a peek at the source code (index.js). It's designed to be open and simple to use and to customize.` diff --git a/game-disks/ur-dead.js b/game-disks/ur-dead.js index 95f36f4..1ceaee5 100644 --- a/game-disks/ur-dead.js +++ b/game-disks/ur-dead.js @@ -209,7 +209,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, delete exit.block; - getItemInRoom('store', 'parkingLot').desc = `It's open for business. Let's make it a Blockbuster night.`; + getItem('store').desc = `It's open for business. Let's make it a Blockbuster night.`; }, }, { @@ -277,7 +277,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, isHidden: true, onTake() { // if the player doesn't have the key and the door is locked, show a message - if (!getItemInInventory('key') && getItemInRoom('door', 'yard').isLocked) { + if (!getItem('key') && getItem('door').isLocked) { println(`There's no key under it, if that's what you're thinking. This is its home. Better leave it be.`); } else { println(`This is its home. Better leave it be.`); @@ -297,7 +297,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, onUse({item}) { if (item.isLocked) { // if the player has the key, unlock the door and enter the room - const key = getItemInInventory('key'); + const key = getItem('key'); if (key) { key.onUse(); } else { @@ -355,7 +355,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, 'Romancing the Stone': () => { println(`You hit PLAY and watch the cassette. You see trailers for *Rhinestone*, *Give My Regards to Broad Street*, and *Muppets Take Manhattan*, and finally, our feature presentation, *Romancing the Stone*. The movie is... fine.`); - const videoCase = getItemInRoom('case', 'livingRoom'); + const videoCase = getItem('case'); if (videoCase.wasSeen) { println(`You comply with the case's instructions to BE KIND and REWIND.`); } @@ -395,7 +395,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, name: 'Blockbuster video case', desc: `The case is empty. Looks like it once held *Romancing the Stone*. A sticker says BE KIND, REWIND.`, onLook() { - const videoCase = getItemInRoom('case', 'livingRoom'); + const videoCase = getItem('case'); videoCase.wasSeen = true; }, onTake() { @@ -492,7 +492,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, name: 'key', desc: `It's not a skeleton key, but it is a skeleton's key. Dirk's, to be specific.`, onUse() { - const door = getItemInRoom('door', 'yard'); + const door = getItem('door'); if (disk.roomId === 'yard') { delete door.isLocked; println(`You use Dirk's key to open the door, placing it under the fake rock before entering into the living room.`); @@ -500,7 +500,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, // leave the door unlocked getRoom('yard').exits.push({dir: ['south', 'in', 'inside'], id: 'livingRoom'}); // remove the key from inventory - const key = getItemInInventory('key'); + const key = getItem('key'); const itemIndex = disk.inventory.findIndex(i => i === key); disk.inventory.splice(itemIndex, 1); } else { From 387144f519549b3ff3c6778881e71f055d692a36 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 17:14:13 -0500 Subject: [PATCH 15/46] disks use shorthand syntax to define methods where appropriate --- game-disks/demo-disk.js | 10 +++++----- game-disks/new-disk-template.js | 2 +- game-disks/unlimited-adventure.js | 8 ++++---- game-disks/ur-dead.js | 30 +++++++++++++++--------------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/game-disks/demo-disk.js b/game-disks/demo-disk.js index 7b73d14..a103d46 100644 --- a/game-disks/demo-disk.js +++ b/game-disks/demo-disk.js @@ -48,7 +48,7 @@ const demoDisk = () => ({ // put the silver key in the pot foyer.items.push({ name: ['shiny thing', 'something shiny', 'pot'], - onUse: () => { + onUse() { const room = getRoom(disk.roomId); if (room.id === 'foyer') { println(`There's nothing to unlock in the foyer.`); @@ -65,7 +65,7 @@ const demoDisk = () => ({ } }, desc: `It's a silver **KEY**!`, - onLook: () => { + onLook() { const key = getItem('shiny'); // now that we know it's a key, place that name first so the engine calls it by that name @@ -78,7 +78,7 @@ const demoDisk = () => ({ delete key.onLook; }, isTakeable: true, - onTake: () => { + onTake() { println(`You took it.`); // update the monstera's description, removing everything starting at the line break const plant = getItem('plant'); @@ -97,7 +97,7 @@ const demoDisk = () => ({ Type **INV** to see a list of items in your inventory.*`), // using the dime randomly prints HEADS or TAILS - onUse: () => { + onUse() { const side = Math.random() > 0.5 ? 'HEADS' : 'TAILS'; println(`You flip the dime. It lands on ${side}.`); }, @@ -128,7 +128,7 @@ const demoDisk = () => ({ { name: 'door', desc: `There are 4" metal letters nailed to the door. They spell out: "RESEARCH LAB".`, - onUse: () => { + onUse() { const reception = getRoom('reception'); const exit = getExit('east', reception.exits); if (exit.block) { diff --git a/game-disks/new-disk-template.js b/game-disks/new-disk-template.js index e158078..1d4b137 100644 --- a/game-disks/new-disk-template.js +++ b/game-disks/new-disk-template.js @@ -21,7 +21,7 @@ const newDiskTemplate = () => ({ name: 'axe', desc: `You could probably USE it to cut the VINES, unblocking the door.`, isTakeable: true, // Allows the player to take the item. - onUse: () => { + onUse() { // Remove the block on the room's only exit. const room = getRoom('start'); const exit = getExit('north', room.exits); diff --git a/game-disks/unlimited-adventure.js b/game-disks/unlimited-adventure.js index 7962396..d697dcf 100644 --- a/game-disks/unlimited-adventure.js +++ b/game-disks/unlimited-adventure.js @@ -75,11 +75,11 @@ WWWWW/\\| / \\|'/\\|/"\\ `, // This is just here as an example of how you can use the onEnter property. // This gets called when the player enters the room. - onEnter: ({disk, println, getRoom}) => { + onEnter({disk, println, getRoom}) { console.log('Entered', disk.roomId); // Logs "Entered endOfTheWorld" }, items: [ - { name: 'key', desc: 'It looks like a key.', isTakeable: true, onUse: ({disk, println, getRoom}) => { + { name: 'key', desc: 'It looks like a key.', isTakeable: true, onUse({disk, println, getRoom}) { // This method gets run when the user types "use key". const room = getRoom(disk.roomId); const door = room.items.find(item => item.name === 'door'); @@ -91,7 +91,7 @@ WWWWW/\\| / \\|'/\\|/"\\ println('There\'s nothing to use the key on.'); } }}, - { name: 'book', desc: 'It appears to contain some sort of encantation, or perhaps... code.', isTakeable: true, onUse: ({disk, println, getRoom}) => { + { name: 'book', desc: 'It appears to contain some sort of encantation, or perhaps... code.', isTakeable: true, onUse({disk, println, getRoom}) { const room = getRoom(disk.roomId); const door = room.items.find(item => item.name === 'door'); @@ -101,7 +101,7 @@ WWWWW/\\| / \\|'/\\|/"\\ } println('A door has appeared from nothing! It seems to go nowhere...'); - room.items.push({ name: 'door', desc: 'It seems to go nowhere...', isOpen: false, onUse: ({disk, println, enterRoom}) => { + room.items.push({ name: 'door', desc: 'It seems to go nowhere...', isOpen: false, onUse({disk, println, enterRoom}) { const door = room.items.find(item => item.name === 'door'); if (door.isOpen) { enterRoom('gameReallyOver'); diff --git a/game-disks/ur-dead.js b/game-disks/ur-dead.js index 1ceaee5..a89304e 100644 --- a/game-disks/ur-dead.js +++ b/game-disks/ur-dead.js @@ -8,7 +8,7 @@ const urDead = () => ({ { name: ['to-do list', 'todo list'], desc: `The list contains the following to-do items:`, - onLook: () => { + onLook() { // sort to-do list by done or not, then by id descending const list = disk.todo .sort((a, b) => { @@ -21,7 +21,7 @@ const urDead = () => ({ list.forEach(println); }, - onUse: ({item, disk}) => { + onUse({item, disk}) { // Using the list is the same as looking at it. println(item.desc); item.onLook({disk}); @@ -47,7 +47,7 @@ const urDead = () => ({ || || ==' '== `, - onEnter: () => { + onEnter() { println('ur dead', 'title'); enterRoom('court'); }, @@ -55,7 +55,7 @@ const urDead = () => ({ { name: '🏀 Craggy Half-Court', id: 'court', - onEnter: () => { + onEnter() { const room = getRoom('court'); if (room.visits === 1) { @@ -83,7 +83,7 @@ const urDead = () => ({ name: ['basketball', 'ball'], desc: 'You could really have a ball with that thing.', isTakeable: true, - onTake: ({item, room}) => { + onTake({item, room}) { const skeletons = getCharacter('dirk'); skeletons.topics = skeletons.tookBallTopics; @@ -149,7 +149,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, { name: 'ramp', desc: `Nothing's stopping you from having a good time but you. Type USE RAMP.`, - onUse: () => { + onUse() { enterRoom('deck'); // add exit after player has learned the USE command @@ -248,7 +248,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, { name: '⚓️ Front Yard', id: 'yard', - onEnter () { + onEnter() { const room = getRoom('yard'); if (room.visits === 1) { @@ -431,7 +431,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, }, { option: 'GIVE the ball back', - onSelected: () => { + onSelected() { println(`Feeling a bit bad, you decide to return the ball and move on.`); disk.methods.resetCourt(); if (disk.askedSkeletonNames) { @@ -607,7 +607,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, "Anyhow, I've said my spiel. I'll be here if you need me." You thank him and end the conversation.`, - onSelected: () => { + onSelected() { endConversation(); const skeleton = getCharacter('dave'); @@ -634,7 +634,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, prereqs: ['fran'], removeOnRead: true, line: () => `Oh, I'm Dave. Pleasure to make your acquaintance${disk.playerName ? ', ' + disk.playerName : ''}.`, - onSelected: () => { + onSelected() { // now that we know his name, let's call him by it const dave = getCharacter('dave'); dave.name = ['Dave', 'bearded skeleton']; @@ -681,7 +681,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, option: `WHO are you?`, line: `"I'm Fran. Didn't you see the nametag?"`, removeOnRead: true, - onSelected: () => { + onSelected() { // now that we know her name, let's call her by it const fran = getCharacter('fran'); fran.name = ['Fran', 'skeleton in a red dress']; @@ -838,11 +838,11 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, ], methods: { // cross an item off player's to-do list - crossOff: (id) => { + crossOff(id) { disk.todo.find(item => item.id === id).done = true; }, // reset the state of the basketball court - resetCourt: () => { + resetCourt() { const skeletons = getCharacter('dirk'); skeletons.topics = `They look pretty busy.`; @@ -857,7 +857,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, disk.inventory.splice(itemIndex, 1); }, // check the player's blockbuster membership card - checkCard: () => { + checkCard() { const numberWithCommas = num => num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); // late fees increase with each command; cents are randomized @@ -877,7 +877,7 @@ There's a bearded skeleton by the sign. He seems to want to TALK.`, const customCommands = { play: () => println(`You're already playing a game.`), // set player's name - name: (arg) => { + name(arg) { if (!arg.length) { println(`Type NAME followed by the name you wish to choose.`); return; From 09be17a87d2bf3d737ce8b0ece549267a2dab312 Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 17:48:53 -0500 Subject: [PATCH 16/46] switch index.html back to using demo disk --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index c81aba8..8b473ac 100644 --- a/index.html +++ b/index.html @@ -16,7 +16,7 @@ - + - + From f0e0619fcb1b25f057a7644c8b0c08acb6eb526c Mon Sep 17 00:00:00 2001 From: okaybenji Date: Sun, 3 Jul 2022 17:50:04 -0500 Subject: [PATCH 17/46] update the readme file --- readme.md | 60 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/readme.md b/readme.md index 0211389..24f58bb 100644 --- a/readme.md +++ b/readme.md @@ -19,7 +19,7 @@ Very little programming is required, but several JavaScript hooks are provided i ### How do I use it? To create your own adventure, you can use one of the files in the [game-disks](https://github.com/okaybenji/text-engine/blob/master/game-disks) folder as a template. For example, take a look at [the disk called newDiskTemplate](https://github.com/okaybenji/text-engine/blob/master/game-disks/new-disk-template.js). -Include your "game disk" (JSON data) in index.html and load it with `loadDisk(myGameData)`. (Look at [index.html](https://github.com/okaybenji/text-engine/blob/master/index.html) in the repo for an example.) +Include your "game disk" (a function returning JSON data) in index.html and load it with `loadDisk(myGameData)`. (Look at [index.html](https://github.com/okaybenji/text-engine/blob/master/index.html) in the repo for an example.) The end product will be your very own text adventure game, similar to [this one](http://okaybenji.github.io/text-engine). It's a good idea to give that game a try to get introduced to the engine. @@ -27,10 +27,10 @@ The end product will be your very own text adventure game, similar to [this one] `text-engine` uses a disk metaphor for the data which represents your game, like the floppy disks of yore. -Including [index.js](https://github.com/okaybenji/text-engine/blob/master/index.js) from this repository in your [index.html](https://github.com/okaybenji/text-engine/blob/master/index.html) `