diff --git a/00_Common/javascript/WebTerminal/HtmlTerminal.css b/00_Common/javascript/WebTerminal/HtmlTerminal.css
new file mode 100644
index 000000000..0150bb9ad
--- /dev/null
+++ b/00_Common/javascript/WebTerminal/HtmlTerminal.css
@@ -0,0 +1,72 @@
+:root {
+ --terminal-font: 1rem "Lucida Console", "Courier New", monospace;
+ --background-color: transparent;
+ --text-color: var(--text);
+ --prompt-char: '$ ';
+ --cursor-char: '_';
+}
+
+/* Basic terminal style.
+ * If you wan t to overwrite them use custom properties (variables).
+ */
+.terminal {
+ display: block;
+ font: var(--terminal-font);
+ background-color: var(--background-color);
+ color: var(--text-color);
+
+ overflow-y: scroll;
+ width: 100%;
+ max-width: 60rem;
+ margin: 0 auto;
+}
+
+/* The terminal consits of multiple "line" elements
+ * Because sometimes we want to add a simulates "prompt" at the end of a line
+ * we need to make it an "inline" element and handle line-breaks
+ * by adding elements */
+.terminal pre.line {
+ display: inline-block;
+ font: var(--terminal-font);
+ margin: 0;
+ padding: 0;
+}
+
+/* The "terminal" has one "prompt" element.
+ * This prompt is not any kind of input, but just a simple
+ * with an id "prompt" and a
+ */
+@keyframes prompt-blink {
+ 100% {
+ opacity: 0;
+ }
+}
+.terminal #prompt {
+ display: inline-block;
+}
+.terminal #prompt:before {
+ display: inline-block;
+ content: var(--prompt-char);
+ font: var(--terminal-font);
+}
+.terminal #prompt:after {
+ display: inline-block;
+ content: var(--cursor-char);
+ background: var(--text);
+ animation: prompt-blink 1s steps(2) infinite;
+ width: 0.75rem;
+ opacity: 1;
+}
+
+
+/* Terminal scrollbar */
+::-webkit-scrollbar {
+ width: 3px;
+ height: 3px;
+}
+::-webkit-scrollbar-track {
+ background: var(--background-color);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--text-color);
+}
diff --git a/00_Common/javascript/WebTerminal/HtmlTerminal.js b/00_Common/javascript/WebTerminal/HtmlTerminal.js
new file mode 100644
index 000000000..c8d529d91
--- /dev/null
+++ b/00_Common/javascript/WebTerminal/HtmlTerminal.js
@@ -0,0 +1,195 @@
+/**
+ * @class HtmlTerminal
+ *
+ * This class is a very basic implementation of a "terminal" in the browser.
+ * It provides simple functions like "write" and an "input" Callback.
+ *
+ * @license AGPL-2.0
+ * @author Alexaner Wunschik
+ */
+class HtmlTerminal {
+
+ /**
+ * Input callback.
+ * If the prompt is activated by calling the input function
+ * a callback is defined. If this member is not set this means
+ * the prompt is not active.
+ *
+ * @private
+ * @type {function}
+ */
+ #inputCallback = undefined;
+
+ /**
+ * A html element to show a "prompt".
+ *
+ * @private
+ * @type {HTMLElement}
+ */
+ #$prompt = undefined;
+
+ /**
+ * Constructor
+ * Creates a basic terminal simulation on the provided HTMLElement.
+ *
+ * @param {HTMLElement} $output - a dom element
+ */
+ constructor($output) {
+ // Store the output DOM element in a local variable.
+ this.$output = $output;
+
+ // Clear terminal.
+ this.clear();
+
+ // Add the call "terminal" to the $output element.
+ this.$output.classList.add('terminal');
+
+ // Create a prompt element.
+ // This element gets added if input is needed
+ this.#$prompt = document.createElement("span");
+ this.#$prompt.setAttribute("id", "prompt");
+ this.#$prompt.innerText = "";
+
+ //TODO: this handler shouls be only on the propt element and only active if cursor is visible
+ document.addEventListener("keyup", this.#handleKey.bind(this));
+ }
+
+ /**
+ * Creates a new HTMLElement with the given text content.
+ * This element than gets added to the $output as a new "line".
+ *
+ * @private
+ * @memberof MinimalTerminal
+ * @param {String} text - text that should be displayed in the new "line".
+ * @returns {HTMLElement} return a new DOM Element
+ */
+ #newLine(text) {
+ const $lineNode = document.createElement("pre");
+ $lineNode.classList.add("line");
+ $lineNode.innerText = text;
+ return $lineNode;
+ }
+
+ /**
+ * TODO
+ *
+ * @private
+ * @param {*} e
+ */
+ #handleKey(e) {
+ // if no input-callback is defined
+ if (!this.#inputCallback) {
+ return;
+ }
+
+ if (e.keyCode === 13 /* ENTER */) {
+ // create a new line with the text input and remove the prompt
+ const text = this.#$prompt.innerText;
+ this.write(text + "\n");
+ this.#$prompt.innerText = "";
+ this.#$prompt.remove();
+
+ // return the inputed text
+ this.#inputCallback(text);
+
+ // remove the callback and the key handler
+ this.#inputCallback = undefined;
+ } else if (e.keyCode === 8 /* BACKSPACE */) {
+ this.#$prompt.innerText = this.#$prompt.innerText.slice(0, -1);
+ } else if (
+ e.keyCode == 16 // "Shift"
+ || e.keyCode == 17 // "Control"
+ || e.keyCode == 20 // "CapsLock"
+ || !e.key.match(/^[a-z0-9!"ยง#$%&'()*+,.\/:;<=>?@\[\] ^_`{|}~-]$/i)
+ ) {
+ // ignore non-visible characters
+ return e;
+ } else {
+ this.#$prompt.innerHtml = '';
+ const key = e.shiftKey ? e.key.toUpperCase() : e.key;
+ this.#$prompt.innerText = this.#$prompt.innerText + key;
+ }
+ }
+
+ /**
+ * Clear the terminal.
+ * Remove all lines.
+ *
+ * @public
+ */
+ clear() {
+ this.$output.innerText = "";
+ }
+
+ /**
+ * TODO:
+ *
+ * @public
+ * @param {*} htmlContent
+ */
+ inserHtml(htmlContent) {
+ const $htmlNode = document.createElement("div");
+ $htmlNode.innerHTML = htmlContent;
+ this.$output.appendChild($htmlNode);
+ document.body.scrollTo(0, document.body.scrollHeight);
+ }
+
+ /**
+ * Write a text to the terminal.
+ * By default there is no linebreak at the end of a new line
+ * except the line ensd with a "\n".
+ * If the given text has multible linebreaks, multibe lines are inserted.
+ *
+ * @public
+ * @param {string} text
+ */
+ write(text) {
+ if (!text || text.length <= 0) {
+ // empty line
+ this.$output.appendChild(document.createElement("br"));
+ } else if (text.endsWith("\n")) {
+ // single line with linebrank
+ const $lineNode = this.#newLine(text);
+ this.$output.appendChild(this.#newLine(text));
+ this.$output.appendChild(document.createElement("br"));
+ } else if (text.includes("\n")) {
+ // multible lines
+ const lines = text.split("\n");
+ lines.forEach((line) => {
+ this.write(line);
+ });
+ } else {
+ // single line
+ this.$output.appendChild(this.#newLine(text));
+ }
+
+ // scroll to the buttom of the page
+ document.body.scrollTo(0, document.body.scrollHeight);
+ }
+
+ /**
+ * Like "write" but with a newline at the end.
+ *
+ * @public
+ * @param {*} text
+ */
+ writeln(text) {
+ this.write(text + "\n");
+ }
+
+ /**
+ * Query from user input.
+ * This is done by adding a input-element at the end of the terminal,
+ * that showes a prompt and a blinking cursor.
+ * If a key is pressed the input is added to the prompt element.
+ * The input ends with a linebreak.
+ *
+ * @public
+ * @param {*} callback
+ */
+ input(callback) {
+ // show prompt with a blinking prompt
+ this.$output.appendChild(this.#$prompt);
+ this.#inputCallback = callback;
+ }
+}
diff --git a/00_Common/javascript/WebTerminal/terminal.html b/00_Common/javascript/WebTerminal/terminal.html
new file mode 100644
index 000000000..d9fab1d63
--- /dev/null
+++ b/00_Common/javascript/WebTerminal/terminal.html
@@ -0,0 +1,129 @@
+
+
+ Minimal node.js terminal
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/00_Common/javascript/WebTerminal/terminal_tests.mjs b/00_Common/javascript/WebTerminal/terminal_tests.mjs
new file mode 100644
index 000000000..d2470c958
--- /dev/null
+++ b/00_Common/javascript/WebTerminal/terminal_tests.mjs
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+
+import { print, println, tab, input } from '../common.mjs';
+
+async function main() {
+ println(tab(30), "Minimal node.js terminal emulator");
+ println();
+ println(tab(0), "tab 0");
+ println(tab(5), "tab 5");
+ println(tab(10), "tab 10");
+ println(tab(15), "tab 15");
+ println(tab(20), "tab 20");
+ println(tab(25), "tab 25");
+ println();
+ println("1234567890", " _ ", "ABCDEFGHIJKLMNOPRSTUVWXYZ");
+ println();
+ print("\nHallo"); print(" "); print("Welt!\n");
+ println("");
+ println("Line 1\nLine 2\nLine 3\nLine 4");
+ println("----------------------------------------------");
+
+ const value = await input("input");
+ println(`input value was "${value}"`);
+
+ println("End of script");
+
+ // 320 END
+ process.exit(0);
+}
+main();
diff --git a/00_Common/javascript/common.mjs b/00_Common/javascript/common.mjs
new file mode 100644
index 000000000..765f5cc2e
--- /dev/null
+++ b/00_Common/javascript/common.mjs
@@ -0,0 +1,67 @@
+/**
+ * Print multible strings to the terminal.
+ * Strings get concatinated (add together) without any space betweent them.
+ * There will be no newline at the end!
+ * If you want a linebrak at the end use `println`.
+ *
+ * This function is normally used if you want to put something on the screen
+ * and later add some content to the same line.
+ * For normal output (similar to `console.log`) use `println`!
+ *
+ * @param {...string} messages - the strings to print to the terminal.
+ */
+export function print(...messages) {
+ process.stdout.write(messages.join(""));
+}
+
+/**
+ * Add multible strings as a new line to the terminal.
+ * Strings get concatinated (add together) without any space betweent them.
+ * There will be a newline at the end!
+ * If you want the terminal to stay active on the current line use `print`.
+ *
+ * @param {...any} messages - the strings to print to the terminal.
+ */
+export function println(...messages) {
+ process.stdout.write(messages.join("") + "\n");
+}
+
+/**
+ * Create an empty string with a given length
+ *
+ * @param {number} length - the length of the string in space-characters.
+ * @returns {string} returns a string containing only ampty spaces with a length of `count`.
+ */
+export function tab(length) {
+ return " ".repeat(length);
+}
+
+/**
+ * Read input from the keyboard and return it as a string.
+ * TODO: to would be very helpfull to only allow a certain class of input (numbers, letters)
+ * TODO: also we could convert all inputs to uppercase (where it makes sence).
+ *
+ * @param {string=''} message - a message or question to print befor the input.
+ * @returns {Promise} - returns the entered text as a string
+ * @async
+ */
+export async function input(message = '') {
+ /* First we need to print the mesage
+ * We append a space by default to seperate the message from the imput.
+ * TODO: If the message already contains a space at the end this is not needed! */
+ process.stdout.write(message + ' ');
+
+ return new Promise(resolve => {
+ process.stdin.on('data', (input) => {
+ /* onData returns a Buffer.
+ * First we need to convert it into a string. */
+ const data = input.toString();
+
+ /* The result fo onData is a string ending with an `\n`.
+ * We just need the actual content so let's remove the newline at the end: */
+ const content = data[data.length] === '\n' ? data.slice(0, -1) : data;
+
+ resolve(content);
+ });
+ });
+}
diff --git a/00_Utilities/build-index.js b/00_Utilities/build-index.js
index 2785957e5..1ed862efe 100644
--- a/00_Utilities/build-index.js
+++ b/00_Utilities/build-index.js
@@ -14,16 +14,29 @@ const path = require('path');
const TITLE = 'BASIC Computer Games';
const JAVASCRIPT_FOLDER = 'javascript';
const IGNORE_FOLDERS_START_WITH = ['.', '00_', 'buildJvm', 'Sudoku'];
+const IGNORE_FILES = [
+ // "84 Super Star Trek" has it's own node/js implementation (using xterm)
+ 'cli.mjs', 'superstartrek.mjs'
+];
function createGameLinks(game) {
- if (game.htmlFiles.length > 1) {
- const entries = game.htmlFiles.map(htmlFile => {
- const name = path.basename(htmlFile).replace('.html', '');
+ const creatFileLink = (file, name = path.basename(file)) => {
+ if (file.endsWith('.html')) {
return `
-