Make reload capture changes to html presentation while still restoring state, adds more documentation
Make reload capture changes to html presentation while still restoring state, adds more documentation
diff --git a/game.js b/game.js
index 31c5361..ce8be7c 100644
--- a/game.js
+++ b/game.js
@@ -1,5 +1,5 @@
/**
- * Copyright 2022 Christopher Pollett
+ * Copyright 2022-2023 Christopher Pollett
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -67,7 +67,10 @@ function xsel(selector)
return makeGameObjects(tag_objects);
}
/**
- *
+ * Returns an array of game objects based on the elements in the current
+ * document of the given tag name.
+ * @param {string} a name of a tag such as x-location or x-object.
+ * @return {Array} of game objects based on the tags that matched the selector
*/
function xtag(name)
{
@@ -75,10 +78,17 @@ function xtag(name)
return makeGameObjects(tag_objects);
}
/**
+ * Sleep time many milliseconds before continuing to execute whatever code was
+ * executing. This function returns a Promise so needs to be executed with await
+ * so that the code following the sleep statement will be used to resolve the
+ * Promise
*
+ * @param {number} time time in milliseconds to sleep for
+ * @return {Promise} a promise whose resolve callback is to be executed after
+ * that many milliseconds.
*/
-function sleep(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
+function sleep(time) {
+ return new Promise(resolve => setTimeout(resolve, time));
}
/**
*
@@ -315,13 +325,25 @@ function interpolateVariables(text)
return text;
}
/**
- *
+ * Encapsulates one place that objects can be in a Game.
*/
class Location
{
present = [];
/**
- *
+ * Used to display a description of a location to the game content
+ * area. This description is based on the x-present tags that were
+ * in the x-location tag from which the Location was parse. For
+ * each such x-present tag in the order it was in the original HTML,
+ * the ck condition is first evaluated (this may contain a delay
+ * or clickProceed call or a boolean expression), once/if condition is
+ * satisfied then the HTML contents of the tag are shown. If the case,
+ * where the ck evaluates to false that x-present tags contents are
+ * omitted. In addition to usual HTML tags, a x-present tag can
+ * have x-conversation subtags. These in turn can have x-speaker
+ * sub-tags to allow one to present conversation between two or more
+ * speakers in bubbles. An x-present tag may also involve x-input tags to
+ * receive/update values for x-objects or x-locations.
*/
async renderPresentation()
{
@@ -348,10 +370,41 @@ class Location
}
}
game_content.innerHTML += "<div class='footer-space'></div>";
+ this.prepareXInputs();
let anchors = sel("#game-content a, #game-content x-button");
addListenersAnchors(anchors);
enableSavesAndInventory();
}
+ /**
+ *
+ */
+ prepareXInputs()
+ {
+ let game_content = elt("game-content");
+ let x_inputs = game_content.querySelectorAll("x-input");
+ for (const x_input of x_inputs) {
+ x_input.setAttribute("contenteditable", "true");
+ let target_object = null;
+ let target_name = x_input.getAttribute("for");
+ if (typeof target_name != "undefined") {
+ if (game.objects[target_name]) {
+ target_object = game.objects[target_name];
+ } else if (game.locations[target_name]) {
+ target_object = game.locations[target_name];
+ }
+ if (target_object) {
+ let target_field = x_input.getAttribute("field");
+ if (target_field) {
+ x_input.innerHTML = target_object[target_field];
+ x_input.addEventListener("input", (evt) =>
+ {
+ target_object[target_field] = x_input.innerHTML;
+ });
+ }
+ }
+ }
+ }
+ }
/**
*
*/
@@ -402,7 +455,8 @@ class Location
while (section != old_section) {
old_section = section;
let speaker_pattern = new RegExp(
- "\<x-speaker[^\>]+name\s*\=\s*("+quote+")[^\>]*>", 'i');
+ "\<x-(?:speaker|thought)[^\>]+name\s*\=\s*("+quote+")[^\>]*>",
+ 'i');
let speaker_match = section.match(speaker_pattern);
if (speaker_match) {
let speaker_id = speaker_match[3];
@@ -437,15 +491,15 @@ class Location
class Game
{
/**
- *
+ * @type {number}
*/
timestamp;
/**
- *
+ *@type {Array<Object>}
*/
objects;
/**
- *
+ *@type {Array<Object>}
*/
locations;
/**
@@ -577,14 +631,52 @@ class Game
return false;
}
this.timestamp = game_state.timestamp;
+ /*
+ during development, changing an object or location's text might
+ not be viewable on a reload unless we copy some fields of the
+ reparsed html file into a save game object.
+ */
+ let old_objects = this.objects;
this.objects = game_state.objects;
+ for (const field in old_objects) {
+ if (typeof this.objects[field] === 'undefined') {
+ /* we assume our game never deletes objects or locations, so
+ if we find an object in old_objects (presumably coming
+ from a more recently parse HTML file) that was not
+ in the saved state, we copy it over.
+ */
+ this.objects[field] = old_objects[field];
+ } else {
+ if (typeof old_objects['action'] !== 'undefined') {
+ this.objects['action'] = old_objects['action'];
+ } else if (typeof this.objects['action'] !== 'undefined') {
+ delete this.objects['action'];
+ }
+ }
+ }
+ let old_locations = this.locations;
let locations = game_state.locations;
+ let location;
this.locations = {};
- for (const location_name in locations) {
- let location_object = locations[location_name];
- let location = new Location();
- for (const field in location_object) {
- location[field] = location_object[field];
+ for (const location_name in old_locations) {
+ if (typeof locations[location_name] === 'undefined') {
+ location = old_locations[location_name];
+ } else {
+ let location_object = locations[location_name];
+ location = new Location();
+ for (const field in location_object) {
+ location[field] = location_object[field];
+ if (field == 'present' || field == 'action' ||
+ field == 'default-action') {
+ if (typeof old_locations[location_name][field] ===
+ 'undefined') {
+ delete location[field];
+ } else {
+ location[field] =
+ old_locations[location_name][field];
+ }
+ }
+ }
}
this.locations[location_name] = location;
}
@@ -882,13 +974,13 @@ class Game
eval(code);
}
/**
- *
+ * Used to present the location the Main Character is currently at.
*/
describeMainCharacterLocation()
{
let main_character = this.objects['main-character'];
let position = main_character.position;
- console.log(position);
+ console.log(position);
let location = this.locations[position];
location.renderPresentation();
}
@@ -904,12 +996,13 @@ var game;
*/
var is_mobile = window.matchMedia("(max-width: 1000px)").matches;
/**
- * Module initialization function, used to set up the game object corrsponding
+ * Module initialization function, used to set up the game object corresponding
* to the current HTML document.
*/
async function initGame()
{
game = new Game();
+ console.log(game.locations);
if (sessionStorage.current) {
game.restoreState(sessionStorage.current);
}