diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..4a8fae8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + "configurations": [ + { + "type": "chrome", + "name": "Editor", + "request": "launch", + "url": "http://127.0.0.1:3000/editor/index.html" + }, + { + "type": "chrome", + "name": "Player", + "request": "launch", + "url": "http://127.0.0.1:3000/player/index.html" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 70ca1f7..f9d8be3 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ # TextAdventureJS A text based adventure engine written in Javascript. -This repo comes with a player that uses the engine as well as an editor to create games for it. +Allows you to create your own text adventure games and embed them into your website or share and play them with included demo player. + +- Games are json files, that follow the textAdventureGameDatabase schema (tadb). +- Editor folder contains an editor that allows to create games with a GUI. +- Player folder contains a demo player that shows how to integreate the engine The library and the player are written in pure JavaScript. -The editor uses jquery. +The editor uses jQuery. ## Player -Animated demo for the textAdventureJS player +Animated demo for the textAdventureJS player Try it here: https://dak0r.github.io/TextAdventureJS/player/ @@ -15,44 +19,63 @@ Try it here: https://dak0r.github.io/TextAdventureJS/player/ This repo also provides a full editor including debugger functionality for creating your own games: -Animated demo for the textAdventureJS player +Animated demo for the textAdventureJS player Try it here: https://dak0r.github.io/TextAdventureJS/editor/ ## Usage - -A barebones example that uses jquery: - +Usage is simple: the engine needs to be initilized with a JS functions that allows the engine to write output and to clear all written output. Then any compatible game file can be loaded: ```js -// Defines where to write output -function witeLine(outputLine) { - $("#gameLog").append(outputLine + "
"); -} -// Clears written output from the area -function clearArea() { - $("#gameLog").html(""); -} -// Read user input -function readInput() { - const inputText = $("#inputField").val().trim(); - writeLine(inputText); - textAdv.input(inputText); - $("#inputField").val(""); -} - -// Add event handler for reading user input: -$("#submit").click(function() { readInput(); }); - -// Init textAdventureJS and load a game -var textAdvEngine = new textAdventureEngine(witeLine, clearArea); -textAdvEngine.loadDatabaseFromFile("game.json"); + var textAdvEngine = new textAdventureEngine(writeLine, clearArea); + textAdvEngine.loadDatabaseFromFile( + "https://dak0r.github.io/TextAdventureJS/games/new_project.tadb.json" + ); ``` -with this html elements: +A minimalistic working example, which uses jquery to keep it short: ```html -
- - + + + + + + + + +
+ + + + ``` See [player/index.html](./player/index.html) for a more complex example. diff --git a/Templates/new_project.tadb.json b/Templates/new_project.tadb.json deleted file mode 100644 index df69190..0000000 --- a/Templates/new_project.tadb.json +++ /dev/null @@ -1,189 +0,0 @@ -{ - "$schema": "../textAdventureGameDatabase.schema.json", - "general": { - "title": "New TextAdventureJS Game", - "author": "Your Name", - "version": "0.1", - "continue_enabled": true, - "request": ["", "What do you do?"], - "start": { - "text": ["Welcome to the textAdventureJS Mini Example"], - "commands": ["gotoLocation first_room"] - }, - "parser_ignored_words": [ - "to", - "through", - "on", - "off", - "from", - "around", - "at" - ], - "parser_error_text": "Sorry, I didn't understand that.", - "parser_unknown_verb_text": "Sorry, I don't know how to do that." - }, - "verbs": { - "look": { - "failure": "Sorry, you can't see that.", - "words": ["look", "view", "watch", "read", "search"], - "standalone_action": { - "text": [], - "commands": ["showLocationDescription"] - } - }, - "restart": { - "failure": "Be careful! 'restart' will reset the game.", - "words": ["restart"], - "standalone_action": { - "text": [], - "commands": ["restartGame"] - } - }, - "pickup": { - "failure": "Sorry, you can't see that.", - "words": ["pick", "pickup", "lift", "take", "get", "reach"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "drop": { - "failure": "How do you want to drop that?", - "words": ["drop", "let go"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "activate": { - "failure": "Sorry, you can't do that.", - "words": ["turn", "power", "switch"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "go": { - "failure": "Sorry, you can't go there.", - "words": ["go", "walk", "run", "goto"], - "standalone_action": { - "text": ["Where do you want to {verb}?"], - "commands": [] - } - }, - "open": { - "failure": "Sorry, you can't open that.", - "words": ["open", "break"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "close": { - "failure": "Sorry, you can't close that.", - "words": ["close", "shut"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "jump": { - "failure": "Sorry, this is not possible.", - "words": ["jump"], - "standalone_action": { - "text": ["You jump up and down."], - "commands": [] - } - }, - "use": { - "failure": "Well.. this does not work.", - "words": ["use", "combine", "throw", "attack"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "light": { - "failure": "Well.. this does not work.", - "words": ["light", "ignite", "burn", "fire"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - }, - "remove": { - "failure": "Well.. this does not work.", - "words": ["remove", "break", "split", "destroy"], - "standalone_action": { - "text": ["What do you want to {verb}?"], - "commands": [] - } - } - }, - "objects": { - "template_object": { - "words": ["object", "obj"], - "locationDescription": "An object is lying on the floor.", - "actions": { - "pickup": { - "text": ["Okay got it."], - "commands": [ - "objectRemoveFromLocation this", - "inventoryAdd template_object_pickedUp" - ] - }, - "look": { - "text": ["A mysterious object."], - "commands": [] - } - }, - "useableObjects": {} - }, - "template_object_pickedUp": { - "words": ["object", "obj"], - "locationDescription": "You are holding an object in your hands.", - "actions": { - "drop": { - "text": ["Okay dropped it."], - "commands": [ - "objectAddToLocation template_object", - "inventoryRemove this" - ] - }, - "look": { - "text": ["A mysterious object."], - "commands": [] - } - }, - "useableObjects": {} - }, - "door_to_second_room": { - "words": ["door", "north", "up"], - "locationDescription": "A door is leading north.", - "actions": { - "go": { - "text": ["You walk through the door."], - "commands": ["gotoLocation second_room"] - } - } - }, - "door_to_first_room": { - "words": ["door", "south", "down"], - "locationDescription": "A door is leading south.", - "actions": { - "go": { - "text": ["You walk through the door."], - "commands": ["gotoLocation first_room"] - } - } - } - }, - "locations": { - "first_room": { - "objects": ["template_object", "door_to_second_room"] - }, - "second_room": { - "objects": ["door_to_first_room"] - } - } -} diff --git a/__tests__/textAdventureEngine.test.js b/__tests__/textAdventureEngine.test.js index 1a7a6c9..9942800 100644 --- a/__tests__/textAdventureEngine.test.js +++ b/__tests__/textAdventureEngine.test.js @@ -3,12 +3,12 @@ const path = require("path"); let sampleGame; let found = false; -const p = path.join(__dirname, "..", "templates", "new_project.tadb.json"); +const p = path.join(__dirname, "..", "games", "new_project.tadb.json"); if (fs.existsSync(p)) { sampleGame = JSON.parse(fs.readFileSync(p, "utf8")); found = true; } else { - throw new Error("Could not find templates/new_project.tadb.json."); + throw new Error("Could not find sample game at " + p); } const TextAdventureEngine = require("../textAdventure.js"); @@ -52,18 +52,18 @@ describe("textAdventureEngine parser & action integration tests (using template) test("take object moves it to inventory and removes from location", () => { engine.input("take object"); const gs = engine.devGetGameState(); - expect(gs.inventory).toContain("template_object_pickedUp"); + expect(gs.inventory).toContain("demo_object_pickedUp"); const objects = gs.locations[gs.currentLocation].objects; - expect(objects).not.toContain("template_object"); + expect(objects).not.toContain("demo_object"); }); test("drop object returns it to location", () => { engine.input("take object"); engine.input("drop object"); const gs = engine.devGetGameState(); - expect(gs.inventory).not.toContain("template_object_pickedUp"); + expect(gs.inventory).not.toContain("demo_object_pickedUp"); expect(gs.locations[gs.currentLocation].objects).toContain( - "template_object" + "demo_object" ); }); @@ -156,7 +156,7 @@ describe("textAdventureEngine parser & action integration tests (using template) ); engine2.loadDatabaseFromObject(sampleGame); expect(engine2.devGetGameState().inventory).toContain( - "template_object_pickedUp" + "demo_object_pickedUp" ); }); }); diff --git a/css/channel-shift.css b/css/channel-shift.css deleted file mode 100644 index e864228..0000000 --- a/css/channel-shift.css +++ /dev/null @@ -1,22 +0,0 @@ -.channel-shift { - position: relative; - padding: 0 0 0 0; - &:before, - &:after { - top: 0; - position: absolute; - left: 0; - padding: 0 0 0 0; - display: block; - mix-blend-mode: screen; - content: attr(data-text); - } - &:before { - color: #ff3c74; - transform: translate(-1px, 1px); - } - &:after { - transform: translate(1px, -1px); - color: #62c4ff; - } -} diff --git a/editor/index.html b/editor/index.html index bae78e6..01a9a4f 100644 --- a/editor/index.html +++ b/editor/index.html @@ -35,8 +35,7 @@ - - + diff --git a/editor/tba_editor.js b/editor/tba_editor.js index 4b7c13d..368470e 100644 --- a/editor/tba_editor.js +++ b/editor/tba_editor.js @@ -307,7 +307,7 @@ function deleteDatabaseFromStorage() { async function getDefaultProjectJson() { try { - const response = await fetch("../templates/new_project.tadb.json"); + const response = await fetch("../games/new_project.tadb.json"); const json = await response.json(); return JSON.stringify(json); } catch (err) { diff --git a/Templates/empty.tadb.json b/games/empty.tadb.json similarity index 85% rename from Templates/empty.tadb.json rename to games/empty.tadb.json index e49f5a8..64bce5c 100644 --- a/Templates/empty.tadb.json +++ b/games/empty.tadb.json @@ -1,6 +1,5 @@ { - "$schema": "../textAdventureGameDatabase.schema.json", - + "$schema": "https://dak0r.github.io/TextAdventureJS/textAdventureGameDatabase.schema.json", "general": { "title": "TextAdventureJS Empty Database Template", "author": "Your Name", diff --git a/templates/new_project.tadb.json b/games/new_project.tadb.json similarity index 94% rename from templates/new_project.tadb.json rename to games/new_project.tadb.json index df69190..b0e474b 100644 --- a/templates/new_project.tadb.json +++ b/games/new_project.tadb.json @@ -1,5 +1,5 @@ { - "$schema": "../textAdventureGameDatabase.schema.json", + "$schema": "https://dak0r.github.io/TextAdventureJS/textAdventureGameDatabase.schema.json", "general": { "title": "New TextAdventureJS Game", "author": "Your Name", @@ -121,7 +121,7 @@ } }, "objects": { - "template_object": { + "demo_object": { "words": ["object", "obj"], "locationDescription": "An object is lying on the floor.", "actions": { @@ -129,7 +129,7 @@ "text": ["Okay got it."], "commands": [ "objectRemoveFromLocation this", - "inventoryAdd template_object_pickedUp" + "inventoryAdd demo_object_pickedUp" ] }, "look": { @@ -139,14 +139,14 @@ }, "useableObjects": {} }, - "template_object_pickedUp": { + "demo_object_pickedUp": { "words": ["object", "obj"], "locationDescription": "You are holding an object in your hands.", "actions": { "drop": { "text": ["Okay dropped it."], "commands": [ - "objectAddToLocation template_object", + "objectAddToLocation demo_object", "inventoryRemove this" ] }, @@ -180,7 +180,7 @@ }, "locations": { "first_room": { - "objects": ["template_object", "door_to_second_room"] + "objects": ["demo_object", "door_to_second_room"] }, "second_room": { "objects": ["door_to_first_room"] diff --git a/player/textadventurejs_teaser.tadb.json b/games/textadventurejs_teaser.tadb.json similarity index 99% rename from player/textadventurejs_teaser.tadb.json rename to games/textadventurejs_teaser.tadb.json index 24b11ba..4a7be95 100644 --- a/player/textadventurejs_teaser.tadb.json +++ b/games/textadventurejs_teaser.tadb.json @@ -1,11 +1,11 @@ { - "$schema": "../textAdventureGameDatabase.schema.json", + "$schema": "https://dak0r.github.io/TextAdventureJS/textAdventureGameDatabase.schema.json", "general": { "title": "TextAdventureJS Teaser", "author": "Daniel Korgel", "version": "1.0", "request": ["", "What do you do?"], - "continue_enabled": true, + "continue_enabled": false, "start": { "text": ["", "", "", "You wake up with a headache..."], "commands": ["gotoLocation start_chamber"] diff --git a/player/index.html b/player/index.html index 556eff4..68459b8 100644 --- a/player/index.html +++ b/player/index.html @@ -1,7 +1,7 @@ - TextAdventureJS Player + textAdventureJS Player - + @@ -28,14 +28,14 @@ analyticsFunction // from textAdventurePlayer.js ); await textAdvEngine.loadDatabaseFromFile( - "./textadventurejs_teaser.tadb.json" + "../../games/textadventurejs_teaser.tadb.json" ); });
-

TextAdventureJS

+

TextAdventureJS Player

diff --git a/shared/channel-shift.css b/shared/channel-shift.css new file mode 100644 index 0000000..fc7044e --- /dev/null +++ b/shared/channel-shift.css @@ -0,0 +1,29 @@ +/** +* A css class that applies a RGB channel shift effect to text elements. +* Usage: +*

Your Text Here

+* Wrapper div is required. The data-text attribute describe the text that the effect is applied to. +* If the text is longer, the effect will only be applied to the first part, which should match the data-text attribute. +*/ +.channel-shift { + position: relative; + padding: 0 0 0 0; + &:before, + &:after { + top: 0; + position: absolute; + left: 0; + padding: 0 0 0 0; + display: block; + mix-blend-mode: screen; + content: attr(data-text); + } + &:before { + color: #ff3c74; + transform: translate(-1px, 1px); + } + &:after { + transform: translate(1px, -1px); + color: #62c4ff; + } +} diff --git a/docs/editor.jpg b/shared/editor.jpg similarity index 100% rename from docs/editor.jpg rename to shared/editor.jpg diff --git a/docs/player.gif b/shared/player.gif similarity index 100% rename from docs/player.gif rename to shared/player.gif diff --git a/templates/empty.tadb.json b/templates/empty.tadb.json deleted file mode 100644 index e49f5a8..0000000 --- a/templates/empty.tadb.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$schema": "../textAdventureGameDatabase.schema.json", - - "general": { - "title": "TextAdventureJS Empty Database Template", - "author": "Your Name", - "version": "0.1", - "continue_enabled": true, - "request": ["", "What do you do?"], - "start": { - "text": ["Welcome to the Empty Template"], - "inventory": "", - "commands": [] - }, - "parser_ignored_words": [], - "parser_error_text": "Sorry, I didn't understand that.", - "parser_unknown_verb_text": "Sorry, I don't know how to do that." - }, - "verbs": {}, - "objects": {}, - "locations": { - "first_room": { - "objects": [] - } - } -} diff --git a/textAdventure.js b/textAdventure.js index 8e35f66..f48fdb0 100644 --- a/textAdventure.js +++ b/textAdventure.js @@ -15,20 +15,28 @@ class textAdventureEngine { } async loadDatabaseFromFile(gamedatabasePath, showGameInfo = true) { - this.outputClear(); - this.#writeOutputLines("Initializing Text Adventure Engine..."); - this.outputClear(); - const response = await fetch(gamedatabasePath); - const json = await response.json(); this.showGameInfo = showGameInfo; - this.#initDatbase(json); + try { + this.outputClear(); + this.#writeOutputLines("Initializing Text Adventure Engine..."); + const response = await fetch(gamedatabasePath); + const json = await response.json(); + this.outputClear(); + this.#initDatbase(json); + } catch (err) { + console.error(err); + this.#writeOutputLines("Error loading game database!"); + } } loadDatabaseFromObject(json) { - this.outputClear(); - this.#writeOutputLines("Initializing Text Adventure Engine..."); - this.outputClear(); - this.#initDatbase(json); + try { + this.outputClear(); + this.#initDatbase(json); + } catch (err) { + console.error(err); + this.#writeOutputLines("Error loading game database!"); + } } input(cmd) {