diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..569b8e9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cSpell.words": [ + "Bitstream", + "Korgel", + "tadb", + "textadventurejs" + ] +} \ No newline at end of file diff --git a/README.md b/README.md index f9d8be3..062c17a 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,17 @@ # TextAdventureJS -A text based adventure engine written in Javascript. -Allows you to create your own text adventure games and embed them into your website or share and play them with included demo player. +A text based adventure engine written in JavaScript. +Create your own text adventure games and embed them into your website, or share and play them with the included demo player. -- Games are json files, that follow the textAdventureGameDatabase schema (tadb). +- 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 +- Player folder contains a demo player that shows how to integrate the engine The library and the player are written in pure JavaScript. The editor uses jQuery. ## Player + Animated demo for the textAdventureJS player Try it here: https://dak0r.github.io/TextAdventureJS/player/ @@ -24,13 +25,16 @@ This repo also provides a full editor including debugger functionality for creat Try it here: https://dak0r.github.io/TextAdventureJS/editor/ ## Usage -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: + +Usage is simple: the engine needs to be initialized 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 - var textAdvEngine = new textAdventureEngine(writeLine, clearArea); - textAdvEngine.loadDatabaseFromFile( - "https://dak0r.github.io/TextAdventureJS/games/new_project.tadb.json" - ); +var textAdvEngine = new textAdventureEngine(writeLine, clearArea); +textAdvEngine.loadDatabaseFromFile( + "https://dak0r.github.io/TextAdventureJS/games/new_project.tadb.json" +); ``` + A minimalistic working example, which uses jquery to keep it short: ```html @@ -80,13 +84,13 @@ A minimalistic working example, which uses jquery to keep it short: See [player/index.html](./player/index.html) for a more complex example. -## Game Database +## Game Databases -A Text Adventure Game Database is JSON file which describes games that can run in the TextAdventureJS Engine. +A Text Adventure Game Database is JSON file which describes a game that can run in the TextAdventureJS Engine. -These game files can be validated using the JSON schema in this repo: [textAdventureGameDatabase.schema.json](./textAdventureGameDatabase.schema.json). +Game files are expected to match this JSON schema: [textAdventureGameDatabase.schema.json](./textAdventureGameDatabase.schema.json). The engine is not validating the schema as this would cause external dependencies. If desired, I suggest to handle it in the player, before passing the file or object to the engine. - I recommend using the [JSON Schema Validator](https://marketplace.visualstudio.com/items?itemName=tberman.json-schema-validator), if editing the json files manually. +In VSCode I can recommend using the [JSON Schema Validator](https://marketplace.visualstudio.com/items?itemName=tberman.json-schema-validator) Addon, if editing the JSON files manually. Alternatively [this browser based solution](https://www.jsonschemavalidator.net/) works well, too. ## Concept @@ -96,7 +100,7 @@ Each game exists of `objects` which are either in the players inventory or in `l Locations are basically groups of `objects`. -The description text of a location soley exists of the objects which can be found in it. This means an empty location has no description. Thus a location should always have at least one object, at any given moment. +The description text of a location solely exists of the objects which can be found in it. This means an empty location has no description. Thus a location should always have at least one object, at any given moment. ### Verbs @@ -135,14 +139,16 @@ To close the chest again, you can use `objectReplaceInLocation` again in `chest_ #### Object specific failure texts -If the player tries to do soemthing with an object and the action is not defined, the engine will output the default verb failure sentance. In some cases you might find it more immersive to have an object specfic failure text, though verbs have no object specifc failures, as they usually will vary by object. +If the player tries to do something with an object and the action is not defined, the engine will output the default verb failure sentence. In some cases you might find it more immersive to have an object specific failure text, though verbs have no object specific failures, as they usually will vary by object. So In this case, you simply have to add the verb as an action to the object and add your failure message as text to the action. ### Placeholders + When adding a text to an action, you can use predefined placeholders which will be filled in automatically when the text is written. If the placeholder does not apply in the given context, the value is not replaced. Existing placeholders are: + - `{verb}` is replaced with the word the player used to describe the verb / action - `{object}` is replaced with the word the player used to describe the object @@ -152,7 +158,7 @@ Commands must be used for any logic that goes beyond outputting text. You can ch #### 'this' in command parameters -If the command is supposed to affect the object that the action is defined on, you can refer to it using `this` instead of its unique nanme. +If the command is supposed to affect the object that the action is defined on, you can refer to it using `this` instead of its unique name. #### objectRemoveFromLocation @@ -235,8 +241,8 @@ Defined events are: - `command`: a command was successfully parsed - `unknown_verb`: the user tried to use a verb that is not defined -- `unknwon_object`: the user tried to use an object that is not present in the players current location or inventory. -- `unkown_verb_for_object`: the user tried to do something with an object that is not defined +- `unknown_object`: the user tried to use an object that is not present in the players current location or inventory. +- `unknown_verb_for_object`: the user tried to do something with an object that is not defined - `unknown_command`: other parsing error Each event contains a body, that includes: @@ -258,13 +264,15 @@ function analyticsFunction(eventName, eventData) { ## Testing ## Running Editor and Player locally + Running the editor and player html files locally requires a local webserver. I recommend the `ms-vscode.live-server` extension for vscode. ### Unit Tests + Quick steps to run tests locally: 1. Install Node.js from https://nodejs.org/ if you don't already have it. 2. From the project root run: - `npm install` (installs dev dependencies like Jest) - - `npm test` (runs the test suite) \ No newline at end of file + - `npm test` (runs the test suite) diff --git a/editor/tba_editor.js b/editor/tba_editor.js index 368470e..0767b52 100644 --- a/editor/tba_editor.js +++ b/editor/tba_editor.js @@ -123,7 +123,7 @@ $(document).ready(function () { ) { e.preventDefault(); if (window.showOpenFilePicker) { - // mimic click handler behaviour but keep it simple for keyboard + // mimic click handler behavior but keep it simple for keyboard $(".file-drop-area").click(); } else { $("#fileInput").click(); @@ -319,10 +319,10 @@ async function getDefaultProjectJson() { function updateEditorState() { if (TBA_DATABASE !== undefined) { $("#drop-area").hide(); - $("#editor-aera").show(); + $("#editor-area").show(); } else { $("#drop-area").show(); - $("#editor-aera").hide(); + $("#editor-area").hide(); } } @@ -1414,7 +1414,7 @@ function generateTextArea(name, value, onChange) { function getNewVerb(name) { var newVerb = {}; - newVerb["failure"] = "That didnt work."; + newVerb["failure"] = "That didn't work."; newVerb["words"] = [name]; return newVerb; } diff --git a/games/new_project.tadb.json b/games/new_project.tadb.json index b0e474b..b03658a 100644 --- a/games/new_project.tadb.json +++ b/games/new_project.tadb.json @@ -136,8 +136,7 @@ "text": ["A mysterious object."], "commands": [] } - }, - "useableObjects": {} + } }, "demo_object_pickedUp": { "words": ["object", "obj"], @@ -154,8 +153,7 @@ "text": ["A mysterious object."], "commands": [] } - }, - "useableObjects": {} + } }, "door_to_second_room": { "words": ["door", "north", "up"], diff --git a/games/textadventurejs_teaser.tadb.json b/games/textadventurejs_teaser.tadb.json index 4a7be95..c668b0a 100644 --- a/games/textadventurejs_teaser.tadb.json +++ b/games/textadventurejs_teaser.tadb.json @@ -121,30 +121,6 @@ } }, "objects": { - "template_object": { - "words": ["desk", "desktop", "workingplace"], - "locationDescription": "", - "actions": { - "pickup": { - "text": ["okay got it"], - "commands": ["inventoryAdd template_object"] - }, - "look": { - "text": ["soft and comfy"], - "commands": [] - } - }, - "useableObjects": { - "bedroom_desk": { - "text": ["soft and comfy"], - "commands": [] - }, - "test": { - "text": ["works"], - "commands": [] - } - } - }, "plaster_wall_door_unidentified": { "words": ["wall", "walls", "plaster", "metal"], "locationDescription": "Plaster is crumbling from the WALLS.", @@ -165,8 +141,7 @@ "text": ["Why would you do that?"], "commands": [] } - }, - "useableObjects": {} + } }, "plaster_wall_door_door_visible": { "words": ["wall", "walls"], @@ -187,8 +162,7 @@ "objectRemoveFromLocation metal_behind_plaster" ] } - }, - "useableObjects": {} + } }, "plaster_wall_door_door_found": { "words": ["wall", "rusty", "door"], @@ -206,8 +180,7 @@ "text": ["You open the rusty door and enter a hallway."], "commands": ["gotoLocation long_hallway"] } - }, - "useableObjects": {} + } }, "metal_behind_plaster": { "words": ["rust", "rusty", "metal", "plaster"], @@ -234,8 +207,7 @@ "objectRemoveFromLocation metal_behind_plaster" ] } - }, - "useableObjects": {} + } }, "room_start_chamber_dark": { "words": ["room"], @@ -253,10 +225,9 @@ "text": ["If only you'd had something to enlighten the room with..."], "commands": [] } - }, - "useableObjects": {} + } }, - "room_start_chamber_enlighted": { + "room_start_chamber_enlightened": { "words": ["room"], "locationDescription": "The room is slightly lit.", "actions": { @@ -268,8 +239,7 @@ "text": ["The room is already slightly illuminated."], "commands": [] } - }, - "useableObjects": {} + } }, "box_matches_unidentified": { "words": ["box"], @@ -288,8 +258,7 @@ ], "commands": [] } - }, - "useableObjects": {} + } }, "box_matches_pickedUp": { "words": ["box", "matches", "matchbox", "match"], @@ -297,22 +266,22 @@ "actions": { "use": { "text": [ - "You light the last match. The room is now slightly illumanted." + "You light the last match. The room is now slightly illuminated." ], "commands": [ "objectReplaceInLocation plaster_wall_door_unidentified plaster_wall_door_door_visible", - "objectReplaceInLocation room_start_chamber_dark room_start_chamber_enlighted", + "objectReplaceInLocation room_start_chamber_dark room_start_chamber_enlightened", "objectAddToLocation metal_behind_plaster", "inventoryRemove box_matches_pickedUp" ] }, "light": { "text": [ - "You light the last match. The room is now slightly illumanted." + "You light the last match. The room is now slightly illuminated." ], "commands": [ "objectReplaceInLocation plaster_wall_door_unidentified plaster_wall_door_door_visible", - "objectReplaceInLocation room_start_chamber_dark room_start_chamber_enlighted", + "objectReplaceInLocation room_start_chamber_dark room_start_chamber_enlightened", "objectAddToLocation metal_behind_plaster", "inventoryRemove box_matches_pickedUp" ] @@ -328,8 +297,7 @@ "text": ["An almost empty box of matches."], "commands": [] } - }, - "useableObjects": {} + } }, "box_matches_dropped": { "words": ["box", "matches", "matchbox"], @@ -346,8 +314,7 @@ "text": ["A small box of matches, that you dropped here..."], "commands": [] } - }, - "useableObjects": {} + } }, "room_hallway": { "words": ["room", "hallway"], @@ -357,8 +324,7 @@ "text": [""], "commands": [] } - }, - "useableObjects": {} + } }, "exit_light": { "words": ["light", "exit"], @@ -376,8 +342,7 @@ ], "commands": [] } - }, - "useableObjects": {} + } }, "exit_light_without_leaflet": { "words": ["light", "exit"], @@ -395,8 +360,7 @@ ], "commands": [] } - }, - "useableObjects": {} + } }, "dev_leaflet_on_ground": { "words": ["leaflet", "notice", "paper"], @@ -416,8 +380,7 @@ "inventoryAdd dev_leaflet_pickedUp" ] } - }, - "useableObjects": {} + } }, "dev_leaflet_pickedUp": { "words": ["leaflet", "notice", "paper"], @@ -438,8 +401,7 @@ "inventoryRemove dev_leaflet_pickedUp" ] } - }, - "useableObjects": {} + } } }, "locations": { diff --git a/player/index.html b/player/index.html index 68459b8..40ef9fe 100644 --- a/player/index.html +++ b/player/index.html @@ -17,7 +17,7 @@ diff --git a/textAdventure.js b/textAdventure.js index f48fdb0..e53592e 100644 --- a/textAdventure.js +++ b/textAdventure.js @@ -14,15 +14,15 @@ class textAdventureEngine { this.analyticsFunction = analyticsFunction; } - async loadDatabaseFromFile(gamedatabasePath, showGameInfo = true) { + async loadDatabaseFromFile(gameDatabasePath, showGameInfo = true) { this.showGameInfo = showGameInfo; try { this.outputClear(); this.#writeOutputLines("Initializing Text Adventure Engine..."); - const response = await fetch(gamedatabasePath); + const response = await fetch(gameDatabasePath); const json = await response.json(); this.outputClear(); - this.#initDatbase(json); + this.#initDatabase(json); } catch (err) { console.error(err); this.#writeOutputLines("Error loading game database!"); @@ -32,7 +32,7 @@ class textAdventureEngine { loadDatabaseFromObject(json) { try { this.outputClear(); - this.#initDatbase(json); + this.#initDatabase(json); } catch (err) { console.error(err); this.#writeOutputLines("Error loading game database!"); @@ -43,7 +43,7 @@ class textAdventureEngine { this.#praseCommand(cmd); } - #initDatbase(gameDatabaseObject) { + #initDatabase(gameDatabaseObject) { this.#database = gameDatabaseObject; if ( @@ -301,7 +301,7 @@ class textAdventureEngine { this.#gameState.currentLocation ).objects.push(acts[2]); } else if (acts[0] == "gotoLocation") { - console.log("SIWTCHING LOCATION TO:" + acts[1]); + console.log("SWITCHING LOCATION TO:" + acts[1]); this.#gameState.currentLocation = acts[1]; const currentRoomState = this.#getLocationState( this.#gameState.currentLocation diff --git a/textAdventureGameDatabase.schema.json b/textAdventureGameDatabase.schema.json index e81b84c..6b5dc38 100644 --- a/textAdventureGameDatabase.schema.json +++ b/textAdventureGameDatabase.schema.json @@ -111,7 +111,7 @@ } }, "objects": { - "description": "List of objecs that are available throughout the game", + "description": "List of objects that are available throughout the game", "type": "object", "additionalProperties": { "type": "object", @@ -133,13 +133,6 @@ "additionalProperties": { "$ref": "#/definitions/action_definition" } - }, - "useableObjects": { - "description": "List of possible combinations", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/action_definition" - } } } }