From 555f8de2aeb3e55fa730396cce83b835cdca68ac Mon Sep 17 00:00:00 2001 From: Joshua Bell Date: Sat, 10 Feb 2024 18:22:08 -0800 Subject: [PATCH] Simulate Thunderclock in Slot 4 See the new sample for details/example. Note that reading 12/24-hour time formats that have colons will not currently work, as the colons will terminate input and result in invisible ?EXTRA IGNORED errors. Fixes #44 --- cm/basic.js | 2 +- dos.js | 134 +++++++++++++++++++++++++++++--- reference.html | 8 +- samples/index.txt | 1 + samples/sample.thunderclock.txt | 21 +++++ tty.js | 3 +- 6 files changed, 154 insertions(+), 15 deletions(-) create mode 100644 samples/sample.thunderclock.txt diff --git a/cm/basic.js b/cm/basic.js index b628a72..65cb60f 100644 --- a/cm/basic.js +++ b/cm/basic.js @@ -44,7 +44,7 @@ CodeMirror.defineMode('basic', function(config, parserConfig) { "HPLOT": STATEMENT, "HTAB": STATEMENT, "IF": STATEMENT, - "IN#": UNSUPPORTED, + "IN#": STATEMENT, "INPUT": STATEMENT, "INT": FUNCTION, "INVERSE": STATEMENT, diff --git a/dos.js b/dos.js index f0a3e9c..72acc41 100644 --- a/dos.js +++ b/dos.js @@ -39,6 +39,11 @@ function DOS(tty) { tty_readChar, tty_writeChar, + // Hooked I/O routines + hooked_readLine, + hooked_readChar, + hooked_writeChar, + // character output state commandBuffer = "", commandMode = false, @@ -358,8 +363,25 @@ function DOS(tty) { args = parseArgs(m[2]); if (slot === 0) { if (tty.setFirmwareActive) { tty.setFirmwareActive(false); } + hooked_writeChar = tty_writeChar; } else if (slot === 3) { if (tty.setFirmwareActive) { tty.setFirmwareActive(true); } + hooked_writeChar = tty_writeChar; + } else if (slot === 4) { + hooked_writeChar = clock_writeChar; + } else { + doserror(DOSErrors.RANGE_ERROR); + } + } else if ((m = command.match(/^IN#\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/))) { + // IN# slot Direct input to slot + slot = Number(m[1]); + args = parseArgs(m[2]); + if (slot === 0 || slot === 3) { + hooked_readLine = tty_readLine; + hooked_readChar = tty_readChar; + } else if (slot === 4) { + hooked_readLine = clock_readLine; + hooked_readChar = clock_readChar; } else { doserror(DOSErrors.RANGE_ERROR); } @@ -381,7 +403,15 @@ function DOS(tty) { tty_readChar = tty.readChar; tty_writeChar = tty.writeChar; - tty.readLine = function dos_readLine(callback, prompt) { + hooked_readLine = tty_readLine; + hooked_readChar = tty_readChar; + hooked_writeChar = tty_writeChar; + + tty.readLine = dos_readLine; + tty.readChar = dos_readChar; + tty.writeChar = dos_writeChar; + + function dos_readLine(callback, prompt) { var string = "", c, data, len, fp, buffer; if (mode === "r") { @@ -415,12 +445,11 @@ function DOS(tty) { // Non-blocking return setTimeout(function() { callback(string); }, 0); } else { - tty_readLine(callback, prompt); + hooked_readLine(callback, prompt); } + } - }; - - tty.readChar = function dos_readChar(callback) { + function dos_readChar(callback) { var character = ""; if (mode === "r") { @@ -432,17 +461,17 @@ function DOS(tty) { activebuffer.filepointer += 1; if (monico & MON_I && tty) { - tty_writeChar(character); + hooked_writeChar(character); } // Non-blocking return setTimeout(function() { callback(character); }, 0); } else { - tty_readChar(callback); + hooked_readChar(callback); } - }; + } - tty.writeChar = function dos_writeChar(c) { + function dos_writeChar(c) { if (commandMode) { if (c === "\r") { @@ -463,7 +492,7 @@ function DOS(tty) { var buf, d; if (monico & MON_O) { - tty_writeChar(c); + hooked_writeChar(c); } buf = activebuffer; @@ -484,8 +513,89 @@ function DOS(tty) { buf.filepointer += 1; } else { - tty_writeChar(c); + hooked_writeChar(c); + } + } + + //---------------------------------------------------------------------- + // Clock routine - vaguely ThunderClock compatible + //---------------------------------------------------------------------- + + var clockbuf = ''; + function clock_writeChar(c) { + var DAYS = ['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']; + var MONTHS = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', + 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC']; + function spad2(s) { + return ('00' + String(s)).slice(-2); + } + function zpad2(s) { + return ('00' + String(s)).slice(-2); + } + function zpad3(s) { + return ('000' + String(s)).slice(-3); } - }; // writeChar + var now = new Date(); + switch (c) { + default: + case '%': // AM/PM ASCII string mode - e.g. "TUE MAY 12 4:32:55 PM" + case '>': // AM/PM ASCII string mode - e.g. "TUE MAY 12 4:32:55 PM" + clockbuf = + DAYS[now.getDay()] + ' ' + + MONTHS[now.getMonth()] + ' ' + + now.getDate() + ' ' + + spad2((now.getHours() === 0 ? 12 : now.getHours() > 12 ? now.getHours() - 12 : now.getHours())) + ':' + + zpad2(now.getMinutes()) + ':' + + zpad2(now.getSeconds()) + ' ' + + (now.getHours() < 12 ? 'AM' : 'PM'); + break; + case '&': // 24 hour ASCII string - e.g. "TUE MAY 12 16:32:55" + case '<': // 24 hour ASCII string - e.g. "TUE MAY 12 16:32:55" + clockbuf = + DAYS[now.getDay()] + ' ' + + MONTHS[now.getMonth()] + ' ' + + now.getDate() + ' ' + + spad2(now.getHours()) + ':' + + zpad2(now.getMinutes()) + ':' + + zpad2(now.getSeconds()) + ' ' + + (now.getHours() < 12 ? 'AM' : 'PM'); + break; + case ' ': // Mountain Computer Apple Clock Format - e.g. "05/12 16;32;55.000" + clockbuf = + zpad2(now.getMonth()+1) + '/' + + zpad2(now.getDate()) + ' ' + + zpad2(now.getHours()) + ';' + + zpad2(now.getMinutes()) + ';' + + zpad2(now.getSeconds()) + '.' + + zpad3(now.getMilliseconds()); + break; + case '#': // Numeric format, e.g. MO,DW,DT,HR,MN,SEC + clockbuf = [ + now.getMonth()+1, + now.getDay(), + now.getDate(), + now.getHours(), + now.getMinutes(), + now.getSeconds() + ].join(','); + break; + } + clockbuf += '\r'; + } + function clock_readLine(callback, prompt) { + tty.writeString(prompt); // TODO: Correct? Newline? + var tmp = clockbuf; + clockbuf = ''; + callback(tmp); + } + function clock_readChar(callback) { + if (!clockbuf.length) { + callback('\r'); + } else { + var c = clockbuf.substring(0, 1); + clockbuf = clockbuf.slice(1); + callback(c); + } + } } diff --git a/reference.html b/reference.html index 63686eb..627c260 100644 --- a/reference.html +++ b/reference.html @@ -169,7 +169,6 @@ can be literals (unquoted strings), strings, or numbers

