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
+    
+    
+    
+    
+  
+  
+    
+

BASIC Computer Games

+
+
+ + + + 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 ` -
  • - ${name} -
  • +
  • ${name.replace('.html', '')}
  • `; + } else if (file.endsWith('.mjs')) { + return ` +
  • ${name.replace('.mjs', '')} (node.js)
  • + `; + } else { + throw new Error(`Unknown file-type found: ${file}`); + } + } + + if (game.files.length > 1) { + const entries = game.files.map(file => { + return creatFileLink(file); }); return `
  • @@ -32,7 +45,7 @@ function createGameLinks(game) {
  • `; } else { - return `
  • ${game.name}
  • `; + return creatFileLink(game.files[0], game.name); } } @@ -73,11 +86,11 @@ function createIndexHtml(title, games) { `.trim().replace(/\s\s+/g, ''); } -function findHtmlFilesInFolder(folder) { +function findJSFilesInFolder(folder) { // filter folders that do not include a subfolder called "javascript" const hasJavascript = fs.existsSync(`${folder}/${JAVASCRIPT_FOLDER}`); if (!hasJavascript) { - throw new Error(`Game "${folder}" is missing a javascript implementation`); + throw new Error(`Game "${folder}" is missing a javascript folder`); } // get all files in the javascript folder @@ -85,12 +98,17 @@ function findHtmlFilesInFolder(folder) { // filter files only allow .html files const htmlFiles = files.filter(file => file.endsWith('.html')); - - if (htmlFiles.length == 0) { - throw new Error(`Game "${folder}" is missing a html file in the "${folder}/${JAVASCRIPT_FOLDER}" folder`); + const mjsFiles = files.filter(file => file.endsWith('.mjs')); + const entries = [ + ...htmlFiles, + ...mjsFiles + ].filter(file => !IGNORE_FILES.includes(file)); + + if (entries.length == 0) { + throw new Error(`Game "${folder}" is missing a HTML or node.js file in the folder "${folder}/${JAVASCRIPT_FOLDER}"`); } - return htmlFiles.map(htmlFile => path.join(folder, JAVASCRIPT_FOLDER, htmlFile)); + return entries.map(file => path.join(folder, JAVASCRIPT_FOLDER, file)); } function main() { @@ -111,10 +129,10 @@ function main() { // get name and javascript file from folder const games = folders.map(folder => { const name = folder.replace('_', ' '); - let htmlFiles; + let files; try { - htmlFiles = findHtmlFilesInFolder(folder); + files = findJSFilesInFolder(folder); } catch (error) { console.warn(`Game "${name}" is missing a javascript implementation: ${error.message}`); return null; @@ -122,7 +140,7 @@ function main() { return { name, - htmlFiles + files } }).filter(game => game !== null); diff --git a/00_Utilities/javascript/style_terminal.css b/00_Utilities/javascript/style_terminal.css index 1301983f5..dce94bb99 100644 --- a/00_Utilities/javascript/style_terminal.css +++ b/00_Utilities/javascript/style_terminal.css @@ -31,7 +31,11 @@ body { background-color: var(--background); color: var(--text); font: var(--font); - padding: 3rem; + margin: 0; +} + +#output { + padding: 1rem; } /* format input fields */ @@ -80,7 +84,10 @@ a:hover { } /* add all the face flicker effects (only on desktop) */ -@media screen and (min-width: 640px) { +@media screen and (min-width: 960px) { + main { + padding: 3rem; + } @keyframes flicker { 0% { opacity: 0.27861; diff --git a/57_Literature_Quiz/javascript/literature-quiz-node.mjs b/57_Literature_Quiz/javascript/litquiz.mjs similarity index 53% rename from 57_Literature_Quiz/javascript/literature-quiz-node.mjs rename to 57_Literature_Quiz/javascript/litquiz.mjs index 5dadd50b9..89daef928 100644 --- a/57_Literature_Quiz/javascript/literature-quiz-node.mjs +++ b/57_Literature_Quiz/javascript/litquiz.mjs @@ -1,37 +1,23 @@ -import * as readline from 'readline' +#!/usr/bin/env node -// start reusable code -async function input(prompt = "") { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }) +import { println, input } from '../../00_Common/javascript/common.mjs'; - return new Promise((resolve, _) => { - rl.setPrompt(prompt) - // show user the question - rl.prompt() - // listen for user answer, - // callback is triggered as soon as user hits enter key - rl.on('line', answer => { - rl.close() - // resolve the promise, with the input the user entered - resolve(answer) - }) - }) -} - -function println(message = "", align = "left"){ - let padColCount = 0 - if(align === "center"){ +function printAlign(message = "", align = "left") { + // process.stdout.columns is the number of spaces per line in the terminal + const maxWidth = process.stdout.columns + if (align === "center") { // calculate the amount of spaces required to center the message - // process.stdout.columns is the number of spaces per line in the terminal - padColCount = Math.round(process.stdout.columns / 2 + message.length / 2) + const padColCount = Math.round((process.stdout.columns-message.length)/2); + const padding = padColCount <= 0 ? '' : ' '.repeat(padColCount); + println(padding, message); + } else if (align === "right") { + const padColCount = Math.round(process.stdout.columns-message.length); + const padding = padColCount <= 0 ? '' : ' '.repeat(padColCount); + println(padding, message); + } else { + println(message); } - console.log(message.padStart(padColCount, " ")) } -// end reusable code - function equalIgnoreCase(correct, provided){ return correct.toString().toLowerCase() === provided.toString().toLowerCase() @@ -39,8 +25,10 @@ function equalIgnoreCase(correct, provided){ async function evaluateQuestion(question, answerOptions, correctAnswer, correctMessage, wrongMessage){ // ask the user to answer the given question + println(question); + println(answerOptions.map((answer, index) => `${index+1})${answer}`).join(', ')); // this is a blocking wait - const answer = await input(question + "\n" + answerOptions + "\n") + const answer = await input('?') const isCorrect = equalIgnoreCase(correctAnswer, answer) println(isCorrect ? correctMessage : wrongMessage) return isCorrect ? 1 : 0 @@ -48,31 +36,40 @@ async function evaluateQuestion(question, answerOptions, correctAnswer, correctM async function main(){ let score = 0 - println("LITERATURE QUIZ", "center") - println("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", "center") - println();println();println() + + printAlign("LITERATURE QUIZ", "center") + printAlign("CREATIVE COMPUTING MORRISTOWN, NEW JERSEY", "center") + println("\n\n") + + println("TEST YOUR KNOWLEDGE OF CHILDREN'S LITERATURE."); + println(); + println("THIS IS A MULTIPLE-CHOICE QUIZ."); + println("TYPE A 1, 2, 3, OR 4 AFTER THE QUESTION MARK."); + println(); + println("GOOD LUCK!"); + println("\n\n"); score += await evaluateQuestion("IN PINOCCHIO, WHAT WAS THE NAME OF THE CAT?", - "1)TIGGER, 2)CICERO, 3)FIGARO, 4)GUIPETTO", 3, + [ "TIGGER", "CICERO", "FIGARO", "GUIPETTO"], 3, "VERY GOOD! HERE'S ANOTHER.", "SORRY...FIGARO WAS HIS NAME.") println() score += await evaluateQuestion("FROM WHOSE GARDEN DID BUGS BUNNY STEAL THE CARROTS?", - "1)MR. NIXON'S, 2)ELMER FUDD'S, 3)CLEM JUDD'S, 4)STROMBOLI'S", 2, + [ "MR. NIXON'S", "ELMER FUDD'S", "CLEM JUDD'S", "STROMBOLI'S" ], 2, "PRETTY GOOD!", "TOO BAD...IT WAS ELMER FUDD'S GARDEN.") println() score += await evaluateQuestion("IN THE WIZARD OF OS, DOROTHY'S DOG WAS NAMED", - "1)CICERO, 2)TRIXIA, 3)KING, 4)TOTO", 4, + [ "CICERO", "TRIXIA", "KING", "TOTO" ], 4, "YEA! YOU'RE A REAL LITERATURE GIANT.", "BACK TO THE BOOKS,...TOTO WAS HIS NAME.") println() score += await evaluateQuestion("WHO WAS THE FAIR MAIDEN WHO ATE THE POISON APPLE", - "1)SLEEPING BEAUTY, 2)CINDERELLA, 3)SNOW WHITE, 4)WENDY", 3, + [ "SLEEPING BEAUTY", "CINDERELLA", "SNOW WHITE", "WENDY" ], 3, "GOOD MEMORY!", "OH, COME ON NOW...IT WAS SNOW WHITE.") - println();println() + println("\n") if(score === 4) { println("WOW! THAT'S SUPER! YOU REALLY KNOW YOUR NURSERY\n"+ diff --git a/74_Rock_Scissors_Paper/javascript/rockscissors.html b/74_Rock_Scissors_Paper/javascript/rockscissors.html deleted file mode 100644 index 3b5199a4c..000000000 --- a/74_Rock_Scissors_Paper/javascript/rockscissors.html +++ /dev/null @@ -1,10 +0,0 @@ - - -ROCK, SCISSORS, PAPER - - - -
    
    -
    -
    -
    diff --git a/74_Rock_Scissors_Paper/javascript/rockscissors.js b/74_Rock_Scissors_Paper/javascript/rockscissors.js
    deleted file mode 100644
    index 707712fb4..000000000
    --- a/74_Rock_Scissors_Paper/javascript/rockscissors.js
    +++ /dev/null
    @@ -1,107 +0,0 @@
    -// ROCK, SCISSORS, PAPER
    -//
    -// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
    -//
    -
    -function print(str)
    -{
    -    document.getElementById("output").appendChild(document.createTextNode(str));
    -}
    -
    -function input()
    -{
    -    var input_element;
    -    var input_str;
    -
    -    return new Promise(function (resolve) {
    -                       input_element = document.createElement("INPUT");
    -
    -                       print("? ");
    -                       input_element.setAttribute("type", "text");
    -                       input_element.setAttribute("length", "50");
    -                       document.getElementById("output").appendChild(input_element);
    -                       input_element.focus();
    -                       input_str = undefined;
    -                       input_element.addEventListener("keydown", function (event) {
    -                                                      if (event.keyCode == 13) {
    -                                                      input_str = input_element.value;
    -                                                      document.getElementById("output").removeChild(input_element);
    -                                                      print(input_str);
    -                                                      print("\n");
    -                                                      resolve(input_str);
    -                                                      }
    -                                                      });
    -                       });
    -}
    -
    -function tab(space)
    -{
    -    var str = "";
    -    while (space-- > 0)
    -        str += " ";
    -    return str;
    -}
    -
    -// Main control section
    -async function main()
    -{
    -    print(tab(21) + "GAME OF ROCK, SCISSORS, PAPER\n");
    -    print(tab(15) + "CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY\n");
    -    print("\n");
    -    print("\n");
    -    print("\n");
    -    while (1) {
    -        print("HOW MANY GAMES");
    -        q = parseInt(await input());
    -        if (q >= 11)
    -            print("SORRY, BUT WE AREN'T ALLOWED TO PLAY THAT MANY.\n");
    -        else
    -            break;
    -    }
    -    h = 0;  // Human
    -    c = 0;  // Computer
    -    for (g = 1; g <= q; g++ ) {
    -        print("\n");
    -        print("GAME NUMBER " + g + "\n");
    -        x = Math.floor(Math.random() * 3 + 1);
    -        while (1) {
    -            print("3=ROCK...2=SCISSORS...1=PAPER\n");
    -            print("1...2...3...WHAT'S YOUR CHOICE");
    -            k = parseInt(await input());
    -            if (k != 1 && k != 2 && k != 3)
    -                print("INVALID.\n");
    -            else
    -                break;
    -        }
    -        print("THIS IS MY CHOICE...");
    -        switch (x) {
    -            case 1:
    -                print("...PAPER\n");
    -                break;
    -            case 2:
    -                print("...SCISSORS\n");
    -                break;
    -            case 3:
    -                print("...ROCK\n");
    -                break;
    -        }
    -        if (x == k) {
    -            print("TIE GAME.  NO WINNER.\n");
    -        } else if ((x > k && (k != 1 || x != 3)) || (x == 1 && k == 3)) {
    -            print("WOW!  I WIN!!!\n");
    -            c++;
    -        } else {
    -            print("YOU WIN!!!\n");
    -            h++;
    -        }
    -    }
    -    print("\n");
    -    print("HERE IS THE FINAL GAME SCORE:\n");
    -    print("I HAVE WON " + c + " GAME(S).\n");
    -    print("YOU HAVE WON " + h + " GAME(S).\n");
    -    print("AND " + (q - (c + h)) + " GAME(S) ENDED IN A TIE.\n");
    -    print("\n");
    -    print("THANKS FOR PLAYING!!\n");
    -}
    -
    -main();
    diff --git a/74_Rock_Scissors_Paper/javascript/rockscissors.mjs b/74_Rock_Scissors_Paper/javascript/rockscissors.mjs
    new file mode 100644
    index 000000000..be2328f28
    --- /dev/null
    +++ b/74_Rock_Scissors_Paper/javascript/rockscissors.mjs
    @@ -0,0 +1,117 @@
    +#!/usr/bin/env node
    +// ROCK, SCISSORS, PAPER
    +//
    +// Converted from BASIC to Javascript by Alexander Wunschik (mojoaxel)
    +
    +import { println, tab, input } from '../../00_Common/javascript/common.mjs';
    +
    +let userWins = 0;
    +let computerWins = 0;
    +let ties = 0;
    +
    +// 30 INPUT "HOW MANY GAMES";Q
    +// 40 IF Q<11 THEN 60
    +// 50 PRINT "SORRY, BUT WE AREN'T ALLOWED TO PLAY THAT MANY.": GOTO 30
    +// 60 FOR G=1 TO Q
    +async function getGameCount() {
    +	let gameCount = await input("HOW MANY GAMES");
    +	if (gameCount > 10) {
    +		println("SORRY, BUT WE AREN'T ALLOWED TO PLAY THAT MANY.");
    +		return await getGameCount();
    +	}
    +	return gameCount;
    +}
    +
    +// #90 PRINT "3=ROCK...2=SCISSORS...1=PAPER"
    +// #100 INPUT "1...2...3...WHAT'S YOUR CHOICE";K
    +// #110 IF (K-1)*(K-2)*(K-3)<>0 THEN PRINT "INVALID.": GOTO 90
    +async function getUserInput() {
    +	println("3=ROCK...2=SCISSORS...1=PAPER");
    +	const userChoice = await input("1...2...3...WHAT'S YOUR CHOICE");
    +	if (userChoice < 1 || userChoice > 3) {
    +		println("INVALID.");
    +		return await getUserInput();
    +	}
    +	return userChoice;
    +}
    +
    +async function game() {
    +	// 10 PRINT TAB(21);"GAME OF ROCK, SCISSORS, PAPER"
    +	// 20 PRINT TAB(15);"CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY"
    +	// 25 PRINT:PRINT:PRINT
    +	println(tab(21), 'GAME OF ROCK, SCISSORS, PAPER');
    +	println(tab(15), 'CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY');
    +	println('\n\n');
    +
    +	let gameCount = await getGameCount();
    +
    +	async function playGame(gameNumber) {
    +		// 70 PRINT: PRINT "GAME NUMBER";G
    +		println("\nGAME NUMBER ", gameNumber);
    +
    +		const ROCK = 3;
    +		const SCISSORS = 2;
    +		const PAPER = 1;
    +
    +		const usersChoice = await getUserInput();
    +
    +		// 80 X=INT(RND(1)*3+1)
    +		const computersChoice = Math.floor(Math.random()*3) + 1;
    +
    +		// 120 PRINT "THIS IS MY CHOICE..."
    +		// 130 ON X GOTO 140,150,160
    +		// 140 PRINT "...PAPER": GOTO 170
    +		// 150 PRINT "...SCISSORS": GOTO 170
    +		// 160 PRINT "...ROCK"
    +		println("THIS IS MY CHOICE...", 
    +			computersChoice === PAPER ? "...PAPER" : 
    +				computersChoice === SCISSORS ? "...SCISSORS" : 
    +					"...ROCK");
    +
    +
    +		// 170 IF X=K THEN 250
    +		// 180 IF X>K THEN 230
    +		// 190 IF X=1 THEN 210
    +		// 200 PRINT "YOU WIN!!!":H=H+1: GOTO 260
    +		// 210 IF K<>3 THEN 200
    +		// 220 PRINT "WOW!  I WIN!!!":C=C+1:GOTO 260
    +		// 230 IF K<>1 OR X<>3 THEN 220
    +		// 240 GOTO 200
    +		// 250 PRINT "TIE GAME.  NO WINNER."
    +		if (computersChoice == usersChoice) {
    +			println("TIE GAME.  NO WINNER.");
    +			ties++;
    +		} else if (
    +			(computersChoice == ROCK && usersChoice == SCISSORS) ||
    +			(computersChoice == PAPER && usersChoice == ROCK) ||
    +			(computersChoice == SCISSORS && usersChoice == PAPER)
    +		) {
    +			println("WOW!  I WIN!!!");
    +			computerWins++;
    +		} else {
    +			println("YOU WIN!!!");
    +			userWins++;
    +		}
    +	}
    +
    +	for (let gameNumber = 1; gameNumber <= gameCount; gameNumber++) {
    +		await playGame(gameNumber);
    +		// 260 NEXT G
    +	}
    +
    +	// 270 PRINT: PRINT "HERE IS THE FINAL GAME SCORE:"
    +	// 280 PRINT "I HAVE WON";C;"GAME(S)."
    +	// 290 PRINT "YOU HAVE WON";H;"GAME(S)."
    +	// 300 PRINT "AND";Q-(C+H20);"GAME(S) ENDED IN A TIE."
    +	println("\nHERE IS THE FINAL GAME SCORE:");
    +	println(`I HAVE WON ${computerWins} GAME(S).`);
    +	println(`YOU HAVE WON ${userWins} GAME(S).`);
    +	println(`AND ${ties} GAME(S) ENDED IN A TIE.`);
    +
    +	// 310 PRINT: PRINT "THANKS FOR PLAYING!!"
    +	println("\nTHANKS FOR PLAYING!!");
    +	
    +	// 320 END
    +	process.exit(0);
    +}
    +game();
    diff --git a/78_Sine_Wave/javascript/sinewave.html b/78_Sine_Wave/javascript/sinewave.html
    deleted file mode 100644
    index c9ff7d9b6..000000000
    --- a/78_Sine_Wave/javascript/sinewave.html
    +++ /dev/null
    @@ -1,16 +0,0 @@
    -
    -
    -SINE WAVE
    -
    -
    -
    -
    
    -
    -
    -
    -
    diff --git a/78_Sine_Wave/javascript/sinewave.js b/78_Sine_Wave/javascript/sinewave.js
    deleted file mode 100644
    index 3823273d9..000000000
    --- a/78_Sine_Wave/javascript/sinewave.js
    +++ /dev/null
    @@ -1,22 +0,0 @@
    -print(tab(30), "SINE WAVE");
    -print(tab(15), "CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY");
    -print("\n\n\n\n");
    -
    -// REMARKABLE PROGRAM BY DAVID AHL
    -// Transliterated to Javascript by Les Orchard 
    -
    -let toggleWord = true;
    -
    -for (let step = 0; step < 40; step += 0.25) {
    -  let indent = Math.floor(26 + 25 * Math.sin(step));
    -  print(tab(indent), toggleWord ? "CREATIVE" : "COMPUTING");
    -  toggleWord = !toggleWord;
    -}
    -
    -function print(...messages) {
    -  console.log(messages.join(" "));
    -}
    -
    -function tab(count) {
    -  return " ".repeat(count);
    -}
    diff --git a/78_Sine_Wave/javascript/sinewave.mjs b/78_Sine_Wave/javascript/sinewave.mjs
    new file mode 100644
    index 000000000..bd390e75d
    --- /dev/null
    +++ b/78_Sine_Wave/javascript/sinewave.mjs
    @@ -0,0 +1,18 @@
    +#!/usr/bin/env node
    +
    +import { println, tab } from '../../00_Common/javascript/common.mjs';
    +
    +println(tab(30), "SINE WAVE");
    +println(tab(15), "CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY");
    +println("\n".repeat(4));
    +
    +// REMARKABLE PROGRAM BY DAVID AHL
    +// Transliterated to Javascript by Les Orchard 
    +
    +let toggleWord = true;
    +
    +for (let step = 0; step < 40; step += 0.25) {
    +  let indent = Math.floor(26 + 25 * Math.sin(step));
    +  println(tab(indent), toggleWord ? "CREATIVE" : "COMPUTING");
    +  toggleWord = !toggleWord;
    +}
    diff --git a/84_Super_Star_Trek/javascript/cli.mjs b/84_Super_Star_Trek/javascript/cli.mjs
    index bacceff85..4277c2323 100644
    --- a/84_Super_Star_Trek/javascript/cli.mjs
    +++ b/84_Super_Star_Trek/javascript/cli.mjs
    @@ -2,12 +2,9 @@ import {
       onExit,
       onPrint,
       onInput,
    -  setGameOptions,
    -  getGameState,
       gameMain,
     } from "./superstartrek.mjs";
     
    -import util from "util";
     import readline from "readline";
     
     onExit(function exit() {
    diff --git a/96_Word/javascript/word.html b/96_Word/javascript/word.html
    deleted file mode 100644
    index 02419ce4a..000000000
    --- a/96_Word/javascript/word.html
    +++ /dev/null
    @@ -1,10 +0,0 @@
    -
    -
    -WORD
    -
    -
    -
    -
    
    -
    -
    -
    diff --git a/96_Word/javascript/word.js b/96_Word/javascript/word.mjs
    similarity index 74%
    rename from 96_Word/javascript/word.js
    rename to 96_Word/javascript/word.mjs
    index 3d7e3a5ac..e4a1bd8ed 100644
    --- a/96_Word/javascript/word.js
    +++ b/96_Word/javascript/word.mjs
    @@ -1,43 +1,9 @@
    +#!/usr/bin/env node
     // WORD
     //
     // Converted from BASIC to Javascript by Oscar Toledo G. (nanochess)
    -//
    -
    -function print(str)
    -{
    -    document.getElementById("output").appendChild(document.createTextNode(str));
    -}
    -
    -function input()
    -{
    -
    -    return new Promise(function (resolve) {
    -                       const input_element = document.createElement("INPUT");
    -
    -                       print("? ");
    -                       input_element.setAttribute("type", "text");
    -                       input_element.setAttribute("length", "50");
    -                       document.getElementById("output").appendChild(input_element);
    -                       input_element.focus();
    -                       input_element.addEventListener("keydown", function (event) {
    -                                                      if (event.keyCode === 13) {
    -                                                          const input_str = input_element.value;
    -                                                          document.getElementById("output").removeChild(input_element);
    -                                                          print(input_str);
    -                                                          print("\n");
    -                                                          resolve(input_str);
    -                                                      }
    -                                                      });
    -                       });
    -}
     
    -function tab(space)
    -{
    -    let str = "";
    -    while (space-- > 0)
    -        str += " ";
    -    return str;
    -}
    +import { print, tab, input } from '../../00_Common/javascript/common.mjs';
     
     // These are the words that the game knows about> If you want a bigger challenge you could add more words to the array
     const WORDS = ["DINKY", "SMOKE", "WATER", "GLASS", "TRAIN",
    @@ -74,7 +40,7 @@ async function main()
     
             let guess = undefined;
             while (1) {
    -            print("GUESS A FIVE LETTER WORD");
    +            print("GUESS A FIVE LETTER WORD:");
                 guess = (await input()).toUpperCase();
                 guessCount++;
                 if (secretWord === guess) {
    diff --git a/HOW_TO_RUN_THE_GAMES.md b/HOW_TO_RUN_THE_GAMES.md
    index f1998ddd4..688ee136a 100644
    --- a/HOW_TO_RUN_THE_GAMES.md
    +++ b/HOW_TO_RUN_THE_GAMES.md
    @@ -40,9 +40,21 @@ or if you are **using JDK11 or later** you can now execute a self contained java
     
     ## javascript
     
    -The javascript examples can be run from within your web browser:
    +There are two ways of javascript implementations:
     
    -1. Simply open the corresponding `.html` file from your web browser.
    +### browser
    +
    +The html examples can be run from within your web browser. Simply open the corresponding `.html` file from your web browser.
    +
    +### node.js
    +
    +Some games are implemented as a [node.js](https://nodejs.org/) script. In this case there is no `*.html` file in the folder.
    +
    +1. [install node.js](https://nodejs.org/en/download/) for your system.
    +1. change directory to the root of this repository (e.g. `cd basic-computer-games`).
    +1. from a terminal call the script you want to run (e.g. `node 78_Sine_Wave/javascript/sinewave.mjs`).
    +
    +_Hint: Normally javascript files have a `*.js` extension. We are using `*.mjs` to let node know , that we are using [ES modules](https://nodejs.org/docs/latest/api/esm.html#modules-ecmascript-modules) instead of [CommonJS](https://nodejs.org/docs/latest/api/modules.html#modules-commonjs-modules)._
     
     ## kotlin
     
    diff --git a/index.html b/index.html
    index 45e6ec84b..a14d61f64 100644
    --- a/index.html
    +++ b/index.html
    @@ -1 +1 @@
    -BASIC Computer Games

    BASIC Computer Games

    \ No newline at end of file +BASIC Computer Games

    BASIC Computer Games

    \ No newline at end of file