diff --git a/css/game.css b/css/game.css
new file mode 100644
index 0000000..9153331
--- /dev/null
+++ b/css/game.css
@@ -0,0 +1,262 @@
+/**
+ * FRISE (FRee Interactive Story Engine)
+ * A light-weight engine for writing interactive fiction and games.
+ *
+ * Copyright 2022-2023 Christopher Pollett chris@pollett.org
+ *
+ * @license
+ * 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
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * @file The CSS code used to style the elements used by a game
+ * @author Chris Pollett
+ * @link https://www.frise.org/
+ * @copyright 2022 - 2023
+ */
+x-game,
+x-action
+{
+ display: none;
+}
+.float-right
+{
+ float: right;
+}
+.float-left
+{
+ float: left;
+}
+.left
+{
+ text-align: left;
+}
+.right
+{
+ text-align: right;
+}
+.center
+{
+ text-align: center;
+}
+.fit-content
+{
+ width: fit-content;
+}
+.rounded
+{
+ border-radius: 20px;
+}
+.rounded-top
+{
+ border-radius: 20px 20px 0 0;
+}
+.medium-width
+{
+ width: 150px;
+}
+.mobile .medium-width
+{
+ width: 100px;
+}
+.medium-border
+{
+ border: solid 2px black;
+}
+#game-content
+{
+ font-family: "Oswald", serif;
+ font-size: 18pt;
+ height: 100%;
+ left: 300px;
+ line-height: 1.6;
+ overflow-x: scroll;
+ overflow-y: scroll;
+ position: fixed;
+ transition: left .25s ease-in;
+ top: 0px;
+ padding-top: 23px;
+ width: calc(100% - 300px);
+}
+#main-nav,
+#main-bar
+{
+ background: linear-gradient(.25turn, white, 97%, lightgray);
+ height: 100%;
+ left: 0;
+ overflow-y: scroll;
+ position: fixed;
+ text-align: center;
+ transition: left .25s ease-in;
+ top: 0px;
+ width: 240px;
+ z-index: 0;
+}
+#main-bar
+{
+ background: white;
+ height: 50px;
+ text-align: left;
+ top: 0;
+ width: 50px;
+ z-index: 2;
+}
+.mobile #main-bar
+{
+ background: white;
+ height: 50px;
+ text-align: left;
+ top: 0;
+ width: 50px;
+ z-index: 2;
+}
+#main-nav button,
+#main-bar button
+{
+ border-radius: 10px;
+ font-size: 21pt;
+ padding: 2px 5px 5px 5px;
+ width: 40px;
+}
+.mobile #main-nav button,
+.mobile #main-bar button
+{
+ font-size: 18pt;
+ height: 50px;
+ margin: 2px;
+ padding: 8px 10px 10px 10px;
+ width: 40px;
+}
+#main-nav h1
+{
+ margin: 8px;
+}
+#main-nav x-button,
+#main-nav input[type="range"]
+{
+ width: 170px;
+}
+#main-nav .nav-label
+{
+ font-size: 16pt;
+ margin: auto;
+ text-align:left;
+ width: 170px;
+}
+.filled
+{
+ background-color: blue;
+ color: white;
+}
+.float-right
+{
+ float: right;
+}
+table.save-table
+{
+ width: 7in;
+}
+table.save-table,
+table.save-table tr,
+table.save-table th,
+table.save-table td
+{
+ border-collapse: collapse;
+ border: 1px solid black;
+ padding:10px;
+ text-align: center;
+}
+table.save-table td.save-name
+{
+ width: 3in;
+}
+h1
+{
+ margin: 0px;
+ padding: 0px;
+}
+x-speaker
+{
+ border: 3px solid black;
+ border-radius: 10px;
+ display: block;
+ font-size:18pt;
+ margin: 10px;
+ min-height: 110px;
+ padding: 10px;
+ width: 90%;
+}
+.mobile x-speaker
+{
+ margin: 2px;
+ padding: 4px;
+ width: 82%;
+}
+x-speaker figure:first-of-type
+{
+ border-radius: 5px;
+ display: block;
+ float: left;
+ margin: 0;
+ width: 120px;
+}
+.mobile x-speaker figure:first-of-type
+{
+ width: 80px;
+}
+x-speaker figure:first-of-type > img
+{
+ border-radius: 10px;
+ display: block;
+ height: 100px;
+ margin: auto;
+ width: 100px;
+}
+.mobile x-speaker figure:first-of-type > img
+{
+ width: 70px;
+ height: 70px;
+}
+.footer-space {
+ height: 1in;
+}
+x-button
+{
+ background-color: #F0F0F6;
+ border: 1px solid gray;
+ border-radius: 5px;
+ color: black;
+ display: inline-block;
+ font-size: 18pt;
+ font-weight: bold;
+ padding: 8px;
+ margin: 3px;
+}
+x-button.disabled
+{
+ border: 1px solid lightgray;
+ background-color: #F6F6FA;
+ color: #666;
+ cursor: not-allowed;
+}
+x-button:hover
+{
+ background-color: lightgray;
+}
+
+x-button.disabled:hover
+{
+ color: #666;
+ background-color: #F6F6FA;
+}
+img
+{
+ max-width: 90%;
+}
+input
+{
+ border: 2px solid lightblue;
+ border-radius: 5px;
+ font-size: 18pt;
+ padding: 2px;
+}
diff --git a/game.js b/js/game.js
similarity index 91%
rename from game.js
rename to js/game.js
index 85acdec..12d65ea 100644
--- a/game.js
+++ b/js/game.js
@@ -1,17 +1,20 @@
/**
- * Copyright 2022-2023 Christopher Pollett
+ * FRISE (FRee Interactive Story Engine)
+ * A light-weight engine for writing interactive fiction and games.
+ *
+ * Copyright 2022-2023 Christopher Pollett chris@pollett.org
*
+ * @license
* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
- */
-/**
- * FRISE (FRee Interactive Story Engine)
- *
- * A light-weight engine for writing interactive fiction and games.
*
+ * @file The JS code used to manage a game
+ * @author Chris Pollett
+ * @link https://www.frise.org/
+ * @copyright 2022 - 2023
*/
-/**
+/*
* Global Variables
*/
/**
@@ -188,18 +191,28 @@ function upperFirst(str)
*/
var object_counter = 0;
/**
- * Used to convert a DOM Element dom_object to an Object or Location sutiable
+ * Used to convert a DOM Element dom_object to an Object or Location suitable
* for a FRISE game. dom_object's whose tagName does not begin with x-
* will result in null being returned. If the tagName is x-location, a
- * Location object will be returned otherwise a Javascript Object is returned.
+ * Location object will be returned, otherwise, a Javascript Object is returned.
* The innerHTML of any subtag of an x-object or an
- * x-location beginning x- become the value of a field in the resulting
- * object with name the name of of the tag less x-. For example,
- * <x-object>
- *
+ * x-location beginning with x- becomes the value of a field in the resulting
+ * object with the name of the tag less x-. For example, a DOM_object
+ * representing the following HTML code:
+ * <x-object id="bob">
+ * <x-name>Robert Smith</x-name>
+ * <x-age>25</x-age>
* </x-object>
- * @param {Element}
- * @return {Object?|Location?}
+ * will be processed to a Javascript Object
+ * {
+ * id: "bob",
+ * name: "Robert Smith",
+ * age: "25"
+ * }
+ * @param {Element} DOMElement to be convert into a FRISE game Object or
+ * Location
+ * @return {Object?|Location?} the resulting FRISE game Object or Location or
+ * null if the tagName of the DOMElement didn't begin with x-
*/
function makeGameObject(dom_object)
{
@@ -279,30 +292,32 @@ function toggleDisplay(id, display_type)
}
}
/**
- *
+ * Used to toggle the display or non-display of the main navigation bar
+ * on the side of game screen
*/
-function toggleOptions(elt_id, stop_pos)
+function toggleMainNav()
{
let game_content = elt('game-content');
- let elt_obj = elt(elt_id);
- if ((!elt_obj.style.left && !elt_obj.style.right) ||
- elt_obj.style.left == '0px' || elt_obj.style.right == '0px') {
- elt_obj.style.left = (stop_pos - 300) + 'px';
- if (is_mobile) {
- elt_obj.style.width = "240px";
- }
+ let nav_obj = elt('main-nav');
+ if ((!nav_obj.style.left && !nav_obj.style.right) ||
+ nav_obj.style.left == '0px' || nav_obj.style.right == '0px') {
game_content.style.left = "55px";
game_content.style.width = "calc(100% - 40px)";
- elt_obj.style.backgroundColor = 'white';
+ nav_obj.style.left = '-300px';
+ if (is_mobile) {
+ nav_obj.style.width = "240px";
+ game_content.style.width = "calc(100% - 70px)";
+ }
+ nav_obj.style.backgroundColor = 'white';
} else {
- elt_obj.style.left = '0px';
+ nav_obj.style.left = '0px';
game_content.style.left = "300px";
game_content.style.width = "calc(100% - 480px)";
if (is_mobile) {
- elt_obj.style.width = "100%";
+ nav_obj.style.width = "100%";
game_content.style.left = "100%";
}
- elt_obj.style.backgroundColor = 'lightgray';
+ nav_obj.style.backgroundColor = 'lightgray';
}
}
/**
@@ -329,7 +344,7 @@ function addListenersAnchors(anchors)
if (!anchor.classList.contains('disabled')) {
game.takeTurn(hash);
if (call_toggle) {
- toggleOptions('main-nav', 0);
+ toggleMainNav();
}
}
event.preventDefault();
@@ -382,7 +397,7 @@ function enableSavesAndInventory()
}
}
/**
- *
+ *
*/
function interpolateVariables(text)
{
@@ -438,6 +453,8 @@ class Location
disableSavesAndInventory();
let game_content = elt("game-content");
game_content.innerHTML = "";
+ game_content.scrollTop = 0;
+ game_content.scrollLeft = 0;
for (let section of this.present) {
if (!section[1]) {
continue;
@@ -468,25 +485,31 @@ class Location
*/
prepareControls()
{
- let game_content = elt("game-content");
- let input_fields = game_content.querySelectorAll("input");
- for (const input_field of input_fields) {
- let target_object = null;
- let target_name = input_field.getAttribute("data-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 = input_field.getAttribute("name");
- if (target_field) {
- input_field.value = target_object[target_field];
- input_field.addEventListener("input", (evt) =>
- {
- target_object[target_field] = input_field.value;
- });
+ const content_areas = ["main-nav", "game-content"];
+ for (const content_area of content_areas) {
+ let content = elt(content_area);
+ let input_fields = content.querySelectorAll("input");
+ for (const input_field of input_fields) {
+ let target_object = null;
+ let target_name = input_field.getAttribute("data-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 = input_field.getAttribute("name");
+ if (target_field) {
+ input_field.value = target_object[target_field];
+ if(!input_field.disabled) {
+ input_field.addEventListener("input", (evt) =>
+ {
+ target_object[target_field] =
+ input_field.value;
+ });
+ }
+ }
}
}
}
@@ -663,7 +686,7 @@ class Game
</div>
<div id="game-content"></div>`;
elt('main-toggle').onclick = (evt) => {
- toggleOptions('main-nav', 0);
+ toggleMainNav('main-nav', 0);
};
elt('next-history').onclick = (evt) => {
this.nextHistory();
@@ -687,7 +710,7 @@ class Game
let head = tag("head")[0];
head.innerHTML += `<meta name="viewport" `+
`content="width=device-width, initial-scale=1.0" >`
- toggleOptions('main-nav', 0);
+ toggleMainNav();
}
/**
*
@@ -921,7 +944,7 @@ class Game
saves_location['filename' + slot_number] = "...";
}
/**
- *
+ * Deletes all game saves from sessionStorage
*/
deleteSlotAll()
{
@@ -1039,7 +1062,9 @@ class Game
return true;
}
/**
+ * Moves the main character according to the provided url fragment.
*
+ * @param {string} hash a url fragment as described above
*/
moveMainCharacter(hash)
{
@@ -1119,7 +1144,10 @@ class Game
}
/**
* Module initialization function, used to set up the game object corresponding
- * to the current HTML document.
+ * to the current HTML document. If there is a current game state in
+ * sessionStorage it is used to initialize the game state, otherwise,
+ * the game state is based on the start of the game. After this state is
+ * set up, the current location is drawn to the game content area.
*/
async function initGame()
{