Native Platform Interaction - NOT IMPLEMENTED

HIMEM: aexpr
Set upper address of variable memory -
IN# aexpr
Direct input from slot
LOMEM: aexpr
Set lower address of variable memory
WAIT aexpr, aexpr [, aexpr]
Wait until memory location masked by second argument equals third argument (or zero)
@@ -234,6 +233,13 @@ can be literals (unquoted strings), strings, or numbers + +
IN# aexpr
Direct output to slot + diff --git a/samples/index.txt b/samples/index.txt index 383e2bb..54d14d9 100644 --- a/samples/index.txt +++ b/samples/index.txt @@ -93,6 +93,7 @@ sample.onelinetrain One Liner Train (Chris ten Den) sample.piglatin Pig Latin Translator (Gregg Buntin) sample.nuclear Nuclear Power Plant (Stephen R. Berggren c/o Kevin Riggle) sample.factors Prime Factors (Cristiano Trabuio) +sample.thunderclock Thunderclock (incomplete) # ____________________________________________ # Traveller RPG Utilities diff --git a/samples/sample.thunderclock.txt b/samples/sample.thunderclock.txt new file mode 100644 index 0000000..311dd0c --- /dev/null +++ b/samples/sample.thunderclock.txt @@ -0,0 +1,21 @@ +10 PRINT "Mountain Clock Format" +20 PRINT CHR$(4);"PR#4" +30 PRINT CHR$(4);"IN#4" +40 INPUT " ";T$ +50 PRINT CHR$(4);"PR#0" +60 PRINT CHR$(4);"IN#0" +70 PRINT T$ +80 PRINT + +100 PRINT "Numeric Format" +110 PRINT CHR$(4);"PR#4" +120 PRINT CHR$(4);"IN#4" +130 INPUT "#";MO,DW,DT,HR,MN,SEC +140 PRINT CHR$(4);"PR#0" +150 PRINT CHR$(4);"IN#0" +160 PRINT "Month: ";MO +161 PRINT "Day: ";DW +162 PRINT "Date: ";DT +163 PRINT "Hours: ";HR +164 PRINT "Minutes: ";MN +165 PRINT "Seconds: ";SEC diff --git a/tty.js b/tty.js index 3cbf0ae..b9f555d 100644 --- a/tty.js +++ b/tty.js @@ -236,7 +236,8 @@ function TTY(screenElement, keyboardElement) { }; this.setFirmwareActive = function setFirmwareActive(active) { - init(active, 24, active ? 80 : 40); + if (active !== firmwareActive) + init(active, 24, active ? 80 : 40); }; this.isFirmwareActive = function isFirmwareActive() {