From 2d7a1b2a367d54e8675bbffa2e493c54de77beea Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 19 Mar 2022 16:48:30 +0100 Subject: [PATCH 1/7] show terminal header with link to sources --- .../javascript/WebTerminal/HtmlTerminal.css | 69 +++++++ .../javascript/WebTerminal/HtmlTerminal.js | 190 ++++++++++++++++++ .../javascript/WebTerminal/terminal.html | 116 +++++++++++ .../javascript/WebTerminal/terminal_tests.mjs | 30 +++ 00_Common/javascript/common.mjs | 21 ++ 00_Utilities/build-index.js | 46 +++-- 00_Utilities/javascript/style_terminal.css | 11 +- .../javascript/rockscissors.html | 10 - .../javascript/rockscissors.js | 107 ---------- .../javascript/rockscissors.mjs | 117 +++++++++++ 78_Sine_Wave/javascript/sinewave.html | 16 -- 78_Sine_Wave/javascript/sinewave.js | 22 -- 78_Sine_Wave/javascript/sinewave.mjs | 18 ++ HOW_TO_RUN_THE_GAMES.md | 16 +- index.html | 2 +- 15 files changed, 616 insertions(+), 175 deletions(-) create mode 100644 00_Common/javascript/WebTerminal/HtmlTerminal.css create mode 100644 00_Common/javascript/WebTerminal/HtmlTerminal.js create mode 100644 00_Common/javascript/WebTerminal/terminal.html create mode 100644 00_Common/javascript/WebTerminal/terminal_tests.mjs create mode 100644 00_Common/javascript/common.mjs delete mode 100644 74_Rock_Scissors_Paper/javascript/rockscissors.html delete mode 100644 74_Rock_Scissors_Paper/javascript/rockscissors.js create mode 100644 74_Rock_Scissors_Paper/javascript/rockscissors.mjs delete mode 100644 78_Sine_Wave/javascript/sinewave.html delete mode 100644 78_Sine_Wave/javascript/sinewave.js create mode 100644 78_Sine_Wave/javascript/sinewave.mjs diff --git a/00_Common/javascript/WebTerminal/HtmlTerminal.css b/00_Common/javascript/WebTerminal/HtmlTerminal.css new file mode 100644 index 00000000..54eac8d3 --- /dev/null +++ b/00_Common/javascript/WebTerminal/HtmlTerminal.css @@ -0,0 +1,69 @@ +:root { + --terminal-font: 1em "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 { + font: var(--terminal-font); + background-color: var(--background-color); + color: var(--text-color); + + overflow-y: scroll; + width: max-content; +} + +/* 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 00000000..4a095c87 --- /dev/null +++ b/00_Common/javascript/WebTerminal/HtmlTerminal.js @@ -0,0 +1,190 @@ +/** + * @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 {
+      this.#$prompt.innerHtml = '';
+      this.#$prompt.innerText =  this.#$prompt.innerText + e.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.match(/^\n*$/)) {
+      // empty new line
+      text.match(/\n/g).forEach(() => {
+        const $br = document.createElement("br");
+        this.$output.appendChild($br);
+      });
+    } else if (text && text.length && text.includes("\n")) {
+      const lines = text.split("\n");
+      lines.forEach((line) => {
+        if (line.length === 0 || line.match(/^\s*$/)) {
+          this.$output.appendChild(document.createElement("br"));
+        } else {
+          const $lineNode = this.#newLine(line);
+          this.$output.appendChild($lineNode);
+          //this.$node.appendChild(document.createElement("br"));
+        }
+      });
+    } else if (text && text.length) {
+      // simple line
+      const $lineNode = this.#newLine(text);
+      this.$output.appendChild($lineNode);
+    }
+
+    // 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 00000000..626617a1
--- /dev/null
+++ b/00_Common/javascript/WebTerminal/terminal.html
@@ -0,0 +1,116 @@
+
+  
+    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 00000000..804de3f2 --- /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(20), "Minimal node.js terminal 2"); + 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(""); + print("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 00000000..3fcc4212 --- /dev/null +++ b/00_Common/javascript/common.mjs @@ -0,0 +1,21 @@ + +export function print(...messages) { + process.stdout.write(messages.join("")); +} + +export function println(...messages) { + process.stdout.write(messages.join("") + "\n"); +} + +export function tab(count) { + return " ".repeat(count); +} + +export async function input(message = "") { + process.stdout.write(message + ' '); + return new Promise(resolve => { + process.stdin.on('data', (input) => { + resolve(input.toString().replace('\n', '')); + }); + }); +} diff --git a/00_Utilities/build-index.js b/00_Utilities/build-index.js index 2785957e..597ef191 100644 --- a/00_Utilities/build-index.js +++ b/00_Utilities/build-index.js @@ -16,14 +16,23 @@ const JAVASCRIPT_FOLDER = 'javascript'; const IGNORE_FOLDERS_START_WITH = ['.', '00_', 'buildJvm', 'Sudoku']; 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 +41,7 @@ function createGameLinks(game) {
  • `; } else { - return `
  • ${game.name}
  • `; + return creatFileLink(game.files[0], game.name); } } @@ -73,11 +82,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 +94,19 @@ function findHtmlFilesInFolder(folder) { // filter files only allow .html files const htmlFiles = files.filter(file => file.endsWith('.html')); + const mjsFiles = files.filter(file => file.endsWith('.mjs')); + const entries = [ + ...htmlFiles, + ...mjsFiles + ]; + - if (htmlFiles.length == 0) { - throw new Error(`Game "${folder}" is missing a html file in the "${folder}/${JAVASCRIPT_FOLDER}" folder`); + + 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 +127,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 +138,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 1301983f..dce94bb9 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/74_Rock_Scissors_Paper/javascript/rockscissors.html b/74_Rock_Scissors_Paper/javascript/rockscissors.html deleted file mode 100644 index 3b5199a4..00000000 --- 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 707712fb..00000000
    --- 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 00000000..be2328f2
    --- /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 c9ff7d9b..00000000
    --- 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 3823273d..00000000
    --- 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 00000000..bd390e75
    --- /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/HOW_TO_RUN_THE_GAMES.md b/HOW_TO_RUN_THE_GAMES.md
    index f1998ddd..688ee136 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 45e6ec84..9b38c627 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 From 7b0fc56b0dac8b1cde99240c01c3ef9cc562eb7f Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Tue, 29 Mar 2022 23:07:14 +0200 Subject: [PATCH 2/7] [javascript] 57 litquiz: use common functions --- .../{literature-quiz-node.mjs => litquiz.mjs} | 73 +++++++++---------- index.html | 2 +- 2 files changed, 36 insertions(+), 39 deletions(-) rename 57_Literature_Quiz/javascript/{literature-quiz-node.mjs => litquiz.mjs} (53%) 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 5dadd50b..89daef92 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/index.html b/index.html index 9b38c627..7d55fa1e 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 From 306e420869adba506c0d518e1d429d577398984a Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Tue, 29 Mar 2022 23:07:54 +0200 Subject: [PATCH 3/7] [javascript] terminal emulator: max width 60em --- .../javascript/WebTerminal/HtmlTerminal.css | 7 +++++-- 00_Common/javascript/WebTerminal/terminal.html | 17 +++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/00_Common/javascript/WebTerminal/HtmlTerminal.css b/00_Common/javascript/WebTerminal/HtmlTerminal.css index 54eac8d3..0150bb9a 100644 --- a/00_Common/javascript/WebTerminal/HtmlTerminal.css +++ b/00_Common/javascript/WebTerminal/HtmlTerminal.css @@ -1,5 +1,5 @@ :root { - --terminal-font: 1em "Lucida Console", "Courier New", monospace; + --terminal-font: 1rem "Lucida Console", "Courier New", monospace; --background-color: transparent; --text-color: var(--text); --prompt-char: '$ '; @@ -10,12 +10,15 @@ * 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: max-content; + width: 100%; + max-width: 60rem; + margin: 0 auto; } /* The terminal consits of multiple "line" elements diff --git a/00_Common/javascript/WebTerminal/terminal.html b/00_Common/javascript/WebTerminal/terminal.html index 626617a1..6256dffe 100644 --- a/00_Common/javascript/WebTerminal/terminal.html +++ b/00_Common/javascript/WebTerminal/terminal.html @@ -1,7 +1,7 @@ Minimal node.js terminal - + - - diff --git a/96_Word/javascript/word.mjs b/96_Word/javascript/word.mjs new file mode 100644 index 00000000..e4a1bd8e --- /dev/null +++ b/96_Word/javascript/word.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node +// WORD +// +// Converted from BASIC to Javascript by Oscar Toledo G. (nanochess) + +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", + "MIGHT", "FIRST", "CANDY", "CHAMP", "WOULD", + "CLUMP", "DOPEY"]; +const WORD_COUNT = WORDS.length; + +// Main control section +async function main() +{ + print(tab(33) + "WORD\n"); + print(tab(15) + "CREATIVE COMPUTING MORRISTOWN, NEW JERSEY\n"); + print("\n"); + print("\n"); + print("\n"); + print("I AM THINKING OF A WORD -- YOU GUESS IT. I WILL GIVE YOU\n"); + print("CLUES TO HELP YOU GET IT. GOOD LUCK!!\n"); + print("\n"); + print("\n"); + outer: while (1) { + print("\n"); + print("\n"); + print("YOU ARE STARTING A NEW GAME...\n"); + + const secretWord = WORDS[Math.floor(Math.random() * WORD_COUNT)]; + + let guessCount = 0; + // This array holds the letters which have been found in the correct position across all guesses + // For instance if the word is "PLAIN" and the guesses so far are + // "SHALL" ("A" correct) and "CLIMB" ("L" correct) then it will hold "-LA--" + const knownLetters = []; + for (let i = 0; i < 5; i++) + knownLetters[i] = "-"; + + let guess = undefined; + while (1) { + print("GUESS A FIVE LETTER WORD:"); + guess = (await input()).toUpperCase(); + guessCount++; + if (secretWord === guess) { + // The player has guessed correctly + break; + } + + if (guess.charAt(0) === "?") { + // Player has given up + print("THE SECRET WORD IS " + secretWord + "\n"); + print("\n"); + // Start a new game by going to the start of the outer while loop + continue outer; + } + + if (guess.length !== 5) { + print("YOU MUST GUESS A 5 LETTER WORD. START AGAIN.\n"); + print("\n"); + guessCount--; + continue; + } + + // Two things happen in this double loop: + // 1. Letters which are in both the guessed and secret words are put in the lettersInCommon array + // 2. Letters which are in the correct position in the guessed word are added to the knownLetters array + let lettersInCommonCount = 0; + const lettersInCommon = []; + for (let i = 0; i < 5; i++) {// loop round characters in secret word + let secretWordCharacter = secretWord.charAt(i); + for (let j = 0; j < 5; j++) {// loop round characters in guessed word + let guessedWordCharacter = guess.charAt(j); + if (secretWordCharacter === guessedWordCharacter) { + lettersInCommon[lettersInCommonCount] = guessedWordCharacter; + if (i === j) { + // Letter is in the exact position so add to the known letters array + knownLetters[j] = guessedWordCharacter; + } + lettersInCommonCount++; + } + } + } + + const lettersInCommonText = lettersInCommon.join(""); + print("THERE WERE " + lettersInCommonCount + " MATCHES AND THE COMMON LETTERS WERE... " + lettersInCommonText + "\n"); + + const knownLettersText = knownLetters.join(""); + print("FROM THE EXACT LETTER MATCHES, YOU KNOW............ " + knownLettersText + "\n"); + + if (knownLettersText === secretWord) { + guess = knownLettersText; + break; + } + + if (lettersInCommonCount <= 1) { + print("\n"); + print("IF YOU GIVE UP, TYPE '?' FOR YOUR NEXT GUESS.\n"); + print("\n"); + } + } + + print("YOU HAVE GUESSED THE WORD. IT TOOK " + guessCount + " GUESSES!\n"); + print("\n"); + + print("WANT TO PLAY AGAIN"); + const playAgainResponse = (await input()).toUpperCase(); + if (playAgainResponse !== "YES") + break; + } +} + +main(); diff --git a/index.html b/index.html index 79c3031f..a14d61f6 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 From a510d33ce393a5c6e61e161a7c555f9239433688 Mon Sep 17 00:00:00 2001 From: Alexander Wunschik Date: Sat, 2 Apr 2022 14:48:20 +0200 Subject: [PATCH 7/7] fix terminal emulator index link --- 00_Common/javascript/WebTerminal/terminal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/00_Common/javascript/WebTerminal/terminal.html b/00_Common/javascript/WebTerminal/terminal.html index 6256dffe..d9fab1d6 100644 --- a/00_Common/javascript/WebTerminal/terminal.html +++ b/00_Common/javascript/WebTerminal/terminal.html @@ -32,7 +32,7 @@
    -

    BASIC Computer Games

    +

    BASIC Computer Games