From a49ed766c94efed9cd8951a86ad263dec70add54 Mon Sep 17 00:00:00 2001 From: Joshua Bell Date: Wed, 8 Feb 2012 22:37:04 -0500 Subject: [PATCH] dos2unix; factor out index.js; use addEvent/getClassList shims; change screen style using CSS classes --- bell.js | 110 +-- dos.js | 1018 +++++++++++++------------- feed.js | 132 ++-- hires.js | 304 ++++---- index.htm | 654 ++++++----------- index.js | 236 ++++++ lores.js | 384 +++++----- paddles.js | 280 +++---- printer.js | 190 ++--- reference.htm | 1140 ++++++++++++++--------------- styles.css | 1938 +++++++++++++++++++++++++------------------------ tty.js | 1808 ++++++++++++++++++++++----------------------- 12 files changed, 4091 insertions(+), 4103 deletions(-) create mode 100644 index.js diff --git a/bell.js b/bell.js index 04939e6..5dea965 100644 --- a/bell.js +++ b/bell.js @@ -1,55 +1,55 @@ -// -// Applesoft BASIC in Javascript -// Bell - play an audio file for the CHR$(7) "BEL" beep -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -var Bell; -if (typeof Bell !== 'function') { - Bell = function(base) { - - var tag; - - // Prefer HTML5 audio - tag = document.createElement('audio'); - if (typeof tag.canPlayType === 'function') { - tag.setAttribute('preload', 'true'); - if (tag.canPlayType('audio/mp3')) { - tag.setAttribute('src', base + 'bell.mp3'); - } else if (tag.canPlayType('audio/ogg')) { - tag.setAttribute('src', base + 'bell.ogg'); - } else if (tag.canPlayType('audio/wav')) { - tag.setAttribute('src', base + 'bell.wav'); - } - this.play = function() { tag.play(); }; - this.stop = function() { tag.pause(); tag.currentTime = 0; }; - return; - } - - // Fallback for IE<9 - tag = document.createElement('bgsound'); - if ('loop' in tag) { - tag.src = base + 'bell.wav'; - tag.loop = 1; - this.play = function() { document.body.appendChild(tag); }; - this.stop = function() { document.body.removeChild(tag); }; - return; - } - - this.play = function() { }; - this.stop = function() { }; - }; -} +// +// Applesoft BASIC in Javascript +// Bell - play an audio file for the CHR$(7) "BEL" beep +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +var Bell; +if (typeof Bell !== 'function') { + Bell = function(base) { + + var tag; + + // Prefer HTML5 audio + tag = document.createElement('audio'); + if (typeof tag.canPlayType === 'function') { + tag.setAttribute('preload', 'true'); + if (tag.canPlayType('audio/mp3')) { + tag.setAttribute('src', base + 'bell.mp3'); + } else if (tag.canPlayType('audio/ogg')) { + tag.setAttribute('src', base + 'bell.ogg'); + } else if (tag.canPlayType('audio/wav')) { + tag.setAttribute('src', base + 'bell.wav'); + } + this.play = function() { tag.play(); }; + this.stop = function() { tag.pause(); tag.currentTime = 0; }; + return; + } + + // Fallback for IE<9 + tag = document.createElement('bgsound'); + if ('loop' in tag) { + tag.src = base + 'bell.wav'; + tag.loop = 1; + this.play = function() { document.body.appendChild(tag); }; + this.stop = function() { document.body.removeChild(tag); }; + return; + } + + this.play = function() { }; + this.stop = function() { }; + }; +} diff --git a/dos.js b/dos.js index 65d39b3..5751287 100644 --- a/dos.js +++ b/dos.js @@ -1,509 +1,509 @@ -// -// Applesoft BASIC in Javascript -// DOS Emulation - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Usage: -// var dos = new DOS( tty ) // hooks tty's writeChar/readChar/readLine -// dos.reset() // Close all open buffers - -/*global basic*/ - -function DOS(tty) { - /*jslint bitwise: false, browser: true*/ - - var DOSErrors = { - LANGUAGE_NOT_AVAILABLE: [1, "Language not available"], - RANGE_ERROR: [2, 'Range error'], - WRITE_PROTECTED: [4, 'Write protected'], - END_OF_DATA: [5, 'End of data'], - FILE_NOT_FOUND: [6, 'File not found'], - VOLUME_MISMATCH: [7, 'Volume mismatch'], - IO_ERROR: [8, 'I/O error'], - DISK_FULL: [9, 'Disk full'], - FILE_LOCKED: [10, 'File locked'], - INVALID_OPTION: [11, 'Invalid option'], - NO_BUFFERS_AVAILABLE: [12, 'No buffers available'], - FILE_TYPE_MISMATCH: [13, 'File type mismatch'], - PROGRAM_TOO_LARGE: [14, 'Program too large'], - NOT_DIRECT_COMMAND: [15, 'Not direct command'] - }, - - STORAGE_PREFIX = 'vfs/', - - // For MON/NOMON - MON_I = 1, - MON_C = 2, - MON_O = 4, - - // Original versions of hooked I/O routines - tty_readLine, - tty_readChar, - tty_writeChar, - - // character output state - commandBuffer = "", - commandMode = false, - - // I/O buffers - buffers = {}, - activebuffer = null, - mode = "", - - // other state - monico = 0; - - function doserror(msg) { - throw new basic.RuntimeError(msg[1], msg[0]); - } - - // Internal - crack arguments e.g. ",S6,D1" - function parseArgs(str, opts) { - opts = opts || ''; - - // Set these to zero so they're always defined when passed into command handlers - var args = { - V: 0, // Volume - D: 0, // Drive - S: 0, // Slot - L: 0, // Length - R: 0, // Record/Relative - B: 0, // Byte - A: 0, // Address - C: undefined, // Echo Commands - I: undefined, // Echo Input - O: undefined // Echo Output - }; - - while (str.match(/^,?\s*([VDSLRBACIO])\s*([0-9]+|\$[0-9A-Fa-f]+)?\s*([\x20-\x7E]*)/)) { - if (opts.indexOf(RegExp.$1) === -1) { - doserror(DOSErrors.INVALID_OPTION); - } - args[RegExp.$1] = Number(RegExp.$2); - str = RegExp.$3; - } - - if (str.length > 0) { - doserror(DOSErrors.INVALID_OPTION); - } - - return args; - } - - - - //---------------------------------------------------------------------- - // Browser-side VFS - //---------------------------------------------------------------------- - - function vfs_set(key, value) { - return window.localStorage.setItem(STORAGE_PREFIX + key, encodeURIComponent(value)); - } - function vfs_get(key) { - var item = window.localStorage.getItem(STORAGE_PREFIX + key); - return item !== null ? decodeURIComponent(item) : null; - } - function vfs_remove(key) { - return window.localStorage.removeItem(STORAGE_PREFIX + key); - } - - - //---------------------------------------------------------------------- - // Implementation - //---------------------------------------------------------------------- - - this.reset = function _reset() { - buffers = {}; - activebuffer = null; - mode = ""; - }; - - function unlink(filename) { - var item = vfs_get(filename); - - if (item === null) { - doserror(DOSErrors.FILE_NOT_FOUND); - } - - vfs_remove(filename); - } - - function rename(oldname, newname) { - var item = vfs_get(oldname); - - if (item === null) { - doserror(DOSErrors.FILE_NOT_FOUND); - } - - vfs_remove(oldname); - vfs_set(newname, item); - } - - function open(filename, recordlength) { - if (recordlength === 0) { - // Sequential access - recordlength = 1; - } - - // Peek in the VFS cache first - var file = vfs_get(filename), - req, url, async; - if (file === null) { - // Not cached - do a synchronous XmlHttpRequest for the file here - req = new XMLHttpRequest(); - try { - url = "vfs/" + encodeURIComponent(filename.replace(/\./g, '_')) + ".txt"; - async = false; - req.open("GET", url, async); - req.send(null); - if (req.status === 200 || req.status === 0) { // 0 for file:// protocol - file = req.responseText.replace(/\r\n/g, "\r"); - vfs_set(filename, file); - } - } catch (e) { - // File doesn't exist - APPEND/READ will fail - throw e; - } - } - - // Create a buffer for the file - buffers[filename] = { - file: file, - recordlength: recordlength, - recordnum: 0, - filepointer: 0 - }; - } - - function append(filename, recordlength) { - // Normal open logic - open(filename, recordlength); - - if (!buffers.hasOwnProperty(filename)) { - doserror(DOSErrors.FILE_NOT_FOUND); - } - - var buf = buffers[filename]; - - // Then seek to the end of the file - buf.filepointer = buf.file.length; - buf.recordnum = Math.floor(buf.filepointer / buf.recordlength); - } - - function close(filename) { - var buf, fn; - - // If not specified, close all buffers - if (!filename) { - for (fn in buffers) { - if (buffers.hasOwnProperty(fn)) { - close(fn); - } - } - return; - } - - buf = buffers[filename]; - if (buf) { - // flush changes to "disk" - vfs_set(filename, buf.file); - - delete buffers[filename]; - if (buf === activebuffer) { - activebuffer = null; - mode = ""; - } - } - } - - function read(filename, recordnum, bytenum) { - var buf = buffers[filename]; - if (!buf) { - // Open file if no such named buffer, but don't create it - open(filename, 0); - buf = buffers[filename]; - } - - if (buf.file === null) { - doserror(DOSErrors.FILE_NOT_FOUND); - } - - // Set the file position - buf.recordnum = recordnum; - buf.filepointer = buf.recordlength * recordnum + bytenum; - - // Set the active buffer into read mode - activebuffer = buf; - mode = "r"; - } - - function write(filename, recordnum, bytenum) { - var buf = buffers[filename]; - if (!buf) { - // Must open the file before writing - doserror(DOSErrors.FILE_NOT_FOUND); - } - - if (buf.file === null) { - // If we still don't have it, create in VFS if necessary - vfs_set(filename, ''); - buf.file = ''; - } - - // Set up the file position - buf.recordnum = recordnum; - if (buf.recordlength > 1) { - buf.filepointer = buf.recordlength * recordnum; - } - buf.filepointer += bytenum; - - // Set the active buffer into write mode - activebuffer = buf; - mode = "w"; - } - - function position(filename, records) { - var buf = buffers[filename]; - if (!buf) { - // Open file if no such named buffer, but don't create it - open(filename, 0, false); - buf = buffers[filename]; - } - - // Set up the file position - buf.recordnum += records; - buf.filepointer += buf.recordlength * records; - - } - - //---------------------------------------------------------------------- - // Command Dispatch - //---------------------------------------------------------------------- - function executeCommand(command) { - // Delegate to various commands - // http://www.xs4all.nl/~fjkraan/comp/apple2faq/app2doscmdfaq.html - // http://www.textfiles.com/apple/ANATOMY/ - - var filename, filename2, args, slot; - - if (monico & MON_C && tty) { - tty.writeString(command + "\r"); - } - - if (command.match(/^MON([\x20-\x7E]*)/)) { - // MON[,C][,I][,O] Traces DOS 3.3 commands ('Commands', 'Input' and 'Output') - args = parseArgs(RegExp.$1, 'ICO'); - - if (args.I !== undefined) { - monico |= MON_I; - } - if (args.C !== undefined) { - monico |= MON_C; - } - if (args.O !== undefined) { - monico |= MON_O; - } - - } else if (command.match(/^NOMON([\x20-\x7E]*)/)) { - // NOMON[,C][,I][,O] Cancels tracing of DOS 3.3 commands ('Commands', 'Input' and 'Output') - args = parseArgs(RegExp.$1, 'ICO'); - if (args.I !== undefined) { - monico &= ~MON_I; - } - if (args.C !== undefined) { - monico &= ~MON_C; - } - if (args.O !== undefined) { - monico &= ~MON_O; - } - } else if (command.match(/^OPEN\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // OPEN filename[,Llen] Opens a text file. - filename = RegExp.$1; - args = parseArgs(RegExp.$2, 'L'); - open(filename, args.L); - } else if (command.match(/^APPEND\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // APPEND filename Appends to a text file. - filename = RegExp.$1; - args = parseArgs(RegExp.$2); - append(filename, args.L); - } else if (command.match(/^CLOSE\s*([\x20-\x2B\x2D-\x7E]+)?(,[\x20-\x7E]*)?/)) { - // CLOSE [filename] Closes specified (or all) open text files. - filename = RegExp.$1; - close(filename); - } else if (command.match(/^POSITION\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // POSITION filename[,Rnum] Advances position in text file. - filename = RegExp.$1; - args = parseArgs(RegExp.$2, 'R'); - position(filename, args.R); - } else if (command.match(/^READ\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // READ filename[,Rnum][,Bbyte] Reads from a text file. - filename = RegExp.$1; - args = parseArgs(RegExp.$2, 'RB'); - read(filename, args.R, args.B); - } else if (command.match(/^WRITE\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // WRITE filename[,Rnum][,Bbyte] Writes to a text file. - filename = RegExp.$1; - args = parseArgs(RegExp.$2, 'RB'); - write(filename, args.R, args.B); - } else if (command.match(/^DELETE\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // DELETE filename Delete a file - filename = RegExp.$1; - args = parseArgs(RegExp.$2); - unlink(filename); - } else if (command.match(/^RENAME\s*([\x20-\x2B\x2D-\x7E]+),\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // RENAME filename,filename Rename a file - filename = RegExp.$1; - filename2 = RegExp.$2; - args = parseArgs(RegExp.$3); - rename(filename, filename2); - } else if (command.match(/^PR#\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { - // PR# slot Direct output to slot - slot = Number(RegExp.$1); - args = parseArgs(RegExp.$2); - if (slot === 0) { - if (tty.setFirmwareActive) { tty.setFirmwareActive(false); } - } else if (slot === 3) { - if (tty.setFirmwareActive) { tty.setFirmwareActive(true); } - } else { - doserror(DOSErrors.RANGE_ERROR); - } - } else if (command.match(/^$/)) { - // Null command - terminates a READ/WRITE, but doesn't CLOSE - // (leaves record length intact on open buffer) - activebuffer = null; - mode = ""; - } else { - doserror(DOSErrors.INVALID_OPTION); - } - } - - - //---------------------------------------------------------------------- - // Install TTY Hooks - //---------------------------------------------------------------------- - tty_readLine = tty.readLine; - tty_readChar = tty.readChar; - tty_writeChar = tty.writeChar; - - tty.readLine = function _dos_readLine(callback, prompt) { - - var string = "", c, data, len, fp, buffer; - if (mode === "r") { - // Cache for performance - data = activebuffer.file; - len = data.length; - fp = activebuffer.filepointer; - - if (fp >= len) { - doserror(DOSErrors.END_OF_DATA); - } - - buffer = []; - while (fp < len) { - // Sequential Access - c = data[fp]; - fp += 1; - if (c === "\r" || c === "\n" || c === "\x00") { - break; - } else { - buffer.push(c); - } - } - activebuffer.filepointer = fp; - string = buffer.join(""); - - if (monico & MON_I) { - tty.writeString(prompt + string + "\r"); - } - - // Non-blocking return - setTimeout(function() { callback(string); }, 0); - } else { - tty_readLine(callback, prompt); - } - - }; - - tty.readChar = function _dos_readChar(callback) { - - var character = ""; - if (mode === "r") { - if (activebuffer.filepointer >= activebuffer.file.length) { - doserror(DOSErrors.END_OF_DATA); - } - - character = activebuffer.file[activebuffer.filepointer]; - activebuffer.filepointer += 1; - - if (monico & MON_I && tty) { - tty_writeChar(character); - } - - // Non-blocking return - setTimeout(function() { callback(character); }, 0); - } else { - tty_readChar(callback); - } - }; - - tty.writeChar = function _dos_writeChar(c) { - - if (commandMode) { - if (c === "\r") { - commandMode = false; - executeCommand(commandBuffer); - commandBuffer = ""; - } else { - commandBuffer += c; - } - return; - } else if (c === "\x04") { - commandBuffer = ""; - commandMode = true; - return; - } - - if (mode === "w") { - var buf, d; - - if (monico & MON_O) { - tty_writeChar(c); - } - - buf = activebuffer; - // Extend file to necessary length - while (buf.filepointer > buf.file.length) { - buf.file += "\x00"; - } - - // Append or insert character - if (buf.filepointer === buf.file.length) { - buf.file += c; - } else { - d = buf.file.substring(0, buf.filepointer); - d += c; - d += buf.file.substring(buf.filepointer + 1); - buf.file = d; - } - - buf.filepointer += 1; - } else { - tty_writeChar(c); - } - - }; // writeChar -} +// +// Applesoft BASIC in Javascript +// DOS Emulation + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Usage: +// var dos = new DOS( tty ) // hooks tty's writeChar/readChar/readLine +// dos.reset() // Close all open buffers + +/*global basic*/ + +function DOS(tty) { + /*jslint bitwise: false, browser: true*/ + + var DOSErrors = { + LANGUAGE_NOT_AVAILABLE: [1, "Language not available"], + RANGE_ERROR: [2, 'Range error'], + WRITE_PROTECTED: [4, 'Write protected'], + END_OF_DATA: [5, 'End of data'], + FILE_NOT_FOUND: [6, 'File not found'], + VOLUME_MISMATCH: [7, 'Volume mismatch'], + IO_ERROR: [8, 'I/O error'], + DISK_FULL: [9, 'Disk full'], + FILE_LOCKED: [10, 'File locked'], + INVALID_OPTION: [11, 'Invalid option'], + NO_BUFFERS_AVAILABLE: [12, 'No buffers available'], + FILE_TYPE_MISMATCH: [13, 'File type mismatch'], + PROGRAM_TOO_LARGE: [14, 'Program too large'], + NOT_DIRECT_COMMAND: [15, 'Not direct command'] + }, + + STORAGE_PREFIX = 'vfs/', + + // For MON/NOMON + MON_I = 1, + MON_C = 2, + MON_O = 4, + + // Original versions of hooked I/O routines + tty_readLine, + tty_readChar, + tty_writeChar, + + // character output state + commandBuffer = "", + commandMode = false, + + // I/O buffers + buffers = {}, + activebuffer = null, + mode = "", + + // other state + monico = 0; + + function doserror(msg) { + throw new basic.RuntimeError(msg[1], msg[0]); + } + + // Internal - crack arguments e.g. ",S6,D1" + function parseArgs(str, opts) { + opts = opts || ''; + + // Set these to zero so they're always defined when passed into command handlers + var args = { + V: 0, // Volume + D: 0, // Drive + S: 0, // Slot + L: 0, // Length + R: 0, // Record/Relative + B: 0, // Byte + A: 0, // Address + C: undefined, // Echo Commands + I: undefined, // Echo Input + O: undefined // Echo Output + }; + + while (str.match(/^,?\s*([VDSLRBACIO])\s*([0-9]+|\$[0-9A-Fa-f]+)?\s*([\x20-\x7E]*)/)) { + if (opts.indexOf(RegExp.$1) === -1) { + doserror(DOSErrors.INVALID_OPTION); + } + args[RegExp.$1] = Number(RegExp.$2); + str = RegExp.$3; + } + + if (str.length > 0) { + doserror(DOSErrors.INVALID_OPTION); + } + + return args; + } + + + + //---------------------------------------------------------------------- + // Browser-side VFS + //---------------------------------------------------------------------- + + function vfs_set(key, value) { + return window.localStorage.setItem(STORAGE_PREFIX + key, encodeURIComponent(value)); + } + function vfs_get(key) { + var item = window.localStorage.getItem(STORAGE_PREFIX + key); + return item !== null ? decodeURIComponent(item) : null; + } + function vfs_remove(key) { + return window.localStorage.removeItem(STORAGE_PREFIX + key); + } + + + //---------------------------------------------------------------------- + // Implementation + //---------------------------------------------------------------------- + + this.reset = function _reset() { + buffers = {}; + activebuffer = null; + mode = ""; + }; + + function unlink(filename) { + var item = vfs_get(filename); + + if (item === null) { + doserror(DOSErrors.FILE_NOT_FOUND); + } + + vfs_remove(filename); + } + + function rename(oldname, newname) { + var item = vfs_get(oldname); + + if (item === null) { + doserror(DOSErrors.FILE_NOT_FOUND); + } + + vfs_remove(oldname); + vfs_set(newname, item); + } + + function open(filename, recordlength) { + if (recordlength === 0) { + // Sequential access + recordlength = 1; + } + + // Peek in the VFS cache first + var file = vfs_get(filename), + req, url, async; + if (file === null) { + // Not cached - do a synchronous XmlHttpRequest for the file here + req = new XMLHttpRequest(); + try { + url = "vfs/" + encodeURIComponent(filename.replace(/\./g, '_')) + ".txt"; + async = false; + req.open("GET", url, async); + req.send(null); + if (req.status === 200 || req.status === 0) { // 0 for file:// protocol + file = req.responseText.replace(/\r\n/g, "\r"); + vfs_set(filename, file); + } + } catch (e) { + // File doesn't exist - APPEND/READ will fail + throw e; + } + } + + // Create a buffer for the file + buffers[filename] = { + file: file, + recordlength: recordlength, + recordnum: 0, + filepointer: 0 + }; + } + + function append(filename, recordlength) { + // Normal open logic + open(filename, recordlength); + + if (!buffers.hasOwnProperty(filename)) { + doserror(DOSErrors.FILE_NOT_FOUND); + } + + var buf = buffers[filename]; + + // Then seek to the end of the file + buf.filepointer = buf.file.length; + buf.recordnum = Math.floor(buf.filepointer / buf.recordlength); + } + + function close(filename) { + var buf, fn; + + // If not specified, close all buffers + if (!filename) { + for (fn in buffers) { + if (buffers.hasOwnProperty(fn)) { + close(fn); + } + } + return; + } + + buf = buffers[filename]; + if (buf) { + // flush changes to "disk" + vfs_set(filename, buf.file); + + delete buffers[filename]; + if (buf === activebuffer) { + activebuffer = null; + mode = ""; + } + } + } + + function read(filename, recordnum, bytenum) { + var buf = buffers[filename]; + if (!buf) { + // Open file if no such named buffer, but don't create it + open(filename, 0); + buf = buffers[filename]; + } + + if (buf.file === null) { + doserror(DOSErrors.FILE_NOT_FOUND); + } + + // Set the file position + buf.recordnum = recordnum; + buf.filepointer = buf.recordlength * recordnum + bytenum; + + // Set the active buffer into read mode + activebuffer = buf; + mode = "r"; + } + + function write(filename, recordnum, bytenum) { + var buf = buffers[filename]; + if (!buf) { + // Must open the file before writing + doserror(DOSErrors.FILE_NOT_FOUND); + } + + if (buf.file === null) { + // If we still don't have it, create in VFS if necessary + vfs_set(filename, ''); + buf.file = ''; + } + + // Set up the file position + buf.recordnum = recordnum; + if (buf.recordlength > 1) { + buf.filepointer = buf.recordlength * recordnum; + } + buf.filepointer += bytenum; + + // Set the active buffer into write mode + activebuffer = buf; + mode = "w"; + } + + function position(filename, records) { + var buf = buffers[filename]; + if (!buf) { + // Open file if no such named buffer, but don't create it + open(filename, 0, false); + buf = buffers[filename]; + } + + // Set up the file position + buf.recordnum += records; + buf.filepointer += buf.recordlength * records; + + } + + //---------------------------------------------------------------------- + // Command Dispatch + //---------------------------------------------------------------------- + function executeCommand(command) { + // Delegate to various commands + // http://www.xs4all.nl/~fjkraan/comp/apple2faq/app2doscmdfaq.html + // http://www.textfiles.com/apple/ANATOMY/ + + var filename, filename2, args, slot; + + if (monico & MON_C && tty) { + tty.writeString(command + "\r"); + } + + if (command.match(/^MON([\x20-\x7E]*)/)) { + // MON[,C][,I][,O] Traces DOS 3.3 commands ('Commands', 'Input' and 'Output') + args = parseArgs(RegExp.$1, 'ICO'); + + if (args.I !== undefined) { + monico |= MON_I; + } + if (args.C !== undefined) { + monico |= MON_C; + } + if (args.O !== undefined) { + monico |= MON_O; + } + + } else if (command.match(/^NOMON([\x20-\x7E]*)/)) { + // NOMON[,C][,I][,O] Cancels tracing of DOS 3.3 commands ('Commands', 'Input' and 'Output') + args = parseArgs(RegExp.$1, 'ICO'); + if (args.I !== undefined) { + monico &= ~MON_I; + } + if (args.C !== undefined) { + monico &= ~MON_C; + } + if (args.O !== undefined) { + monico &= ~MON_O; + } + } else if (command.match(/^OPEN\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // OPEN filename[,Llen] Opens a text file. + filename = RegExp.$1; + args = parseArgs(RegExp.$2, 'L'); + open(filename, args.L); + } else if (command.match(/^APPEND\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // APPEND filename Appends to a text file. + filename = RegExp.$1; + args = parseArgs(RegExp.$2); + append(filename, args.L); + } else if (command.match(/^CLOSE\s*([\x20-\x2B\x2D-\x7E]+)?(,[\x20-\x7E]*)?/)) { + // CLOSE [filename] Closes specified (or all) open text files. + filename = RegExp.$1; + close(filename); + } else if (command.match(/^POSITION\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // POSITION filename[,Rnum] Advances position in text file. + filename = RegExp.$1; + args = parseArgs(RegExp.$2, 'R'); + position(filename, args.R); + } else if (command.match(/^READ\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // READ filename[,Rnum][,Bbyte] Reads from a text file. + filename = RegExp.$1; + args = parseArgs(RegExp.$2, 'RB'); + read(filename, args.R, args.B); + } else if (command.match(/^WRITE\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // WRITE filename[,Rnum][,Bbyte] Writes to a text file. + filename = RegExp.$1; + args = parseArgs(RegExp.$2, 'RB'); + write(filename, args.R, args.B); + } else if (command.match(/^DELETE\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // DELETE filename Delete a file + filename = RegExp.$1; + args = parseArgs(RegExp.$2); + unlink(filename); + } else if (command.match(/^RENAME\s*([\x20-\x2B\x2D-\x7E]+),\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // RENAME filename,filename Rename a file + filename = RegExp.$1; + filename2 = RegExp.$2; + args = parseArgs(RegExp.$3); + rename(filename, filename2); + } else if (command.match(/^PR#\s*([\x20-\x2B\x2D-\x7E]+)(,[\x20-\x7E]*)?/)) { + // PR# slot Direct output to slot + slot = Number(RegExp.$1); + args = parseArgs(RegExp.$2); + if (slot === 0) { + if (tty.setFirmwareActive) { tty.setFirmwareActive(false); } + } else if (slot === 3) { + if (tty.setFirmwareActive) { tty.setFirmwareActive(true); } + } else { + doserror(DOSErrors.RANGE_ERROR); + } + } else if (command.match(/^$/)) { + // Null command - terminates a READ/WRITE, but doesn't CLOSE + // (leaves record length intact on open buffer) + activebuffer = null; + mode = ""; + } else { + doserror(DOSErrors.INVALID_OPTION); + } + } + + + //---------------------------------------------------------------------- + // Install TTY Hooks + //---------------------------------------------------------------------- + tty_readLine = tty.readLine; + tty_readChar = tty.readChar; + tty_writeChar = tty.writeChar; + + tty.readLine = function _dos_readLine(callback, prompt) { + + var string = "", c, data, len, fp, buffer; + if (mode === "r") { + // Cache for performance + data = activebuffer.file; + len = data.length; + fp = activebuffer.filepointer; + + if (fp >= len) { + doserror(DOSErrors.END_OF_DATA); + } + + buffer = []; + while (fp < len) { + // Sequential Access + c = data[fp]; + fp += 1; + if (c === "\r" || c === "\n" || c === "\x00") { + break; + } else { + buffer.push(c); + } + } + activebuffer.filepointer = fp; + string = buffer.join(""); + + if (monico & MON_I) { + tty.writeString(prompt + string + "\r"); + } + + // Non-blocking return + setTimeout(function() { callback(string); }, 0); + } else { + tty_readLine(callback, prompt); + } + + }; + + tty.readChar = function _dos_readChar(callback) { + + var character = ""; + if (mode === "r") { + if (activebuffer.filepointer >= activebuffer.file.length) { + doserror(DOSErrors.END_OF_DATA); + } + + character = activebuffer.file[activebuffer.filepointer]; + activebuffer.filepointer += 1; + + if (monico & MON_I && tty) { + tty_writeChar(character); + } + + // Non-blocking return + setTimeout(function() { callback(character); }, 0); + } else { + tty_readChar(callback); + } + }; + + tty.writeChar = function _dos_writeChar(c) { + + if (commandMode) { + if (c === "\r") { + commandMode = false; + executeCommand(commandBuffer); + commandBuffer = ""; + } else { + commandBuffer += c; + } + return; + } else if (c === "\x04") { + commandBuffer = ""; + commandMode = true; + return; + } + + if (mode === "w") { + var buf, d; + + if (monico & MON_O) { + tty_writeChar(c); + } + + buf = activebuffer; + // Extend file to necessary length + while (buf.filepointer > buf.file.length) { + buf.file += "\x00"; + } + + // Append or insert character + if (buf.filepointer === buf.file.length) { + buf.file += c; + } else { + d = buf.file.substring(0, buf.filepointer); + d += c; + d += buf.file.substring(buf.filepointer + 1); + buf.file = d; + } + + buf.filepointer += 1; + } else { + tty_writeChar(c); + } + + }; // writeChar +} diff --git a/feed.js b/feed.js index 1373b60..59afaa0 100644 --- a/feed.js +++ b/feed.js @@ -1,66 +1,66 @@ -// -// Atom to HTML - fetch a feed, inject it as dl/dt/dd -// - -// Copyright (C) 2009-2010 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -function atomToHtml(uri, element) { - - var READYSTATE_UNINITIALIZED = 0; - var READYSTATE_LOADING = 1; - var READYSTATE_LOADED = 2; - var READYSTATE_INTERACTIVE = 3; - var READYSTATE_COMPLETE = 4; - - var xhr = new XMLHttpRequest(); - var async = true; - xhr.open("GET", uri, async); - xhr.onreadystatechange = function() { - if (xhr.readyState === READYSTATE_COMPLETE) { - if ((xhr.status === 200 || xhr.status === 0) && xhr.responseXML) { - var doc = xhr.responseXML; - var entries = doc.getElementsByTagName('entry'); - var html = []; - - html.push('
'); - - for (var i = 0; i < entries.length; i += 1) { - var entry = entries[i]; - try { - var entryHTML = []; - entryHTML.push('
', entry.getElementsByTagName('title')[0].childNodes[0].nodeValue); - entryHTML.push('
', entry.getElementsByTagName('content')[0].childNodes[0].nodeValue); - html.push(entryHTML.join('')); - } catch (e) { - if (console && console.log) { console.log("Error:", e); } - } - } - - html.push('
'); - - element.innerHTML = html.join(''); - } else { - element.innerHTML = 'Unable to load feed'; - } - } - }; - - try { - xhr.send(null); - } catch (e) { - element.innerHTML = 'Unable to load feed'; - } -} - +// +// Atom to HTML - fetch a feed, inject it as dl/dt/dd +// + +// Copyright (C) 2009-2010 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +function atomToHtml(uri, element) { + + var READYSTATE_UNINITIALIZED = 0; + var READYSTATE_LOADING = 1; + var READYSTATE_LOADED = 2; + var READYSTATE_INTERACTIVE = 3; + var READYSTATE_COMPLETE = 4; + + var xhr = new XMLHttpRequest(); + var async = true; + xhr.open("GET", uri, async); + xhr.onreadystatechange = function() { + if (xhr.readyState === READYSTATE_COMPLETE) { + if ((xhr.status === 200 || xhr.status === 0) && xhr.responseXML) { + var doc = xhr.responseXML; + var entries = doc.getElementsByTagName('entry'); + var html = []; + + html.push('
'); + + for (var i = 0; i < entries.length; i += 1) { + var entry = entries[i]; + try { + var entryHTML = []; + entryHTML.push('
', entry.getElementsByTagName('title')[0].childNodes[0].nodeValue); + entryHTML.push('
', entry.getElementsByTagName('content')[0].childNodes[0].nodeValue); + html.push(entryHTML.join('')); + } catch (e) { + if (console && console.log) { console.log("Error:", e); } + } + } + + html.push('
'); + + element.innerHTML = html.join(''); + } else { + element.innerHTML = 'Unable to load feed'; + } + } + }; + + try { + xhr.send(null); + } catch (e) { + element.innerHTML = 'Unable to load feed'; + } +} + diff --git a/hires.js b/hires.js index 9b00b75..4659d7d 100644 --- a/hires.js +++ b/hires.js @@ -1,152 +1,152 @@ -// -// Applesoft BASIC in Javascript -// High Resolution Graphics (HiRes) Emulation -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// This variation does not rely on a canvas element with the same dimensions -// as the pixel buffer; instead, it paints pixels on the canvas using rectangles. -// This works even if the underlying canvas implementation does not support -// scaling (e.g. fallbacks for IE using VML or Flash) - -// Usage: -// -// var hires = new LoRes( element, width, height ) -// hires.clear() -// hires.setColor( color_index ) -// hires.plot( x, y ) -// hires.plot_to( x, x ) -// hires.getPixel( x, x ) // for "HSCRN" -// hires.show( bool ) -// { width: w, height: h } = hires.getScreenSize() -// -// Example: -// -// -// - -function HiRes(element, width, height) { - var COLORS, // Apple II to HTML color table - last_x = 0, - last_y = 0, - pixels = [], - color = 0, - context = element.getContext("2d"); - - pixels.length = width * height; - - // Colors c/o USENET: - // Date: Wed, 05 Sep 2007 01:04:20 +0200 - // From: Linards Ticmanis - // Newsgroups: comp.sys.apple2 - // Subject: Re: Double hires mode color artifacts - // Message-ID: <46dde477$0$4527$9b4e6d93@newsspool3.arcor-online.net> - - COLORS = [ - '#000000', // Black 1 - '#2fbc1a', // Green - '#d043e5', // Violet - '#ffffff', // White 1 - '#000000', // Black 2 - '#d06a1a', // Orange - '#2f95e5', // Medium Blue - '#ffffff' // White 2 - ]; - - this.clear = function(use_color) { - var i; - if (!use_color) { - context.clearRect(0, 0, element.width, element.height); - pixels = []; - pixels.length = width * height; - } else { - context.fillStyle = COLORS[color]; - context.fillRect(0, 0, element.width, element.height); - pixels = []; - pixels.length = width * height; - for (i = 0; i < pixels.length; i += 1) { - pixels[i] = color; - } - } - }; - - - this.setColor = function(newColor) { - color = Math.floor(newColor) % COLORS.length; - }; - - - function drawPixel(x, y) { - var sx = element.width / width, - sy = element.height / height; - context.fillRect(x * sx, y * sy, sx, sy); - pixels[x + y * width] = color; - } - - this.plot = function(x, y) { - context.fillStyle = COLORS[color]; - drawPixel(x, y); - - last_x = x; - last_y = y; - - }; - - this.plot_to = function(x, y) { - var x0 = last_x, y0 = last_y, x1 = x, y1 = y, - dx = Math.abs(x1 - x0), - dy = Math.abs(y1 - y0), - sx = (x0 < x1) ? 1 : -1, - sy = (y0 < y1) ? 1 : -1, - err = dx - dy, - e2; - - while (true) { - this.plot(x0, y0); - - if (x0 === x1 && y0 === y1) { return; } - e2 = 2 * err; - if (e2 > -dy) { - err -= dy; - x0 += sx; - } - if (e2 < dx) { - err += dx; - y0 += sy; - } - } - last_x = x; - last_y = y; - - }; - - this.getPixel = function(x, y) { - /*jslint bitwise:false*/ - return pixels[y * width + x] >>> 0; - }; - - this.getScreenSize = function() { - return { width: width, height: height }; - }; - - this.show = function(state) { - element.style.visibility = state ? "visible" : "hidden"; - }; -} +// +// Applesoft BASIC in Javascript +// High Resolution Graphics (HiRes) Emulation +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// This variation does not rely on a canvas element with the same dimensions +// as the pixel buffer; instead, it paints pixels on the canvas using rectangles. +// This works even if the underlying canvas implementation does not support +// scaling (e.g. fallbacks for IE using VML or Flash) + +// Usage: +// +// var hires = new LoRes( element, width, height ) +// hires.clear() +// hires.setColor( color_index ) +// hires.plot( x, y ) +// hires.plot_to( x, x ) +// hires.getPixel( x, x ) // for "HSCRN" +// hires.show( bool ) +// { width: w, height: h } = hires.getScreenSize() +// +// Example: +// +// +// + +function HiRes(element, width, height) { + var COLORS, // Apple II to HTML color table + last_x = 0, + last_y = 0, + pixels = [], + color = 0, + context = element.getContext("2d"); + + pixels.length = width * height; + + // Colors c/o USENET: + // Date: Wed, 05 Sep 2007 01:04:20 +0200 + // From: Linards Ticmanis + // Newsgroups: comp.sys.apple2 + // Subject: Re: Double hires mode color artifacts + // Message-ID: <46dde477$0$4527$9b4e6d93@newsspool3.arcor-online.net> + + COLORS = [ + '#000000', // Black 1 + '#2fbc1a', // Green + '#d043e5', // Violet + '#ffffff', // White 1 + '#000000', // Black 2 + '#d06a1a', // Orange + '#2f95e5', // Medium Blue + '#ffffff' // White 2 + ]; + + this.clear = function(use_color) { + var i; + if (!use_color) { + context.clearRect(0, 0, element.width, element.height); + pixels = []; + pixels.length = width * height; + } else { + context.fillStyle = COLORS[color]; + context.fillRect(0, 0, element.width, element.height); + pixels = []; + pixels.length = width * height; + for (i = 0; i < pixels.length; i += 1) { + pixels[i] = color; + } + } + }; + + + this.setColor = function(newColor) { + color = Math.floor(newColor) % COLORS.length; + }; + + + function drawPixel(x, y) { + var sx = element.width / width, + sy = element.height / height; + context.fillRect(x * sx, y * sy, sx, sy); + pixels[x + y * width] = color; + } + + this.plot = function(x, y) { + context.fillStyle = COLORS[color]; + drawPixel(x, y); + + last_x = x; + last_y = y; + + }; + + this.plot_to = function(x, y) { + var x0 = last_x, y0 = last_y, x1 = x, y1 = y, + dx = Math.abs(x1 - x0), + dy = Math.abs(y1 - y0), + sx = (x0 < x1) ? 1 : -1, + sy = (y0 < y1) ? 1 : -1, + err = dx - dy, + e2; + + while (true) { + this.plot(x0, y0); + + if (x0 === x1 && y0 === y1) { return; } + e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + last_x = x; + last_y = y; + + }; + + this.getPixel = function(x, y) { + /*jslint bitwise:false*/ + return pixels[y * width + x] >>> 0; + }; + + this.getScreenSize = function() { + return { width: width, height: height }; + }; + + this.show = function(state) { + element.style.visibility = state ? "visible" : "hidden"; + }; +} diff --git a/index.htm b/index.htm index 634df88..514b5b0 100644 --- a/index.htm +++ b/index.htm @@ -1,452 +1,202 @@ - - - - -Applesoft BASIC in JavaScript - - - - - - - - - -

Applesoft BASIC in Javascript

-

-By Joshua Bell -| Applesoft BASIC Quick Reference -| Notes & Known Issues -| To Do -| Links -| History - -

Related projects: - Logo in Javascript - | Streaming video to an Apple II - vnIIc -

- -
- - -
-
-
- - -
-
-
- - - - - -
- Enter code below -       - - - - - - - - -
- -
- - - - -
- -
- -
- -

Notes & Known Issues

-
    -
  • The BASIC program is compiled to JavaScript before execution. Syntax errors are therefore detected at compile-time - rather than at run-time as on a traditional interpreter. For example, the following program would run without errors - on an Apple since the erroneous second statement is never reached. 10 END : CHR$(PRINT) -
  • Handling of BASIC code that does not match the canonical LIST output format may not behave as on an Apple: -
      -
    • Keyword parsing differs from Applesoft command line. For example FOR I = S TO P doesn't collapse into FOR I = STOP. -
    • The interpreter doesn't actually care about line numbers for statement ordering (just for GOTO/GOSUB targets and IF statements). So 20 PRINT "A", 10 PRINT "B" will just print A, then B -
    -
  • To improve readability, lines may start with : and continue the previously numbered line. -
  • Floating point overflow is only detected on variable assignment. -
  • The DOS operating system implements only a subset of DOS 3.3 and ProDOS useful for basic file I/O. -
  • Except for a small number of compatibility shims for common operations (e.g. keyboard strobe), commands that refer to assembly routines (PEEK, POKE, CALL, USR etc.), shape tables, or tape I/O are not implemented. -
  • Commands that operate on the program itself (LIST, RUN, DEL, etc.) are not implemented. -
  • You can run your basic programs from the command line (with only basic text input and output, and no graphics or DOS commands): -
      -
    • On Windows, download basic.js and run from a command prompt via: - cscript.exe basic.js your_basic_program.txt -
    • On Mac/Linux, install Mozilla Rhino, - download basic.js and run from the command prompt via: - java -jar PATH_TO/js.jar basic.js your_program.txt -
    -
- -

To Do

-
    -
  • Implement DOS functionality for consoles -
- - - - -

History

-
- - - - - - - - - - - - - - - - - - - - - - - + + + + +Applesoft BASIC in JavaScript + + + + + + + + + +

Applesoft BASIC in Javascript

+

+By Joshua Bell +| Applesoft BASIC Quick Reference +| Notes & Known Issues +| To Do +| Links +| History + +

Related projects: + Logo in Javascript + | Streaming video to an Apple II - vnIIc +

+ +
+ + +
+
+
+ + +
+
+
+ + + + + +
+ Enter code below +       + + + + + + + + +
+ +
+ + + + +
+ +
+ +
+ +

Notes & Known Issues

+
    +
  • The BASIC program is compiled to JavaScript before execution. Syntax errors are therefore detected at compile-time + rather than at run-time as on a traditional interpreter. For example, the following program would run without errors + on an Apple since the erroneous second statement is never reached. 10 END : CHR$(PRINT) +
  • Handling of BASIC code that does not match the canonical LIST output format may not behave as on an Apple: +
      +
    • Keyword parsing differs from Applesoft command line. For example FOR I = S TO P doesn't collapse into FOR I = STOP. +
    • The interpreter doesn't actually care about line numbers for statement ordering (just for GOTO/GOSUB targets and IF statements). So 20 PRINT "A", 10 PRINT "B" will just print A, then B +
    +
  • To improve readability, lines may start with : and continue the previously numbered line. +
  • Floating point overflow is only detected on variable assignment. +
  • The DOS operating system implements only a subset of DOS 3.3 and ProDOS useful for basic file I/O. +
  • Except for a small number of compatibility shims for common operations (e.g. keyboard strobe), commands that refer to assembly routines (PEEK, POKE, CALL, USR etc.), shape tables, or tape I/O are not implemented. +
  • Commands that operate on the program itself (LIST, RUN, DEL, etc.) are not implemented. +
  • You can run your basic programs from the command line (with only basic text input and output, and no graphics or DOS commands): +
      +
    • On Windows, download basic.js and run from a command prompt via: + cscript.exe basic.js your_basic_program.txt +
    • On Mac/Linux, install Mozilla Rhino, + download basic.js and run from the command prompt via: + java -jar PATH_TO/js.jar basic.js your_program.txt +
    +
+ +

To Do

+
    +
  • Implement DOS functionality for consoles +
+ + + + +

History

+
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/index.js b/index.js new file mode 100644 index 0000000..3f7d1a2 --- /dev/null +++ b/index.js @@ -0,0 +1,236 @@ + +window.onload = function () { + + var $ = function (s) { return document.querySelector(s); }; + + $('#lb_files').selectedIndex = 0; + + var bell = (function () { + var b = new Bell(/^.*\/|/.exec(window.location)[0]); + return function () { b.play(); }; + } ()); + + + var tty = new TTY($('#screen'), $('#keyboard'), bell); + var dos = new DOS(tty); + + var lores = new LoRes($('#lores'), 40, 48); + var hires = new HiRes($('#hires'), 280, 192); + var hires2 = new HiRes($('#hires2'), 280, 192); + var display = { + state: { graphics: false, full: true, page1: true, lores: true }, + setState: function (state, value /* ... */) { + var args = Array.prototype.slice.call(arguments); + while (args.length) { + state = args.shift(); + value = args.shift(); + this.state[state] = value; + } + + if (this.state.graphics) { + lores.show(this.state.lores); + hires.show(!this.state.lores && this.state.page1); + hires2.show(!this.state.lores && !this.state.page1); + tty.splitScreen(tty.getScreenSize().height - (this.state.full ? 0 : 4)); + } else { + lores.show(false); + hires.show(false); + hires2.show(false); + tty.splitScreen(0); + } + } + }; + var pdl = [0, 0, 0, 0]; + + // Lexical highlighting + var editor = new CodeMirror($('#editorframe'), { + mode: 'basic', + tabMode: 'default', + content: $('#source').value, + height: '100%' + }); + + $('#btn_share').onclick = function () { + // Load up the hidden text area with the current source + $('#source').value = getSource(); + }; + + function getSource() { + return editor.getValue(); + } + + function setSource(source) { + editor.setValue(source); + } + + // Do not let certain events take focus away from "keyboard" + function keyboardFocus(e) { + tty.focus(); + e.stopPropagation(); // W3C + e.preventDefault(); // e.g. to block arrows from scrolling the page + } + + addEvent($('#lores'), 'click', keyboardFocus); + addEvent($('#hires'), 'click', keyboardFocus); + addEvent($('#hires2'), 'click', keyboardFocus); + addEvent($('#screen'), 'click', keyboardFocus); + addEvent($('#frame'), 'click', keyboardFocus); + addEvent($('#frame'), 'blur', keyboardFocus); // Needed on IE, not sure why it tries to get focus + + addEvent($('#keyboard'), 'focus', function () { + getClassList($('#frame')).add('focused'); + }); + addEvent($('#keyboard'), 'blur', function () { + getClassList($('#frame')).remove('focused'); + }); + + var program; + addEvent($('#btn_run'), 'click', function () { + dos.reset(); + tty.reset(); + tty.autoScroll = true; + + try { + program = basic.compile(getSource()); + } catch (e) { + if (e instanceof basic.ParseError) { + editor.setCursor({ line: e.line - 1, ch: e.column - 1 }); + console.log(e.message + + ' (source line:' + e.line + ', column:' + e.column + ')'); + } + alert(e); + return; + } + + stopped = false; + updateUI(); + + program.init({ + tty: tty, + hires: hires, + hires2: hires2, + lores: lores, + display: display, + paddle: function (n) { return pdl[n]; } + }); + setTimeout(driver, 0); + }); + + addEvent($('#btn_stop'), 'click', function () { + tty.reset(); // cancel any blocking input + stopped = true; + updateUI(); + }); + + addEvent($('#lb_files'), 'change', function () { + var sel = $('#lb_files'); + loadFile('samples/' + sel.value + ".txt", setSource); + }); + + addEvent($('#btn_save'), 'click', function () { + window.localStorage.setItem("save_program", getSource()); + }); + addEvent($('#btn_load'), 'click', function () { + setSource(window.localStorage.getItem("save_program")); + }); + + // Add a "printer" on demand + addEvent($('#btn_capture'), 'click', function () { + $('#btn_capture').disabled = true; + var printer = new Printer(tty); + + printer.onclose = function () { + $('#btn_capture').disabled = false; + }; + }); + + + // Mouse-as-Joystick + var wrapper = $('#screen-wrapper'); + addEvent(wrapper, 'mousemove', function (e) { + // compute relative coordinates + var x = e.clientX, y = e.clientY, elem = wrapper; + while (elem) { + x -= elem.offsetLeft - elem.scrollLeft; + y -= elem.offsetTop - elem.scrollTop; + elem = elem.offsetParent; + } + + function clamp(n, min, max) { return Math.min(Math.max(n, min), max); } + pdl[0] = clamp(x / (wrapper.offsetWidth - 1), 0, 1); + pdl[1] = clamp(y / (wrapper.offsetHeight - 1), 0, 1); + }); + + + var stopped = true; + function updateUI() { + $("#btn_stop").disabled = stopped ? "disabled" : ""; + $("#btn_run").disabled = stopped ? "" : "disabled"; + $("#lb_files").disabled = stopped ? "" : "disabled"; + + if (stopped) { + $("#btn_run").focus(); + } else { + tty.focus(); + } + } + + + // TODO: Expose a RESPONSIVE |---+--------| FAST slider in the UI + // Number of steps to execute before yielding execution + // (Use a prime to minimize risk of phasing with loops) + var NUM_SYNCHRONOUS_STEPS = 37; + + function driver() { + var state = basic.STATE_RUNNING; + var statements = NUM_SYNCHRONOUS_STEPS; + + while (!stopped && state === basic.STATE_RUNNING && statements > 0) { + + try { + state = program.step(driver); + } catch (e) { + console.log(e); + alert(e.message ? e.message : e); + stopped = true; + updateUI(); + return; + } + + statements -= 1; + } + + if (state === basic.STATE_STOPPED || stopped) { + stopped = true; + updateUI(); + } else if (state === basic.STATE_BLOCKED) { + // Fall out + } else { // state === basic.STATE_RUNNING + setTimeout(driver, 0); // Keep going + } + } + + + function loadFile(filename, callback) { + var req = new XMLHttpRequest(); + var url = encodeURI(filename); // not encodeURIComponent, so paths can be specified + var async = true; + req.open("GET", url, async); + req.onreadystatechange = function () { + if (req.readyState === XMLHttpRequest.DONE) { + if (req.status === 200 || req.status === 0) { + callback(req.responseText); + } + } + }; + req.send(null); + } + + // load default + loadFile('samples/sample.default.txt', setSource); + + // Show change history + atomToHtml('feed.xml?' + Math.random(), $('#feed')); +}; + + diff --git a/lores.js b/lores.js index ef96704..f360352 100644 --- a/lores.js +++ b/lores.js @@ -1,192 +1,192 @@ -// -// Applesoft BASIC in Javascript -// Low Resolution Graphics (LoRes) Emulation -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Usage: -// -// var lores = new LoRes( element, width, height ) -// lores.clear() -// lores.setColor( color_index ) -// lores.plot( x, y ) -// lores.hlin( x1, x2, y ) -// lores.vlin( x1, x2, y ) -// lores.show( bool ) -// color_index = lores.getPixel( x, y ) -// { width: w, height: h } = lores.getScreenSize() -// -// Example: -// -// -// -//
- -function LoRes(element, width, height) { - - /*jslint browser: true*/ - - var COLORS, // Apple II to HTML color table - loresPixel = [], // element references - pixels = [], // color values - color = 0; // current color - - // Colors c/o USENET: - // Date: Wed, 05 Sep 2007 01:04:20 +0200 - // From: Linards Ticmanis - // Newsgroups: comp.sys.apple2 - // Subject: Re: Double hires mode color artifacts - // Message-ID: <46dde477$0$4527$9b4e6d93@newsspool3.arcor-online.net> - - COLORS = [ - '#000000', // Black - '#901740', // Deep Red - '#402ca5', // Dark Blue - '#d043e5', // Purple - '#006940', // Dark Green - '#808080', // Gray 1 - '#2f95e5', // Medium Blue - '#bfabff', // Light Blue - '#402400', // Brown // WAS #405400, error pointed out by MJM - '#d06a1a', // Orange - '#808080', // Gray 2 - '#ff96bf', // Pink - '#2fbc1a', // Light Green - '#bfd35a', // Yellow - '#6fe8bf', // Aquamarine - '#ffffff' // White - ]; - - function init() { - var x, y, table, tbody, tr, td; - - pixels = []; - pixels.length = width * height; - loresPixel = []; - loresPixel.length = width * height; - - table = document.createElement('table'); - table.className = 'loresDisplay'; - - tbody = document.createElement('tbody'); - for (y = 0; y < height; y += 1) { - tr = document.createElement('tr'); - tr.className = 'loresRow'; - for (x = 0; x < width; x += 1) { - td = document.createElement('td'); - td.className = 'loresPixel'; - td.style.backgroundColor = 'black'; - - loresPixel[y * width + x] = td; - pixels[y * width + x] = 0; - - tr.appendChild(td); - } - tbody.appendChild(tr); - } - table.appendChild(tbody); - - element.innerHTML = ""; - element.appendChild(table); - } - - this.clear = function() { - var x, y, pixel; - for (y = 0; y < height; y += 1) { - for (x = 0; x < width; x += 1) { - pixel = loresPixel[y * width + x]; - pixel.style.backgroundColor = "black"; - pixels[y * width + x] = 0; - } - } - - }; - - this.setColor = function(newColor) { - color = Math.floor(newColor) % COLORS.length; - }; - - function plot(x, y) { - var pixel = loresPixel[y * width + x]; - if (pixel) { - pixel.style.backgroundColor = COLORS[color]; - pixels[y * width + x] = color; - } - } - - this.plot = function(x, y) { - plot(x, y); - }; - - this.getPixel = function(x, y) { - if (0 <= x && x < width && - 0 <= y && y < height) { - - return pixels[y * width + x]; - } else { - return 0; - } - }; - - this.hlin = function(x1, x2, y) { - var x; - if (x1 > x2) { - x = x1; - x1 = x2; - x2 = x; - } - - for (x = x1; x <= x2; x += 1) { - plot(x, y); - } - }; - - this.vlin = function(y1, y2, x) { - var y; - if (y1 > y2) { - y = y1; - y1 = y2; - y2 = y; - } - - for (y = y1; y <= y2; y += 1) { - plot(x, y); - } - }; - - this.getScreenSize = function() { - return { width: width, height: height }; - }; - - this.show = function(state) { - element.style.visibility = state ? "visible" : "hidden"; - - }; - - //---------------------------------------------------------------------- - // Constructor Logic - //---------------------------------------------------------------------- - - init(); -} - - +// +// Applesoft BASIC in Javascript +// Low Resolution Graphics (LoRes) Emulation +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Usage: +// +// var lores = new LoRes( element, width, height ) +// lores.clear() +// lores.setColor( color_index ) +// lores.plot( x, y ) +// lores.hlin( x1, x2, y ) +// lores.vlin( x1, x2, y ) +// lores.show( bool ) +// color_index = lores.getPixel( x, y ) +// { width: w, height: h } = lores.getScreenSize() +// +// Example: +// +// +// +//
+ +function LoRes(element, width, height) { + + /*jslint browser: true*/ + + var COLORS, // Apple II to HTML color table + loresPixel = [], // element references + pixels = [], // color values + color = 0; // current color + + // Colors c/o USENET: + // Date: Wed, 05 Sep 2007 01:04:20 +0200 + // From: Linards Ticmanis + // Newsgroups: comp.sys.apple2 + // Subject: Re: Double hires mode color artifacts + // Message-ID: <46dde477$0$4527$9b4e6d93@newsspool3.arcor-online.net> + + COLORS = [ + '#000000', // Black + '#901740', // Deep Red + '#402ca5', // Dark Blue + '#d043e5', // Purple + '#006940', // Dark Green + '#808080', // Gray 1 + '#2f95e5', // Medium Blue + '#bfabff', // Light Blue + '#402400', // Brown // WAS #405400, error pointed out by MJM + '#d06a1a', // Orange + '#808080', // Gray 2 + '#ff96bf', // Pink + '#2fbc1a', // Light Green + '#bfd35a', // Yellow + '#6fe8bf', // Aquamarine + '#ffffff' // White + ]; + + function init() { + var x, y, table, tbody, tr, td; + + pixels = []; + pixels.length = width * height; + loresPixel = []; + loresPixel.length = width * height; + + table = document.createElement('table'); + table.className = 'loresDisplay'; + + tbody = document.createElement('tbody'); + for (y = 0; y < height; y += 1) { + tr = document.createElement('tr'); + tr.className = 'loresRow'; + for (x = 0; x < width; x += 1) { + td = document.createElement('td'); + td.className = 'loresPixel'; + td.style.backgroundColor = 'black'; + + loresPixel[y * width + x] = td; + pixels[y * width + x] = 0; + + tr.appendChild(td); + } + tbody.appendChild(tr); + } + table.appendChild(tbody); + + element.innerHTML = ""; + element.appendChild(table); + } + + this.clear = function() { + var x, y, pixel; + for (y = 0; y < height; y += 1) { + for (x = 0; x < width; x += 1) { + pixel = loresPixel[y * width + x]; + pixel.style.backgroundColor = "black"; + pixels[y * width + x] = 0; + } + } + + }; + + this.setColor = function(newColor) { + color = Math.floor(newColor) % COLORS.length; + }; + + function plot(x, y) { + var pixel = loresPixel[y * width + x]; + if (pixel) { + pixel.style.backgroundColor = COLORS[color]; + pixels[y * width + x] = color; + } + } + + this.plot = function(x, y) { + plot(x, y); + }; + + this.getPixel = function(x, y) { + if (0 <= x && x < width && + 0 <= y && y < height) { + + return pixels[y * width + x]; + } else { + return 0; + } + }; + + this.hlin = function(x1, x2, y) { + var x; + if (x1 > x2) { + x = x1; + x1 = x2; + x2 = x; + } + + for (x = x1; x <= x2; x += 1) { + plot(x, y); + } + }; + + this.vlin = function(y1, y2, x) { + var y; + if (y1 > y2) { + y = y1; + y1 = y2; + y2 = y; + } + + for (y = y1; y <= y2; y += 1) { + plot(x, y); + } + }; + + this.getScreenSize = function() { + return { width: width, height: height }; + }; + + this.show = function(state) { + element.style.visibility = state ? "visible" : "hidden"; + + }; + + //---------------------------------------------------------------------- + // Constructor Logic + //---------------------------------------------------------------------- + + init(); +} + + diff --git a/paddles.js b/paddles.js index 3cd7b5a..fdeb8e1 100644 --- a/paddles.js +++ b/paddles.js @@ -1,140 +1,140 @@ -// -// Applesoft BASIC in Javascript -// Paddle and Joystick Emulation -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - - -// Usage: -// -// initPaddle( device_element, thumb_element, callback_function ); -// initJoystick( device_element, thumb_element, callback_function_x, callback_function_y ); -// -// Example: -// -// -// -//
Slide the Paddle!
-//
Twiddle me!
- -/*extern document,window,addEvent,removeEvent */ - -function initDevice(device, thumb, fn_x, fn_y) { - device.style.position = "relative"; - device.style.overflow = "hidden"; - - thumb.style.position = "absolute"; - thumb.style.left = 0; - thumb.style.top = 0; - - thumb.last_x = 0; - thumb.last_y = 0; - - var onStickDrag = function (e) { - e.returnValue = false; - e.cancelBubble = true; - if (e.stopPropagation) { e.stopPropagation(); } - if (e.preventDefault) { e.preventDefault(); } - - if (fn_x) { - var x = e.clientX - thumb.originX + thumb.posX; - var min_x = 0; - var max_x = device.clientWidth - thumb.offsetWidth; - if (x < min_x) { x = min_x; } - if (x > max_x) { x = max_x; } - - if (x != thumb.last_x) { - thumb.last_x = x; - thumb.style.left = x + "px"; - fn_x((x - min_x) / (max_x - min_x)); - } - } - - if (fn_y) { - var y = e.clientY - thumb.originY + thumb.posY; - var min_y = 0; - var max_y = device.clientHeight - thumb.offsetHeight; - if (y < min_y) { y = min_y; } - if (y > max_y) { y = max_y; } - - if (y != thumb.last_y) { - thumb.last_y = y; - thumb.style.top = y + "px"; - fn_y((y - min_y) / (max_y - min_y)); - } - } - - return false; - }; - - var onStickUp = function (e) { - e.returnValue = false; - e.cancelBubble = true; - if (e.stopPropagation) { e.stopPropagation(); } - if (e.preventDefault) { e.preventDefault(); } - - removeEvent(document, "mousemove", onStickDrag); - removeEvent(document, "mouseup", onStickUp); - if (thumb.releaseCapture) { thumb.releaseCapture(true); } - - return false; - }; - - var onStickDown = function (e) { - e.returnValue = false; - e.cancelBubble = true; - if (e.stopPropagation) { e.stopPropagation(); } - if (e.preventDefault) { e.preventDefault(); } - - addEvent(document, "mousemove", onStickDrag); - addEvent(document, "mouseup", onStickUp); - if (thumb.setCapture) { thumb.setCapture(true); } - - if (fn_x) { - thumb.originX = e.clientX; - thumb.posX = parseInt(thumb.style.left, 10); - } - - if (fn_y) { - thumb.originY = e.clientY; - thumb.posY = parseInt(thumb.style.top, 10); - } - - return false; - }; - - device.appendChild(thumb); - - addEvent(thumb, "mousedown", onStickDown); -} - -function initPaddle(device, control, fn) { - initDevice(device, control, fn); -} - -function initJoystick(device, control, fn_x, fn_y) { - initDevice(device, control, fn_x, fn_y); -} +// +// Applesoft BASIC in Javascript +// Paddle and Joystick Emulation +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// Usage: +// +// initPaddle( device_element, thumb_element, callback_function ); +// initJoystick( device_element, thumb_element, callback_function_x, callback_function_y ); +// +// Example: +// +// +// +//
Slide the Paddle!
+//
Twiddle me!
+ +/*extern document,window,addEvent,removeEvent */ + +function initDevice(device, thumb, fn_x, fn_y) { + device.style.position = "relative"; + device.style.overflow = "hidden"; + + thumb.style.position = "absolute"; + thumb.style.left = 0; + thumb.style.top = 0; + + thumb.last_x = 0; + thumb.last_y = 0; + + var onStickDrag = function (e) { + e.returnValue = false; + e.cancelBubble = true; + if (e.stopPropagation) { e.stopPropagation(); } + if (e.preventDefault) { e.preventDefault(); } + + if (fn_x) { + var x = e.clientX - thumb.originX + thumb.posX; + var min_x = 0; + var max_x = device.clientWidth - thumb.offsetWidth; + if (x < min_x) { x = min_x; } + if (x > max_x) { x = max_x; } + + if (x != thumb.last_x) { + thumb.last_x = x; + thumb.style.left = x + "px"; + fn_x((x - min_x) / (max_x - min_x)); + } + } + + if (fn_y) { + var y = e.clientY - thumb.originY + thumb.posY; + var min_y = 0; + var max_y = device.clientHeight - thumb.offsetHeight; + if (y < min_y) { y = min_y; } + if (y > max_y) { y = max_y; } + + if (y != thumb.last_y) { + thumb.last_y = y; + thumb.style.top = y + "px"; + fn_y((y - min_y) / (max_y - min_y)); + } + } + + return false; + }; + + var onStickUp = function (e) { + e.returnValue = false; + e.cancelBubble = true; + if (e.stopPropagation) { e.stopPropagation(); } + if (e.preventDefault) { e.preventDefault(); } + + removeEvent(document, "mousemove", onStickDrag); + removeEvent(document, "mouseup", onStickUp); + if (thumb.releaseCapture) { thumb.releaseCapture(true); } + + return false; + }; + + var onStickDown = function (e) { + e.returnValue = false; + e.cancelBubble = true; + if (e.stopPropagation) { e.stopPropagation(); } + if (e.preventDefault) { e.preventDefault(); } + + addEvent(document, "mousemove", onStickDrag); + addEvent(document, "mouseup", onStickUp); + if (thumb.setCapture) { thumb.setCapture(true); } + + if (fn_x) { + thumb.originX = e.clientX; + thumb.posX = parseInt(thumb.style.left, 10); + } + + if (fn_y) { + thumb.originY = e.clientY; + thumb.posY = parseInt(thumb.style.top, 10); + } + + return false; + }; + + device.appendChild(thumb); + + addEvent(thumb, "mousedown", onStickDown); +} + +function initPaddle(device, control, fn) { + initDevice(device, control, fn); +} + +function initJoystick(device, control, fn_x, fn_y) { + initDevice(device, control, fn_x, fn_y); +} diff --git a/printer.js b/printer.js index f5c5ef1..14956e2 100644 --- a/printer.js +++ b/printer.js @@ -1,95 +1,95 @@ -// -// Applesoft BASIC in Javascript -// Printer (for copying output) -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -function Printer(tty) { - - // For closures - var self = this; - - // Create new window and document - var w = window.open('about:blank', '_blank', 'width=500,height=400,status=0,location=0,menubar=0,toolbar=0'); - var body = w.document.getElementsByTagName('body')[0]; - body.style.fontFamily = 'Courier, Monospace'; - body.style.backgroundColor = '#ffffff'; - body.style.backgroundImage = "url('http://calormen.com/Star_Trek/ASCII/lpt.jpg')"; - body.style.color = '#000000'; - body.style.paddingLeft = '50px'; - - var paper = w.document.createElement('div'); - paper.style.whiteSpace = 'pre'; - body.appendChild(paper); - - var tty_writeChar = tty.writeChar; - tty.writeChar = function(c) { - - tty_writeChar(c); - - switch (c.charCodeAt(0)) { - case 0: - case 1: - case 2: - case 3: - case 4: // DOS hook - case 5: - case 6: - case 7: // (BEL) bell - case 8: // (BS) backspace - case 9: - case 11: // (VT) clear EOS - case 12: // (FF) clear - case 14: // (SO) normal - case 15: // (SI) inverse - case 16: - case 17: // (DC1) 40-column - case 18: // (DC2) 80-column - case 19: // (DC3) stop list - case 20: - case 21: // (NAK) quit (disable 80-col card) - case 22: // (SYN) scroll - case 23: // (ETB) scroll up - case 24: // (CAN) disable mousetext - case 25: // (EM) home - case 26: // (SUB) clear line - case 27: // (ESC) enable mousetext - case 28: // (FS) forward space - case 29: // (GS) clear EOL - case 30: // (RS) gotoXY - case 31: - break; - - case 10: // (LF) line feed - case 13: // (CR) return - paper.appendChild(w.document.createTextNode('\n')); - break; - - default: - paper.appendChild(w.document.createTextNode(c)); - break; - } - - paper.parentElement.scrollTop = paper.parentElement.scrollHeight; - }; - - w.onunload = function() { - if (self.onclose) { - self.onclose(); - } - tty.writeChar = tty_writeChar; - }; -} +// +// Applesoft BASIC in Javascript +// Printer (for copying output) +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +function Printer(tty) { + + // For closures + var self = this; + + // Create new window and document + var w = window.open('about:blank', '_blank', 'width=500,height=400,status=0,location=0,menubar=0,toolbar=0'); + var body = w.document.getElementsByTagName('body')[0]; + body.style.fontFamily = 'Courier, Monospace'; + body.style.backgroundColor = '#ffffff'; + body.style.backgroundImage = "url('http://calormen.com/Star_Trek/ASCII/lpt.jpg')"; + body.style.color = '#000000'; + body.style.paddingLeft = '50px'; + + var paper = w.document.createElement('div'); + paper.style.whiteSpace = 'pre'; + body.appendChild(paper); + + var tty_writeChar = tty.writeChar; + tty.writeChar = function(c) { + + tty_writeChar(c); + + switch (c.charCodeAt(0)) { + case 0: + case 1: + case 2: + case 3: + case 4: // DOS hook + case 5: + case 6: + case 7: // (BEL) bell + case 8: // (BS) backspace + case 9: + case 11: // (VT) clear EOS + case 12: // (FF) clear + case 14: // (SO) normal + case 15: // (SI) inverse + case 16: + case 17: // (DC1) 40-column + case 18: // (DC2) 80-column + case 19: // (DC3) stop list + case 20: + case 21: // (NAK) quit (disable 80-col card) + case 22: // (SYN) scroll + case 23: // (ETB) scroll up + case 24: // (CAN) disable mousetext + case 25: // (EM) home + case 26: // (SUB) clear line + case 27: // (ESC) enable mousetext + case 28: // (FS) forward space + case 29: // (GS) clear EOL + case 30: // (RS) gotoXY + case 31: + break; + + case 10: // (LF) line feed + case 13: // (CR) return + paper.appendChild(w.document.createTextNode('\n')); + break; + + default: + paper.appendChild(w.document.createTextNode(c)); + break; + } + + paper.parentElement.scrollTop = paper.parentElement.scrollHeight; + }; + + w.onunload = function() { + if (self.onclose) { + self.onclose(); + } + tty.writeChar = tty_writeChar; + }; +} diff --git a/reference.htm b/reference.htm index 0479d66..8f79964 100644 --- a/reference.htm +++ b/reference.htm @@ -1,570 +1,570 @@ - - - -Applesoft BASIC Reference - - - - -
- -

Applesoft BASIC Quick Reference

- -

-This is intended as a quick reference for the Applesoft BASIC in JavaScript by Joshua Bell. -

- -

-Extensions beyond Applesoft BASIC are called out with footnotes. -

- -

See also:

- - - - - -

Statements

- - -

Variable Control

-
-
CLEAR
Clear all variables -
[LET] var = expr
Assign variable -
DIM var( size [ , size2 ...] ) [ , var2( size [ , size2 ...] ) ... ]
Allocate array -
DEF FN f(var ) = expr
Define function of a single variable [1] -
-

Flow Control

-
-
GOTO
Go to line number -
GOSUB line
Go to subroutine -
RETURN
Return from subroutine -
ON expr GOTO line [, line2 ... ]
Branch based on index (value = 1, 2, ...) -
ON expr GOSUB line [, line2 ... ]
Subroutine branch based on index (value = 1, 2, ...) -
POP
Convert last gosub into a goto -
FOR var = start TO end [ STEP incr ]
Loop with counter variable -
NEXT [var [, var ...] ]
End of loop(s) -
IF expr THEN statement
IF expr GOTO line
Conditional; if expr is false, rest of line is skipped -
END
Terminate program -
STOP
Break, as if an error occurred -
-

Error Handling

-
-
ONERR GOTO line
Set error hook -
RESUME
Retry line that caused ONERR GOTO -
-

Input/Output

-
-
PRINT expr [ [;,] expr2 ... ] [;]
Output text -
INPUT [string ;] var [, var2 ... ]
Read line of input -
GET var
Read single key -
HOME
Clear text display -
HTAB expr
Position text cursor horizontally -
VTAB expr
Position text cursor vertically -
INVERSE
Set output mode to black-on-white -
FLASH
Set output mode to flashing -
NORMAL
Set output mode to white-on-black -
TEXT
Set display to text mode -
-

Miscellaneous

-
-
REM [ comment ]
Begin a comment; rest of line is skipped -
TRACE
Turn on trace mode (line numbers printed) -
NOTRACE
Turn off trace mode -
-

Inline Data

-
-
DATA value [, value2 ... ]
Define inline data -
READ var [, var2 ... ]
Read the next DATA value -
RESTORE
Restore the DATA pointer to the first value -
-

Lo-Res Graphics

-
-
GR
Set display to lores mode, clear screen -
COLOR= expr
Set lores color (0...15) -
PLOT expr, expr
Plot lores point (x = 0...39, y = 0...40/48) -
HLIN expr, expr AT expr
Plot horizontal line (x1, x2 at y) -
VLIN expr, expr AT expr
Plot vertical line (y1, y2 at x) -
-

Hi-Res Graphics

-
-
HGR
Set display to hires mode, clear screen -
HGR2
Set display to hires mode (page 2), clear screen -
HPLOT [TO] expr, expr [ TO expr, expr ] ...
Plot hires point/line (x=0...279, y=0...191) -
HCOLOR= expr
Set hires color (0...7) -
-

Hi-Res Shape Tables - NOT IMPLEMENTED

-
-
ROT= expr
Set hires shape table rotation (0...63) -
SCALE= expr
Set hires shape table scale (1...255) -
DRAW expr [ AT expr, expr ]
Draw hires shape table shape in color -
XDRAW expr [ AT expr, expr ]
Draw hires shape table shape with XOR -
-

Interpreter and Program State - NOT IMPLEMENTED

-
-
CONT
Continue from a STOP -
DEL
Delete lines of program -
LIST [ expr [, expr ] ]
List lines of program -
NEW
Clear program and variables -
RUN [ expr ]
Start program execution at line -
-

Native Platform Interaction - NOT IMPLEMENTED

-
-
HIMEM: expr
Set upper address of variable memory -
IN# expr
Direct input from slot -
LOMEM: expr
Set lower address of variable memory -
WAIT expr, expr [, expr]
Wait until memory location masked by second argument equals third argument (or zero) -
-

Cassette Tape - NOT IMPLEMENTED

-
-
LOAD
Load program from cassette -
RECALL
Load variables from cassette -
SAVE
Save program to cassette -
STORE
Save variables to cassette -
SHLOAD
Load hires shape table from cassette -
-

Compatibility Shims

-
-
SPEED= expr
Set character output delay - has no effect -
POKE expr, expr
Set memory location to value -
    -
  • POKE 32,n - Text window left edge -
  • POKE 33,n - Text window width -
  • POKE 34,n - Text window top edge -
  • POKE 35,n - Text window bottom -
  • POKE 36,n - Text cursor x -
  • POKE 37,n - Text cursor y -
  • POKE 216,n - ONERR flag (n < 128 disables ONERR handler) -
  • POKE 230,n - Hi-Res plotting page (32 = page 1, 64 = page 2) -
  • POKE 49168,0 - clear keyboard strobe -
  • POKE 49200,0 - toggle speaker (no-op) -
  • POKE 49232,0 - graphics mode -
  • POKE 49233,0 - text mode -
  • POKE 49234,0 - full graphics -
  • POKE 49235,0 - split screen -
  • POKE 49238,0 - lores -
  • POKE 49239,0 - hires -
- -
CALL expr
Call native routine -
    -
  • CALL -3100 - reveal hi-res page 1 -
  • CALL -3086 - clear current hi-res page to black -
  • CALL -3082 - clear current hi-res page to current color -
  • CALL 54951 - clear stack (cancel pending FOR-NEXT loops and GOSUBs) -
- -
PR# expr
Direct output to slot -
    -
  • PR#0 - set 40 column mode -
  • PR#3 - set 80 column mode -
- -
- - -

Functions

- - -

Numeric Functions

-
-
ABS( expr )
Absolute value of number -
ATN( expr )
Arctangent of number -
COS( expr )
Cosine of number -
EXP( expr )
Raise e to number -
INT( expr )
Integer part of number -
LOG( expr )
Natural log of number -
RND( expr )
Pseudo-random number generator (0 repeats last, negative reseeds) -
SGN( expr )
Sign of number (-1,0,1) -
SIN( expr )
Sine of number -
SQR( expr )
Square root of number -
TAN( expr )
Tangent of number -
-

String Functions

-
-
LEN( expr )
Length of string -
LEFT$( expr, expr )
Left portion of (string, length) -
MID$( expr, expr [, expr] )
Substring of (string, start character, length) -
RIGHT$( expr, expr )
Right portion of (string, length) -
-

Type Conversion Functions

-
-
ASC( expr )
ASCII code for first character of string -
CHR$( expr )
Character at specified ASCII code point[3] -
STR$( expr )
String representation of number -
VAL( expr )
Parse string into number -
-

System Interaction Functions

-
-
FRE( expr )
Garbage collect strings (returns 0) -
PDL( expr )
Paddle position (paddle number) -
POS( expr )
Horizontal cursor position -
SCRN( expr, expr )
Lores color at pixel (x,y) -
HSCRN( expr, expr )
Hires color at pixel (x,y)[4] -
USR( expr )
Execute assembly code at address, return accumulator value - NOT IMPLEMENTED -
-

User Defined Functions

-
-
FN f( expr )
Execute user defined function [1] -
- -

Function Compatibility Shims

-
-
PEEK( expr )
Value at memory location -
    -
  • PEEK(32) - Text window left edge -
  • PEEK(33) - Text window width -
  • PEEK(34) - Text window top edge -
  • PEEK(35) - Text window bottom -
  • PEEK(36) - Text cursor x -
  • PEEK(37) - Text cursor y -
  • PEEK(78) & PEEK(79) - Random-Number Field -
  • PEEK(222) - Last error code -
  • PEEK(230) - Hi-Res plotting page (32 = page 1, 64 = page 2) -
  • PEEK(49152) - Read Keyboard -
  • PEEK(49168) - Clear Keyboard strobe -
  • PEEK(49200) - Click Speaker (no-op) -
  • PEEK(49248) - Read Paddle Button #3 - Use the PageDown key -
  • PEEK(49249) - Read Paddle Button #0 - Use the Home key -
  • PEEK(49250) - Read Paddle Button #1 - Use the End key -
  • PEEK(49251) - Read Paddle Button #2 - Use the PageUp or Shift key -
- -
- - -

Operators

- - -

Comparison Operators

-
-
=
Equality[2] -
<
Less than -
>
Greater than -
<=
=<
Less than or equal -
>=
=>
Greater than or equal -
<>
><
Not equal -
-

Boolean Operators

-
-
AND
Conjunction -
OR
Disjunction -
NOT
Negation -
-

Arithmetic Operators

-
-
^
Exponentiation -
*
Multiplication -
/
Division -
+
Addition -
-
Subtraction -
-

String Operators

-
-
+
String Concatenation -
- - -

Error Codes

- -

Error codes can be determined by calling -PEEK(222) in an ONERR handler.

- -
-
0
Next without for -
16
Syntax error - Not generated -
22
Return without gosub -
42
Out of data -
53
Illegal quantity -
69
Overflow -
77
Out of memory - Not generated by all browsers -
90
Undefined statement -
107
Bad subscript -
120
Redimensioned array -
133
Division by zero -
163
Type mismatch -
176
String too long - Not generated -
191
Formula too complex - Not generated by all browsers -
224
Undefined function -
254
Re-enter -
255
Break -
- - - - -

DOS 3.3 / ProDOS Quick Reference

- - -

See also

- - -

DOS Commands

-
-
MON[,C][,I][,O]
Traces DOS 3.3 commands ('Commands', 'Input' and 'Output') -
NOMON[,C][,I][,O]
Cancels tracing of DOS 3.3 commands ('Commands', 'Input' and 'Output') -
OPEN filename[,Llen]
Opens a text file. -
APPEND filename
Appends to a text file. -
CLOSE [filename]
Closes specified (or all) open text files. -
POSITION filename[,Rnum]
Advances position in text file. -
READ filename[,Rnum][,Bbyte]
Reads from a text file. -
WRITE filename[,Rnum][,Bbyte]
Writes to a text file -
DELETE filename
Delete a file -
RENAME oldname,newname
Rename a file -
PR# slot
Same as the BASIC command -
-

Other DOS commands are NOT IMPLEMENTED.

-

Some sample files are present in a server-side store loaded on-demand into a client-side virtual file system (VFS). Creating or writing to a file will write to the VFS. Subsequent reads will read from the VFS. Files may be deleted from the VFS. These changes will not persist if the browser is refreshed or closed.

- -

DOS Error Codes

- -

Error codes can be determined by calling -PEEK(222) in an ONERR handler.

- -
-
1
Language not available - Not generated -
2
Range error - Not generated -
4
Write protected - Not generated -
5
End of data -
6
File not found -
7
Volume mismatch - Not generated -
8
I/O error - Not generated -
9
Disk full - Not generated -
10
File locked - Not generated -
11
Invalid option -
12
No buffers available - Not generated -
13
File type mismatch - Not generated -
14
Program too large - Not generated -
15
Not direct command - Not generated -
- - -

Input and Output

- - -

Input

- -

-The page attempts to emulate an Apple II keyboard. Note that the keyboard -API available in Web browsers is not standardized and is poorly defined; -a best-effort has been made testing on available systems and browsers. -

- -

-The Open Apple and Closed Apple keys on later Apple II models -correspond to the Button #0 and Button #1 inputs and are emulated on modern keyboards -with the Home and End keys respectively. (Unfortunately, browsers -are currently unable to distinguish the left and right Alt or Command keys.) -

- -

-Since the Tab key is necessary for keyboard access to browser functions and -the rest of the web page, it is not available. Otherwise, special keys can be detected -using GET (blocking) or PEEK(49152) (non-blocking): -

- - -
CHR$(8)Left arrow -
CHR$(10)Down arrow -
CHR$(11)Up arrow -
CHR$(13)Enter or Return -
CHR$(21)Right arrow -
CHR$(24)Clear -
CHR$(27)Escape -
CHR$(127)Delete or Backspace -
- - -

Output

- -

-The page attempts to emulate the display of an Apple II system with -80-column card firmware, which can be activated with the -PR#3 statement. -

- -

-When printing characters, CHR$ functions as expected for values -from 32-126 (printable ASCII). Control characters have the typical Apple II meanings: -

- - -
CHR$(4)DOS command escape prefix -
CHR$(7)Make a "beep" (if your browser supports it) -
CHR$(8)Backspace (move cursor left, wrap up) -
CHR$(10)Line feed (move cursor down) -
CHR$(13)Carriage return (move cursor down and to left edge) -
CHR$(127)Displays a cursor glyph -
- -

-If 80-column firmware is active, the following additional codes are available: -

- -
CHR$(11)Clears from the cursor position to the end of the window -
CHR$(12)Move cursor to upper left and clear window -
CHR$(14)Set normal text output -
CHR$(15)Set inverse text output -
CHR$(17)Set display to 40 columns -
CHR$(18)Set display to 80 columns -
CHR$(21)Deactivate the 80-column firmware -
CHR$(22)Scroll display down, preserving cursor position -
CHR$(23)Scroll display up, preserving cursor position -
CHR$(24)Disable mousetext -
CHR$(25)Move cursor to upper left (but don't clear window) -
CHR$(26)Clear the current line -
CHR$(27)Enable mousetext -
CHR$(28)Forward space (move cursor right, wrap down) -
CHR$(29)Clear from cursor position to end of line -
- -

-The text window can be changed and cursor finely controlled -with POKE 32,n ... POKE 37,n -

- - -

Process and Grammars

- -

For the even geekier in the audience...

- -

Compilation is done by splitting the input into tokens which are then -consumed by a recursive descent parser which outputs a JavaScript -object representing the program.

- -

The token types (treated as terminals) are - reserved, - identifier, - string-literal, - number-literal, - operator, - line-number, - separator, - remark, - data-declaration - - take a peek at the code if you want the gruesome details. Source lines may - only start with line numbers or (as an extension) separators. Special statement - parsing is done while lexing: REM consumes anything to the next - line break, and DATA statements yeild an array of strings - from the comma-delimited, optionally-quoted values in the source. -

- -
    -
  • [x] zero or one occurences of x -
  • {x} zero or more occurences of x -
  • x | y one of either x or y -
  • (x) grouping construct -
- -

Overall program parsing is done with a recursive descent parser.

- -
-    Program                  = Line { Line }
-    Line                     = line-number Statement { separator Statement }
-    Statement                = data-declaration | remark | Command | EmptyStatement
-    Command                  = identifier /*...*/ | reserved /*...*/
-
- -

Statements ("commands" in the above grammar) are parsed with distinct cases for each statement type. -Most statements compile into a function call to a library of Applesoft routines. Expressions are parsed -with a standard recursive descent parser. The parser generates JavaScript expressions for each expression, -which are used as arguments for the library calls.

- -
-    Expression               = OrExpression
-    OrExpression             = AndExpression [ 'OR' AndExpression ... ]
-    AndExpression            = RelationalExpression [ 'AND' RelationalExpression ... ]
-    RelationalExpression     = AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '=<' | '>=' | '=>' | '<>' | '><' ) AdditiveExpression ... ]
-    AdditiveExpression       = MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]
-    MultiplicativeExpression = PowerExpression [ ( '*' | '/' ) PowerExpression ... ]
-    PowerExpression          = UnaryExpression [ '^' UnaryExpression ]
-    UnaryExpression          = ( '+' | '-' | 'NOT' ) UnaryExpression
-                              | FinalExpression
-    FinalExpression          = number-literal
-                              | string-literal
-                              | 'FN' user_function_name '(' Expression ')'
-                              | reserved '(' Expression [, Expression ...] ')'
-                              | identifier [ '(' Expression [, Expression ...] ')' ]
-                              | '(' Expression ')'
-
- - -

Since Applesoft supports re-entrant error handling and synchronous input, -the output of the compiler is an array of statement-functions plus a driver -function which implements the logic for walking over the array.

- - -

Extensions beyond Standard Applesoft

- -
    -
  • - [1] DEF FN supports string and integer functions - e.g. DEF FN IN$(X$) = " " + X$ - (but return type must still match argument type) -
  • - [2] == is supported for equality comparisons, in addition to = -
  • - [3] When printing characters, CHR$ values greater than 255 generate glyphs that might be useful for certain maze games. -
  • - [4] HSCRN(x, y) is added to allow reading the - hires screen. On a real Apple this required a machine-language routine (or a shape table and XDRAW). - -
- - - - + + + +Applesoft BASIC Reference + + + + +
+ +

Applesoft BASIC Quick Reference

+ +

+This is intended as a quick reference for the Applesoft BASIC in JavaScript by Joshua Bell. +

+ +

+Extensions beyond Applesoft BASIC are called out with footnotes. +

+ +

See also:

+ + + + + +

Statements

+ + +

Variable Control

+
+
CLEAR
Clear all variables +
[LET] var = expr
Assign variable +
DIM var( size [ , size2 ...] ) [ , var2( size [ , size2 ...] ) ... ]
Allocate array +
DEF FN f(var ) = expr
Define function of a single variable [1] +
+

Flow Control

+
+
GOTO
Go to line number +
GOSUB line
Go to subroutine +
RETURN
Return from subroutine +
ON expr GOTO line [, line2 ... ]
Branch based on index (value = 1, 2, ...) +
ON expr GOSUB line [, line2 ... ]
Subroutine branch based on index (value = 1, 2, ...) +
POP
Convert last gosub into a goto +
FOR var = start TO end [ STEP incr ]
Loop with counter variable +
NEXT [var [, var ...] ]
End of loop(s) +
IF expr THEN statement
IF expr GOTO line
Conditional; if expr is false, rest of line is skipped +
END
Terminate program +
STOP
Break, as if an error occurred +
+

Error Handling

+
+
ONERR GOTO line
Set error hook +
RESUME
Retry line that caused ONERR GOTO +
+

Input/Output

+
+
PRINT expr [ [;,] expr2 ... ] [;]
Output text +
INPUT [string ;] var [, var2 ... ]
Read line of input +
GET var
Read single key +
HOME
Clear text display +
HTAB expr
Position text cursor horizontally +
VTAB expr
Position text cursor vertically +
INVERSE
Set output mode to black-on-white +
FLASH
Set output mode to flashing +
NORMAL
Set output mode to white-on-black +
TEXT
Set display to text mode +
+

Miscellaneous

+
+
REM [ comment ]
Begin a comment; rest of line is skipped +
TRACE
Turn on trace mode (line numbers printed) +
NOTRACE
Turn off trace mode +
+

Inline Data

+
+
DATA value [, value2 ... ]
Define inline data +
READ var [, var2 ... ]
Read the next DATA value +
RESTORE
Restore the DATA pointer to the first value +
+

Lo-Res Graphics

+
+
GR
Set display to lores mode, clear screen +
COLOR= expr
Set lores color (0...15) +
PLOT expr, expr
Plot lores point (x = 0...39, y = 0...40/48) +
HLIN expr, expr AT expr
Plot horizontal line (x1, x2 at y) +
VLIN expr, expr AT expr
Plot vertical line (y1, y2 at x) +
+

Hi-Res Graphics

+
+
HGR
Set display to hires mode, clear screen +
HGR2
Set display to hires mode (page 2), clear screen +
HPLOT [TO] expr, expr [ TO expr, expr ] ...
Plot hires point/line (x=0...279, y=0...191) +
HCOLOR= expr
Set hires color (0...7) +
+

Hi-Res Shape Tables - NOT IMPLEMENTED

+
+
ROT= expr
Set hires shape table rotation (0...63) +
SCALE= expr
Set hires shape table scale (1...255) +
DRAW expr [ AT expr, expr ]
Draw hires shape table shape in color +
XDRAW expr [ AT expr, expr ]
Draw hires shape table shape with XOR +
+

Interpreter and Program State - NOT IMPLEMENTED

+
+
CONT
Continue from a STOP +
DEL
Delete lines of program +
LIST [ expr [, expr ] ]
List lines of program +
NEW
Clear program and variables +
RUN [ expr ]
Start program execution at line +
+

Native Platform Interaction - NOT IMPLEMENTED

+
+
HIMEM: expr
Set upper address of variable memory +
IN# expr
Direct input from slot +
LOMEM: expr
Set lower address of variable memory +
WAIT expr, expr [, expr]
Wait until memory location masked by second argument equals third argument (or zero) +
+

Cassette Tape - NOT IMPLEMENTED

+
+
LOAD
Load program from cassette +
RECALL
Load variables from cassette +
SAVE
Save program to cassette +
STORE
Save variables to cassette +
SHLOAD
Load hires shape table from cassette +
+

Compatibility Shims

+
+
SPEED= expr
Set character output delay - has no effect +
POKE expr, expr
Set memory location to value +
    +
  • POKE 32,n - Text window left edge +
  • POKE 33,n - Text window width +
  • POKE 34,n - Text window top edge +
  • POKE 35,n - Text window bottom +
  • POKE 36,n - Text cursor x +
  • POKE 37,n - Text cursor y +
  • POKE 216,n - ONERR flag (n < 128 disables ONERR handler) +
  • POKE 230,n - Hi-Res plotting page (32 = page 1, 64 = page 2) +
  • POKE 49168,0 - clear keyboard strobe +
  • POKE 49200,0 - toggle speaker (no-op) +
  • POKE 49232,0 - graphics mode +
  • POKE 49233,0 - text mode +
  • POKE 49234,0 - full graphics +
  • POKE 49235,0 - split screen +
  • POKE 49238,0 - lores +
  • POKE 49239,0 - hires +
+ +
CALL expr
Call native routine +
    +
  • CALL -3100 - reveal hi-res page 1 +
  • CALL -3086 - clear current hi-res page to black +
  • CALL -3082 - clear current hi-res page to current color +
  • CALL 54951 - clear stack (cancel pending FOR-NEXT loops and GOSUBs) +
+ +
PR# expr
Direct output to slot +
    +
  • PR#0 - set 40 column mode +
  • PR#3 - set 80 column mode +
+ +
+ + +

Functions

+ + +

Numeric Functions

+
+
ABS( expr )
Absolute value of number +
ATN( expr )
Arctangent of number +
COS( expr )
Cosine of number +
EXP( expr )
Raise e to number +
INT( expr )
Integer part of number +
LOG( expr )
Natural log of number +
RND( expr )
Pseudo-random number generator (0 repeats last, negative reseeds) +
SGN( expr )
Sign of number (-1,0,1) +
SIN( expr )
Sine of number +
SQR( expr )
Square root of number +
TAN( expr )
Tangent of number +
+

String Functions

+
+
LEN( expr )
Length of string +
LEFT$( expr, expr )
Left portion of (string, length) +
MID$( expr, expr [, expr] )
Substring of (string, start character, length) +
RIGHT$( expr, expr )
Right portion of (string, length) +
+

Type Conversion Functions

+
+
ASC( expr )
ASCII code for first character of string +
CHR$( expr )
Character at specified ASCII code point[3] +
STR$( expr )
String representation of number +
VAL( expr )
Parse string into number +
+

System Interaction Functions

+
+
FRE( expr )
Garbage collect strings (returns 0) +
PDL( expr )
Paddle position (paddle number) +
POS( expr )
Horizontal cursor position +
SCRN( expr, expr )
Lores color at pixel (x,y) +
HSCRN( expr, expr )
Hires color at pixel (x,y)[4] +
USR( expr )
Execute assembly code at address, return accumulator value - NOT IMPLEMENTED +
+

User Defined Functions

+
+
FN f( expr )
Execute user defined function [1] +
+ +

Function Compatibility Shims

+
+
PEEK( expr )
Value at memory location +
    +
  • PEEK(32) - Text window left edge +
  • PEEK(33) - Text window width +
  • PEEK(34) - Text window top edge +
  • PEEK(35) - Text window bottom +
  • PEEK(36) - Text cursor x +
  • PEEK(37) - Text cursor y +
  • PEEK(78) & PEEK(79) - Random-Number Field +
  • PEEK(222) - Last error code +
  • PEEK(230) - Hi-Res plotting page (32 = page 1, 64 = page 2) +
  • PEEK(49152) - Read Keyboard +
  • PEEK(49168) - Clear Keyboard strobe +
  • PEEK(49200) - Click Speaker (no-op) +
  • PEEK(49248) - Read Paddle Button #3 - Use the PageDown key +
  • PEEK(49249) - Read Paddle Button #0 - Use the Home key +
  • PEEK(49250) - Read Paddle Button #1 - Use the End key +
  • PEEK(49251) - Read Paddle Button #2 - Use the PageUp or Shift key +
+ +
+ + +

Operators

+ + +

Comparison Operators

+
+
=
Equality[2] +
<
Less than +
>
Greater than +
<=
=<
Less than or equal +
>=
=>
Greater than or equal +
<>
><
Not equal +
+

Boolean Operators

+
+
AND
Conjunction +
OR
Disjunction +
NOT
Negation +
+

Arithmetic Operators

+
+
^
Exponentiation +
*
Multiplication +
/
Division +
+
Addition +
-
Subtraction +
+

String Operators

+
+
+
String Concatenation +
+ + +

Error Codes

+ +

Error codes can be determined by calling +PEEK(222) in an ONERR handler.

+ +
+
0
Next without for +
16
Syntax error - Not generated +
22
Return without gosub +
42
Out of data +
53
Illegal quantity +
69
Overflow +
77
Out of memory - Not generated by all browsers +
90
Undefined statement +
107
Bad subscript +
120
Redimensioned array +
133
Division by zero +
163
Type mismatch +
176
String too long - Not generated +
191
Formula too complex - Not generated by all browsers +
224
Undefined function +
254
Re-enter +
255
Break +
+ + + + +

DOS 3.3 / ProDOS Quick Reference

+ + +

See also

+ + +

DOS Commands

+
+
MON[,C][,I][,O]
Traces DOS 3.3 commands ('Commands', 'Input' and 'Output') +
NOMON[,C][,I][,O]
Cancels tracing of DOS 3.3 commands ('Commands', 'Input' and 'Output') +
OPEN filename[,Llen]
Opens a text file. +
APPEND filename
Appends to a text file. +
CLOSE [filename]
Closes specified (or all) open text files. +
POSITION filename[,Rnum]
Advances position in text file. +
READ filename[,Rnum][,Bbyte]
Reads from a text file. +
WRITE filename[,Rnum][,Bbyte]
Writes to a text file +
DELETE filename
Delete a file +
RENAME oldname,newname
Rename a file +
PR# slot
Same as the BASIC command +
+

Other DOS commands are NOT IMPLEMENTED.

+

Some sample files are present in a server-side store loaded on-demand into a client-side virtual file system (VFS). Creating or writing to a file will write to the VFS. Subsequent reads will read from the VFS. Files may be deleted from the VFS. These changes will not persist if the browser is refreshed or closed.

+ +

DOS Error Codes

+ +

Error codes can be determined by calling +PEEK(222) in an ONERR handler.

+ +
+
1
Language not available - Not generated +
2
Range error - Not generated +
4
Write protected - Not generated +
5
End of data +
6
File not found +
7
Volume mismatch - Not generated +
8
I/O error - Not generated +
9
Disk full - Not generated +
10
File locked - Not generated +
11
Invalid option +
12
No buffers available - Not generated +
13
File type mismatch - Not generated +
14
Program too large - Not generated +
15
Not direct command - Not generated +
+ + +

Input and Output

+ + +

Input

+ +

+The page attempts to emulate an Apple II keyboard. Note that the keyboard +API available in Web browsers is not standardized and is poorly defined; +a best-effort has been made testing on available systems and browsers. +

+ +

+The Open Apple and Closed Apple keys on later Apple II models +correspond to the Button #0 and Button #1 inputs and are emulated on modern keyboards +with the Home and End keys respectively. (Unfortunately, browsers +are currently unable to distinguish the left and right Alt or Command keys.) +

+ +

+Since the Tab key is necessary for keyboard access to browser functions and +the rest of the web page, it is not available. Otherwise, special keys can be detected +using GET (blocking) or PEEK(49152) (non-blocking): +

+ + +
CHR$(8)Left arrow +
CHR$(10)Down arrow +
CHR$(11)Up arrow +
CHR$(13)Enter or Return +
CHR$(21)Right arrow +
CHR$(24)Clear +
CHR$(27)Escape +
CHR$(127)Delete or Backspace +
+ + +

Output

+ +

+The page attempts to emulate the display of an Apple II system with +80-column card firmware, which can be activated with the +PR#3 statement. +

+ +

+When printing characters, CHR$ functions as expected for values +from 32-126 (printable ASCII). Control characters have the typical Apple II meanings: +

+ + +
CHR$(4)DOS command escape prefix +
CHR$(7)Make a "beep" (if your browser supports it) +
CHR$(8)Backspace (move cursor left, wrap up) +
CHR$(10)Line feed (move cursor down) +
CHR$(13)Carriage return (move cursor down and to left edge) +
CHR$(127)Displays a cursor glyph +
+ +

+If 80-column firmware is active, the following additional codes are available: +

+ +
CHR$(11)Clears from the cursor position to the end of the window +
CHR$(12)Move cursor to upper left and clear window +
CHR$(14)Set normal text output +
CHR$(15)Set inverse text output +
CHR$(17)Set display to 40 columns +
CHR$(18)Set display to 80 columns +
CHR$(21)Deactivate the 80-column firmware +
CHR$(22)Scroll display down, preserving cursor position +
CHR$(23)Scroll display up, preserving cursor position +
CHR$(24)Disable mousetext +
CHR$(25)Move cursor to upper left (but don't clear window) +
CHR$(26)Clear the current line +
CHR$(27)Enable mousetext +
CHR$(28)Forward space (move cursor right, wrap down) +
CHR$(29)Clear from cursor position to end of line +
+ +

+The text window can be changed and cursor finely controlled +with POKE 32,n ... POKE 37,n +

+ + +

Process and Grammars

+ +

For the even geekier in the audience...

+ +

Compilation is done by splitting the input into tokens which are then +consumed by a recursive descent parser which outputs a JavaScript +object representing the program.

+ +

The token types (treated as terminals) are + reserved, + identifier, + string-literal, + number-literal, + operator, + line-number, + separator, + remark, + data-declaration + - take a peek at the code if you want the gruesome details. Source lines may + only start with line numbers or (as an extension) separators. Special statement + parsing is done while lexing: REM consumes anything to the next + line break, and DATA statements yeild an array of strings + from the comma-delimited, optionally-quoted values in the source. +

+ +
    +
  • [x] zero or one occurences of x +
  • {x} zero or more occurences of x +
  • x | y one of either x or y +
  • (x) grouping construct +
+ +

Overall program parsing is done with a recursive descent parser.

+ +
+    Program                  = Line { Line }
+    Line                     = line-number Statement { separator Statement }
+    Statement                = data-declaration | remark | Command | EmptyStatement
+    Command                  = identifier /*...*/ | reserved /*...*/
+
+ +

Statements ("commands" in the above grammar) are parsed with distinct cases for each statement type. +Most statements compile into a function call to a library of Applesoft routines. Expressions are parsed +with a standard recursive descent parser. The parser generates JavaScript expressions for each expression, +which are used as arguments for the library calls.

+ +
+    Expression               = OrExpression
+    OrExpression             = AndExpression [ 'OR' AndExpression ... ]
+    AndExpression            = RelationalExpression [ 'AND' RelationalExpression ... ]
+    RelationalExpression     = AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '=<' | '>=' | '=>' | '<>' | '><' ) AdditiveExpression ... ]
+    AdditiveExpression       = MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]
+    MultiplicativeExpression = PowerExpression [ ( '*' | '/' ) PowerExpression ... ]
+    PowerExpression          = UnaryExpression [ '^' UnaryExpression ]
+    UnaryExpression          = ( '+' | '-' | 'NOT' ) UnaryExpression
+                              | FinalExpression
+    FinalExpression          = number-literal
+                              | string-literal
+                              | 'FN' user_function_name '(' Expression ')'
+                              | reserved '(' Expression [, Expression ...] ')'
+                              | identifier [ '(' Expression [, Expression ...] ')' ]
+                              | '(' Expression ')'
+
+ + +

Since Applesoft supports re-entrant error handling and synchronous input, +the output of the compiler is an array of statement-functions plus a driver +function which implements the logic for walking over the array.

+ + +

Extensions beyond Standard Applesoft

+ +
    +
  • + [1] DEF FN supports string and integer functions + e.g. DEF FN IN$(X$) = " " + X$ + (but return type must still match argument type) +
  • + [2] == is supported for equality comparisons, in addition to = +
  • + [3] When printing characters, CHR$ values greater than 255 generate glyphs that might be useful for certain maze games. +
  • + [4] HSCRN(x, y) is added to allow reading the + hires screen. On a real Apple this required a machine-language routine (or a shape table and XDRAW). + +
+ + + + diff --git a/styles.css b/styles.css index 2f880d8..385acd2 100644 --- a/styles.css +++ b/styles.css @@ -1,968 +1,970 @@ - -body { background-color: #EEEACD; } -h1, h2, h3, p { margin-bottom: 6pt; margin-top: 6pt; } -td { vertical-align: top; } - - -/* Apple II Screen */ -.frame {width: 560px; height: 384px; border-style: ridge; border-width: 10px; border-color: gray; padding: 10px; background-color: #202020; } -.wrapper { width: 560px; height: 384px; overflow: hidden; padding: 0; margin: 0; position: relative; } -.lores {position: absolute; z-index: 2; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; visibility: hidden; } -.hires {position: absolute; z-index: 2; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; visibility: hidden; background-color: #000000; } -.tty {position: absolute; z-index: 3; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; } - -/* Character cells */ -.normal { background-color: #000000; color: #80ff80; } -.inverse { background-color: #80ff80; color: #000000; } -.row { background-color: #000000; } /* Background leaks through on safari even with margin/padding/border of 0 */ - -.a2c { height: 16px; line-height: 0; border: 0; padding: 0; margin: 0; background-repeat:no-repeat; } -.a2c-cursor { position: relative; display: inline-block; } -.tty_tr { height: 16px; } -.tty_table { table-layout: fixed; border-collapse: collapse; } - -/* - function pos(code, sw) { - var x = Math.floor(code / 16); - var y = code % 16; - x = -( sw > 40 ? 1 : 2 )* (3 + 13 * x); - y = -2 * (4 + 13 * y); - return String(x) + 'px ' + String(y) + 'px'; - } - function gen(cc, sc, sw, styles) { - console.log('.tty_'+sw+'col'+styles+' .a2c'+cc+' { background-position: '+ pos(sc,sw)+' }'); - } - for( sw = 40; sw <= 80; sw += 40) { - // 0x00-0x1F = INVERSE @ABC…XYZ[\]^_ - // 0x20-0x3F = INVERSE !"#$…<=>? - - for (i = 0x00; i < 0x20; i++) { gen(i+0x00, i+0xC0, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0x20, i+0xA0, sw, ''); } - - // 80-column firmware inactive - // 0x40-0x5F = FLASH @ABC…XYZ[\]^_ - // 0x60-0x7F = FLASH !"#$…<=>? - - for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0x40, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0xC0, sw, '.flash'); } - for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0x20, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0xA0, sw, '.flash'); } - - // 80-column firmware active - // 0x40-0x5F = MOUSETEXT - // 0x60-0x7F = INVERSE `abc…{|}~ - - for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0x80, sw, '.active'); } - for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0xE0, sw, '.active'); } - - // 0x80-0x9F = NORMAL @ABC…XYZ[\]^_ - // 0xA0-0xBF = NORMAL !"#$…<=>? - // 0xC0-0xDF = NORMAL @ABC…XYZ[\]^_ - // 0xE0-0xFF = NORMAL `abc…{|}~ - - for (i = 0x00; i < 0x20; i++) { gen(i+0x80, i+0x40, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0xA0, i+0x20, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0xC0, i+0x40, sw, ''); } - for (i = 0x00; i < 0x20; i++) { gen(i+0xE0, i+0x60, sw, ''); } - - // Extension symbols - for (i = 0x00; i < 0x20; i++) { gen(i+0x100, i+0x100, sw, ''); } - } -*/ - -/* 40 Column Text */ -.tty_40col .a2c { background-image: url(font-40col.png); width: 14px; } - -/* 80 Column Text */ -.tty_80col .a2c { background-image: url(font-80col.png); width: 7px; } - -.tty_40col .a2c0 { background-position: -318px -8px } -.tty_40col .a2c1 { background-position: -318px -34px } -.tty_40col .a2c2 { background-position: -318px -60px } -.tty_40col .a2c3 { background-position: -318px -86px } -.tty_40col .a2c4 { background-position: -318px -112px } -.tty_40col .a2c5 { background-position: -318px -138px } -.tty_40col .a2c6 { background-position: -318px -164px } -.tty_40col .a2c7 { background-position: -318px -190px } -.tty_40col .a2c8 { background-position: -318px -216px } -.tty_40col .a2c9 { background-position: -318px -242px } -.tty_40col .a2c10 { background-position: -318px -268px } -.tty_40col .a2c11 { background-position: -318px -294px } -.tty_40col .a2c12 { background-position: -318px -320px } -.tty_40col .a2c13 { background-position: -318px -346px } -.tty_40col .a2c14 { background-position: -318px -372px } -.tty_40col .a2c15 { background-position: -318px -398px } -.tty_40col .a2c16 { background-position: -344px -8px } -.tty_40col .a2c17 { background-position: -344px -34px } -.tty_40col .a2c18 { background-position: -344px -60px } -.tty_40col .a2c19 { background-position: -344px -86px } -.tty_40col .a2c20 { background-position: -344px -112px } -.tty_40col .a2c21 { background-position: -344px -138px } -.tty_40col .a2c22 { background-position: -344px -164px } -.tty_40col .a2c23 { background-position: -344px -190px } -.tty_40col .a2c24 { background-position: -344px -216px } -.tty_40col .a2c25 { background-position: -344px -242px } -.tty_40col .a2c26 { background-position: -344px -268px } -.tty_40col .a2c27 { background-position: -344px -294px } -.tty_40col .a2c28 { background-position: -344px -320px } -.tty_40col .a2c29 { background-position: -344px -346px } -.tty_40col .a2c30 { background-position: -344px -372px } -.tty_40col .a2c31 { background-position: -344px -398px } -.tty_40col .a2c32 { background-position: -266px -8px } -.tty_40col .a2c33 { background-position: -266px -34px } -.tty_40col .a2c34 { background-position: -266px -60px } -.tty_40col .a2c35 { background-position: -266px -86px } -.tty_40col .a2c36 { background-position: -266px -112px } -.tty_40col .a2c37 { background-position: -266px -138px } -.tty_40col .a2c38 { background-position: -266px -164px } -.tty_40col .a2c39 { background-position: -266px -190px } -.tty_40col .a2c40 { background-position: -266px -216px } -.tty_40col .a2c41 { background-position: -266px -242px } -.tty_40col .a2c42 { background-position: -266px -268px } -.tty_40col .a2c43 { background-position: -266px -294px } -.tty_40col .a2c44 { background-position: -266px -320px } -.tty_40col .a2c45 { background-position: -266px -346px } -.tty_40col .a2c46 { background-position: -266px -372px } -.tty_40col .a2c47 { background-position: -266px -398px } -.tty_40col .a2c48 { background-position: -292px -8px } -.tty_40col .a2c49 { background-position: -292px -34px } -.tty_40col .a2c50 { background-position: -292px -60px } -.tty_40col .a2c51 { background-position: -292px -86px } -.tty_40col .a2c52 { background-position: -292px -112px } -.tty_40col .a2c53 { background-position: -292px -138px } -.tty_40col .a2c54 { background-position: -292px -164px } -.tty_40col .a2c55 { background-position: -292px -190px } -.tty_40col .a2c56 { background-position: -292px -216px } -.tty_40col .a2c57 { background-position: -292px -242px } -.tty_40col .a2c58 { background-position: -292px -268px } -.tty_40col .a2c59 { background-position: -292px -294px } -.tty_40col .a2c60 { background-position: -292px -320px } -.tty_40col .a2c61 { background-position: -292px -346px } -.tty_40col .a2c62 { background-position: -292px -372px } -.tty_40col .a2c63 { background-position: -292px -398px } -.tty_40col .a2c64 { background-position: -110px -8px } -.tty_40col .a2c65 { background-position: -110px -34px } -.tty_40col .a2c66 { background-position: -110px -60px } -.tty_40col .a2c67 { background-position: -110px -86px } -.tty_40col .a2c68 { background-position: -110px -112px } -.tty_40col .a2c69 { background-position: -110px -138px } -.tty_40col .a2c70 { background-position: -110px -164px } -.tty_40col .a2c71 { background-position: -110px -190px } -.tty_40col .a2c72 { background-position: -110px -216px } -.tty_40col .a2c73 { background-position: -110px -242px } -.tty_40col .a2c74 { background-position: -110px -268px } -.tty_40col .a2c75 { background-position: -110px -294px } -.tty_40col .a2c76 { background-position: -110px -320px } -.tty_40col .a2c77 { background-position: -110px -346px } -.tty_40col .a2c78 { background-position: -110px -372px } -.tty_40col .a2c79 { background-position: -110px -398px } -.tty_40col .a2c80 { background-position: -136px -8px } -.tty_40col .a2c81 { background-position: -136px -34px } -.tty_40col .a2c82 { background-position: -136px -60px } -.tty_40col .a2c83 { background-position: -136px -86px } -.tty_40col .a2c84 { background-position: -136px -112px } -.tty_40col .a2c85 { background-position: -136px -138px } -.tty_40col .a2c86 { background-position: -136px -164px } -.tty_40col .a2c87 { background-position: -136px -190px } -.tty_40col .a2c88 { background-position: -136px -216px } -.tty_40col .a2c89 { background-position: -136px -242px } -.tty_40col .a2c90 { background-position: -136px -268px } -.tty_40col .a2c91 { background-position: -136px -294px } -.tty_40col .a2c92 { background-position: -136px -320px } -.tty_40col .a2c93 { background-position: -136px -346px } -.tty_40col .a2c94 { background-position: -136px -372px } -.tty_40col .a2c95 { background-position: -136px -398px } -.tty_40col.flash .a2c64 { background-position: -318px -8px } -.tty_40col.flash .a2c65 { background-position: -318px -34px } -.tty_40col.flash .a2c66 { background-position: -318px -60px } -.tty_40col.flash .a2c67 { background-position: -318px -86px } -.tty_40col.flash .a2c68 { background-position: -318px -112px } -.tty_40col.flash .a2c69 { background-position: -318px -138px } -.tty_40col.flash .a2c70 { background-position: -318px -164px } -.tty_40col.flash .a2c71 { background-position: -318px -190px } -.tty_40col.flash .a2c72 { background-position: -318px -216px } -.tty_40col.flash .a2c73 { background-position: -318px -242px } -.tty_40col.flash .a2c74 { background-position: -318px -268px } -.tty_40col.flash .a2c75 { background-position: -318px -294px } -.tty_40col.flash .a2c76 { background-position: -318px -320px } -.tty_40col.flash .a2c77 { background-position: -318px -346px } -.tty_40col.flash .a2c78 { background-position: -318px -372px } -.tty_40col.flash .a2c79 { background-position: -318px -398px } -.tty_40col.flash .a2c80 { background-position: -344px -8px } -.tty_40col.flash .a2c81 { background-position: -344px -34px } -.tty_40col.flash .a2c82 { background-position: -344px -60px } -.tty_40col.flash .a2c83 { background-position: -344px -86px } -.tty_40col.flash .a2c84 { background-position: -344px -112px } -.tty_40col.flash .a2c85 { background-position: -344px -138px } -.tty_40col.flash .a2c86 { background-position: -344px -164px } -.tty_40col.flash .a2c87 { background-position: -344px -190px } -.tty_40col.flash .a2c88 { background-position: -344px -216px } -.tty_40col.flash .a2c89 { background-position: -344px -242px } -.tty_40col.flash .a2c90 { background-position: -344px -268px } -.tty_40col.flash .a2c91 { background-position: -344px -294px } -.tty_40col.flash .a2c92 { background-position: -344px -320px } -.tty_40col.flash .a2c93 { background-position: -344px -346px } -.tty_40col.flash .a2c94 { background-position: -344px -372px } -.tty_40col.flash .a2c95 { background-position: -344px -398px } -.tty_40col .a2c96 { background-position: -58px -8px } -.tty_40col .a2c97 { background-position: -58px -34px } -.tty_40col .a2c98 { background-position: -58px -60px } -.tty_40col .a2c99 { background-position: -58px -86px } -.tty_40col .a2c100 { background-position: -58px -112px } -.tty_40col .a2c101 { background-position: -58px -138px } -.tty_40col .a2c102 { background-position: -58px -164px } -.tty_40col .a2c103 { background-position: -58px -190px } -.tty_40col .a2c104 { background-position: -58px -216px } -.tty_40col .a2c105 { background-position: -58px -242px } -.tty_40col .a2c106 { background-position: -58px -268px } -.tty_40col .a2c107 { background-position: -58px -294px } -.tty_40col .a2c108 { background-position: -58px -320px } -.tty_40col .a2c109 { background-position: -58px -346px } -.tty_40col .a2c110 { background-position: -58px -372px } -.tty_40col .a2c111 { background-position: -58px -398px } -.tty_40col .a2c112 { background-position: -84px -8px } -.tty_40col .a2c113 { background-position: -84px -34px } -.tty_40col .a2c114 { background-position: -84px -60px } -.tty_40col .a2c115 { background-position: -84px -86px } -.tty_40col .a2c116 { background-position: -84px -112px } -.tty_40col .a2c117 { background-position: -84px -138px } -.tty_40col .a2c118 { background-position: -84px -164px } -.tty_40col .a2c119 { background-position: -84px -190px } -.tty_40col .a2c120 { background-position: -84px -216px } -.tty_40col .a2c121 { background-position: -84px -242px } -.tty_40col .a2c122 { background-position: -84px -268px } -.tty_40col .a2c123 { background-position: -84px -294px } -.tty_40col .a2c124 { background-position: -84px -320px } -.tty_40col .a2c125 { background-position: -84px -346px } -.tty_40col .a2c126 { background-position: -84px -372px } -.tty_40col .a2c127 { background-position: -84px -398px } -.tty_40col.flash .a2c96 { background-position: -266px -8px } -.tty_40col.flash .a2c97 { background-position: -266px -34px } -.tty_40col.flash .a2c98 { background-position: -266px -60px } -.tty_40col.flash .a2c99 { background-position: -266px -86px } -.tty_40col.flash .a2c100 { background-position: -266px -112px } -.tty_40col.flash .a2c101 { background-position: -266px -138px } -.tty_40col.flash .a2c102 { background-position: -266px -164px } -.tty_40col.flash .a2c103 { background-position: -266px -190px } -.tty_40col.flash .a2c104 { background-position: -266px -216px } -.tty_40col.flash .a2c105 { background-position: -266px -242px } -.tty_40col.flash .a2c106 { background-position: -266px -268px } -.tty_40col.flash .a2c107 { background-position: -266px -294px } -.tty_40col.flash .a2c108 { background-position: -266px -320px } -.tty_40col.flash .a2c109 { background-position: -266px -346px } -.tty_40col.flash .a2c110 { background-position: -266px -372px } -.tty_40col.flash .a2c111 { background-position: -266px -398px } -.tty_40col.flash .a2c112 { background-position: -292px -8px } -.tty_40col.flash .a2c113 { background-position: -292px -34px } -.tty_40col.flash .a2c114 { background-position: -292px -60px } -.tty_40col.flash .a2c115 { background-position: -292px -86px } -.tty_40col.flash .a2c116 { background-position: -292px -112px } -.tty_40col.flash .a2c117 { background-position: -292px -138px } -.tty_40col.flash .a2c118 { background-position: -292px -164px } -.tty_40col.flash .a2c119 { background-position: -292px -190px } -.tty_40col.flash .a2c120 { background-position: -292px -216px } -.tty_40col.flash .a2c121 { background-position: -292px -242px } -.tty_40col.flash .a2c122 { background-position: -292px -268px } -.tty_40col.flash .a2c123 { background-position: -292px -294px } -.tty_40col.flash .a2c124 { background-position: -292px -320px } -.tty_40col.flash .a2c125 { background-position: -292px -346px } -.tty_40col.flash .a2c126 { background-position: -292px -372px } -.tty_40col.flash .a2c127 { background-position: -292px -398px } -.tty_40col.active .a2c64 { background-position: -214px -8px } -.tty_40col.active .a2c65 { background-position: -214px -34px } -.tty_40col.active .a2c66 { background-position: -214px -60px } -.tty_40col.active .a2c67 { background-position: -214px -86px } -.tty_40col.active .a2c68 { background-position: -214px -112px } -.tty_40col.active .a2c69 { background-position: -214px -138px } -.tty_40col.active .a2c70 { background-position: -214px -164px } -.tty_40col.active .a2c71 { background-position: -214px -190px } -.tty_40col.active .a2c72 { background-position: -214px -216px } -.tty_40col.active .a2c73 { background-position: -214px -242px } -.tty_40col.active .a2c74 { background-position: -214px -268px } -.tty_40col.active .a2c75 { background-position: -214px -294px } -.tty_40col.active .a2c76 { background-position: -214px -320px } -.tty_40col.active .a2c77 { background-position: -214px -346px } -.tty_40col.active .a2c78 { background-position: -214px -372px } -.tty_40col.active .a2c79 { background-position: -214px -398px } -.tty_40col.active .a2c80 { background-position: -240px -8px } -.tty_40col.active .a2c81 { background-position: -240px -34px } -.tty_40col.active .a2c82 { background-position: -240px -60px } -.tty_40col.active .a2c83 { background-position: -240px -86px } -.tty_40col.active .a2c84 { background-position: -240px -112px } -.tty_40col.active .a2c85 { background-position: -240px -138px } -.tty_40col.active .a2c86 { background-position: -240px -164px } -.tty_40col.active .a2c87 { background-position: -240px -190px } -.tty_40col.active .a2c88 { background-position: -240px -216px } -.tty_40col.active .a2c89 { background-position: -240px -242px } -.tty_40col.active .a2c90 { background-position: -240px -268px } -.tty_40col.active .a2c91 { background-position: -240px -294px } -.tty_40col.active .a2c92 { background-position: -240px -320px } -.tty_40col.active .a2c93 { background-position: -240px -346px } -.tty_40col.active .a2c94 { background-position: -240px -372px } -.tty_40col.active .a2c95 { background-position: -240px -398px } -.tty_40col.active .a2c96 { background-position: -370px -8px } -.tty_40col.active .a2c97 { background-position: -370px -34px } -.tty_40col.active .a2c98 { background-position: -370px -60px } -.tty_40col.active .a2c99 { background-position: -370px -86px } -.tty_40col.active .a2c100 { background-position: -370px -112px } -.tty_40col.active .a2c101 { background-position: -370px -138px } -.tty_40col.active .a2c102 { background-position: -370px -164px } -.tty_40col.active .a2c103 { background-position: -370px -190px } -.tty_40col.active .a2c104 { background-position: -370px -216px } -.tty_40col.active .a2c105 { background-position: -370px -242px } -.tty_40col.active .a2c106 { background-position: -370px -268px } -.tty_40col.active .a2c107 { background-position: -370px -294px } -.tty_40col.active .a2c108 { background-position: -370px -320px } -.tty_40col.active .a2c109 { background-position: -370px -346px } -.tty_40col.active .a2c110 { background-position: -370px -372px } -.tty_40col.active .a2c111 { background-position: -370px -398px } -.tty_40col.active .a2c112 { background-position: -396px -8px } -.tty_40col.active .a2c113 { background-position: -396px -34px } -.tty_40col.active .a2c114 { background-position: -396px -60px } -.tty_40col.active .a2c115 { background-position: -396px -86px } -.tty_40col.active .a2c116 { background-position: -396px -112px } -.tty_40col.active .a2c117 { background-position: -396px -138px } -.tty_40col.active .a2c118 { background-position: -396px -164px } -.tty_40col.active .a2c119 { background-position: -396px -190px } -.tty_40col.active .a2c120 { background-position: -396px -216px } -.tty_40col.active .a2c121 { background-position: -396px -242px } -.tty_40col.active .a2c122 { background-position: -396px -268px } -.tty_40col.active .a2c123 { background-position: -396px -294px } -.tty_40col.active .a2c124 { background-position: -396px -320px } -.tty_40col.active .a2c125 { background-position: -396px -346px } -.tty_40col.active .a2c126 { background-position: -396px -372px } -.tty_40col.active .a2c127 { background-position: -396px -398px } -.tty_40col .a2c128 { background-position: -110px -8px } -.tty_40col .a2c129 { background-position: -110px -34px } -.tty_40col .a2c130 { background-position: -110px -60px } -.tty_40col .a2c131 { background-position: -110px -86px } -.tty_40col .a2c132 { background-position: -110px -112px } -.tty_40col .a2c133 { background-position: -110px -138px } -.tty_40col .a2c134 { background-position: -110px -164px } -.tty_40col .a2c135 { background-position: -110px -190px } -.tty_40col .a2c136 { background-position: -110px -216px } -.tty_40col .a2c137 { background-position: -110px -242px } -.tty_40col .a2c138 { background-position: -110px -268px } -.tty_40col .a2c139 { background-position: -110px -294px } -.tty_40col .a2c140 { background-position: -110px -320px } -.tty_40col .a2c141 { background-position: -110px -346px } -.tty_40col .a2c142 { background-position: -110px -372px } -.tty_40col .a2c143 { background-position: -110px -398px } -.tty_40col .a2c144 { background-position: -136px -8px } -.tty_40col .a2c145 { background-position: -136px -34px } -.tty_40col .a2c146 { background-position: -136px -60px } -.tty_40col .a2c147 { background-position: -136px -86px } -.tty_40col .a2c148 { background-position: -136px -112px } -.tty_40col .a2c149 { background-position: -136px -138px } -.tty_40col .a2c150 { background-position: -136px -164px } -.tty_40col .a2c151 { background-position: -136px -190px } -.tty_40col .a2c152 { background-position: -136px -216px } -.tty_40col .a2c153 { background-position: -136px -242px } -.tty_40col .a2c154 { background-position: -136px -268px } -.tty_40col .a2c155 { background-position: -136px -294px } -.tty_40col .a2c156 { background-position: -136px -320px } -.tty_40col .a2c157 { background-position: -136px -346px } -.tty_40col .a2c158 { background-position: -136px -372px } -.tty_40col .a2c159 { background-position: -136px -398px } -.tty_40col .a2c160 { background-position: -58px -8px } -.tty_40col .a2c161 { background-position: -58px -34px } -.tty_40col .a2c162 { background-position: -58px -60px } -.tty_40col .a2c163 { background-position: -58px -86px } -.tty_40col .a2c164 { background-position: -58px -112px } -.tty_40col .a2c165 { background-position: -58px -138px } -.tty_40col .a2c166 { background-position: -58px -164px } -.tty_40col .a2c167 { background-position: -58px -190px } -.tty_40col .a2c168 { background-position: -58px -216px } -.tty_40col .a2c169 { background-position: -58px -242px } -.tty_40col .a2c170 { background-position: -58px -268px } -.tty_40col .a2c171 { background-position: -58px -294px } -.tty_40col .a2c172 { background-position: -58px -320px } -.tty_40col .a2c173 { background-position: -58px -346px } -.tty_40col .a2c174 { background-position: -58px -372px } -.tty_40col .a2c175 { background-position: -58px -398px } -.tty_40col .a2c176 { background-position: -84px -8px } -.tty_40col .a2c177 { background-position: -84px -34px } -.tty_40col .a2c178 { background-position: -84px -60px } -.tty_40col .a2c179 { background-position: -84px -86px } -.tty_40col .a2c180 { background-position: -84px -112px } -.tty_40col .a2c181 { background-position: -84px -138px } -.tty_40col .a2c182 { background-position: -84px -164px } -.tty_40col .a2c183 { background-position: -84px -190px } -.tty_40col .a2c184 { background-position: -84px -216px } -.tty_40col .a2c185 { background-position: -84px -242px } -.tty_40col .a2c186 { background-position: -84px -268px } -.tty_40col .a2c187 { background-position: -84px -294px } -.tty_40col .a2c188 { background-position: -84px -320px } -.tty_40col .a2c189 { background-position: -84px -346px } -.tty_40col .a2c190 { background-position: -84px -372px } -.tty_40col .a2c191 { background-position: -84px -398px } -.tty_40col .a2c192 { background-position: -110px -8px } -.tty_40col .a2c193 { background-position: -110px -34px } -.tty_40col .a2c194 { background-position: -110px -60px } -.tty_40col .a2c195 { background-position: -110px -86px } -.tty_40col .a2c196 { background-position: -110px -112px } -.tty_40col .a2c197 { background-position: -110px -138px } -.tty_40col .a2c198 { background-position: -110px -164px } -.tty_40col .a2c199 { background-position: -110px -190px } -.tty_40col .a2c200 { background-position: -110px -216px } -.tty_40col .a2c201 { background-position: -110px -242px } -.tty_40col .a2c202 { background-position: -110px -268px } -.tty_40col .a2c203 { background-position: -110px -294px } -.tty_40col .a2c204 { background-position: -110px -320px } -.tty_40col .a2c205 { background-position: -110px -346px } -.tty_40col .a2c206 { background-position: -110px -372px } -.tty_40col .a2c207 { background-position: -110px -398px } -.tty_40col .a2c208 { background-position: -136px -8px } -.tty_40col .a2c209 { background-position: -136px -34px } -.tty_40col .a2c210 { background-position: -136px -60px } -.tty_40col .a2c211 { background-position: -136px -86px } -.tty_40col .a2c212 { background-position: -136px -112px } -.tty_40col .a2c213 { background-position: -136px -138px } -.tty_40col .a2c214 { background-position: -136px -164px } -.tty_40col .a2c215 { background-position: -136px -190px } -.tty_40col .a2c216 { background-position: -136px -216px } -.tty_40col .a2c217 { background-position: -136px -242px } -.tty_40col .a2c218 { background-position: -136px -268px } -.tty_40col .a2c219 { background-position: -136px -294px } -.tty_40col .a2c220 { background-position: -136px -320px } -.tty_40col .a2c221 { background-position: -136px -346px } -.tty_40col .a2c222 { background-position: -136px -372px } -.tty_40col .a2c223 { background-position: -136px -398px } -.tty_40col .a2c224 { background-position: -162px -8px } -.tty_40col .a2c225 { background-position: -162px -34px } -.tty_40col .a2c226 { background-position: -162px -60px } -.tty_40col .a2c227 { background-position: -162px -86px } -.tty_40col .a2c228 { background-position: -162px -112px } -.tty_40col .a2c229 { background-position: -162px -138px } -.tty_40col .a2c230 { background-position: -162px -164px } -.tty_40col .a2c231 { background-position: -162px -190px } -.tty_40col .a2c232 { background-position: -162px -216px } -.tty_40col .a2c233 { background-position: -162px -242px } -.tty_40col .a2c234 { background-position: -162px -268px } -.tty_40col .a2c235 { background-position: -162px -294px } -.tty_40col .a2c236 { background-position: -162px -320px } -.tty_40col .a2c237 { background-position: -162px -346px } -.tty_40col .a2c238 { background-position: -162px -372px } -.tty_40col .a2c239 { background-position: -162px -398px } -.tty_40col .a2c240 { background-position: -188px -8px } -.tty_40col .a2c241 { background-position: -188px -34px } -.tty_40col .a2c242 { background-position: -188px -60px } -.tty_40col .a2c243 { background-position: -188px -86px } -.tty_40col .a2c244 { background-position: -188px -112px } -.tty_40col .a2c245 { background-position: -188px -138px } -.tty_40col .a2c246 { background-position: -188px -164px } -.tty_40col .a2c247 { background-position: -188px -190px } -.tty_40col .a2c248 { background-position: -188px -216px } -.tty_40col .a2c249 { background-position: -188px -242px } -.tty_40col .a2c250 { background-position: -188px -268px } -.tty_40col .a2c251 { background-position: -188px -294px } -.tty_40col .a2c252 { background-position: -188px -320px } -.tty_40col .a2c253 { background-position: -188px -346px } -.tty_40col .a2c254 { background-position: -188px -372px } -.tty_40col .a2c255 { background-position: -188px -398px } - -.tty_40col .a2c256 { background-position: -422px -8px } -.tty_40col .a2c257 { background-position: -422px -34px } -.tty_40col .a2c258 { background-position: -422px -60px } -.tty_40col .a2c259 { background-position: -422px -86px } -.tty_40col .a2c260 { background-position: -422px -112px } -.tty_40col .a2c261 { background-position: -422px -138px } -.tty_40col .a2c262 { background-position: -422px -164px } -.tty_40col .a2c263 { background-position: -422px -190px } -.tty_40col .a2c264 { background-position: -422px -216px } -.tty_40col .a2c265 { background-position: -422px -242px } -.tty_40col .a2c266 { background-position: -422px -268px } -.tty_40col .a2c267 { background-position: -422px -294px } -.tty_40col .a2c268 { background-position: -422px -320px } -.tty_40col .a2c269 { background-position: -422px -346px } -.tty_40col .a2c270 { background-position: -422px -372px } -.tty_40col .a2c271 { background-position: -422px -398px } -.tty_40col .a2c272 { background-position: -448px -8px } -.tty_40col .a2c273 { background-position: -448px -34px } -.tty_40col .a2c274 { background-position: -448px -60px } -.tty_40col .a2c275 { background-position: -448px -86px } -.tty_40col .a2c276 { background-position: -448px -112px } -.tty_40col .a2c277 { background-position: -448px -138px } -.tty_40col .a2c278 { background-position: -448px -164px } -.tty_40col .a2c279 { background-position: -448px -190px } -.tty_40col .a2c280 { background-position: -448px -216px } -.tty_40col .a2c281 { background-position: -448px -242px } -.tty_40col .a2c282 { background-position: -448px -268px } -.tty_40col .a2c283 { background-position: -448px -294px } -.tty_40col .a2c284 { background-position: -448px -320px } -.tty_40col .a2c285 { background-position: -448px -346px } -.tty_40col .a2c286 { background-position: -448px -372px } -.tty_40col .a2c287 { background-position: -448px -398px } - -.tty_80col .a2c0 { background-position: -159px -8px } -.tty_80col .a2c1 { background-position: -159px -34px } -.tty_80col .a2c2 { background-position: -159px -60px } -.tty_80col .a2c3 { background-position: -159px -86px } -.tty_80col .a2c4 { background-position: -159px -112px } -.tty_80col .a2c5 { background-position: -159px -138px } -.tty_80col .a2c6 { background-position: -159px -164px } -.tty_80col .a2c7 { background-position: -159px -190px } -.tty_80col .a2c8 { background-position: -159px -216px } -.tty_80col .a2c9 { background-position: -159px -242px } -.tty_80col .a2c10 { background-position: -159px -268px } -.tty_80col .a2c11 { background-position: -159px -294px } -.tty_80col .a2c12 { background-position: -159px -320px } -.tty_80col .a2c13 { background-position: -159px -346px } -.tty_80col .a2c14 { background-position: -159px -372px } -.tty_80col .a2c15 { background-position: -159px -398px } -.tty_80col .a2c16 { background-position: -172px -8px } -.tty_80col .a2c17 { background-position: -172px -34px } -.tty_80col .a2c18 { background-position: -172px -60px } -.tty_80col .a2c19 { background-position: -172px -86px } -.tty_80col .a2c20 { background-position: -172px -112px } -.tty_80col .a2c21 { background-position: -172px -138px } -.tty_80col .a2c22 { background-position: -172px -164px } -.tty_80col .a2c23 { background-position: -172px -190px } -.tty_80col .a2c24 { background-position: -172px -216px } -.tty_80col .a2c25 { background-position: -172px -242px } -.tty_80col .a2c26 { background-position: -172px -268px } -.tty_80col .a2c27 { background-position: -172px -294px } -.tty_80col .a2c28 { background-position: -172px -320px } -.tty_80col .a2c29 { background-position: -172px -346px } -.tty_80col .a2c30 { background-position: -172px -372px } -.tty_80col .a2c31 { background-position: -172px -398px } -.tty_80col .a2c32 { background-position: -133px -8px } -.tty_80col .a2c33 { background-position: -133px -34px } -.tty_80col .a2c34 { background-position: -133px -60px } -.tty_80col .a2c35 { background-position: -133px -86px } -.tty_80col .a2c36 { background-position: -133px -112px } -.tty_80col .a2c37 { background-position: -133px -138px } -.tty_80col .a2c38 { background-position: -133px -164px } -.tty_80col .a2c39 { background-position: -133px -190px } -.tty_80col .a2c40 { background-position: -133px -216px } -.tty_80col .a2c41 { background-position: -133px -242px } -.tty_80col .a2c42 { background-position: -133px -268px } -.tty_80col .a2c43 { background-position: -133px -294px } -.tty_80col .a2c44 { background-position: -133px -320px } -.tty_80col .a2c45 { background-position: -133px -346px } -.tty_80col .a2c46 { background-position: -133px -372px } -.tty_80col .a2c47 { background-position: -133px -398px } -.tty_80col .a2c48 { background-position: -146px -8px } -.tty_80col .a2c49 { background-position: -146px -34px } -.tty_80col .a2c50 { background-position: -146px -60px } -.tty_80col .a2c51 { background-position: -146px -86px } -.tty_80col .a2c52 { background-position: -146px -112px } -.tty_80col .a2c53 { background-position: -146px -138px } -.tty_80col .a2c54 { background-position: -146px -164px } -.tty_80col .a2c55 { background-position: -146px -190px } -.tty_80col .a2c56 { background-position: -146px -216px } -.tty_80col .a2c57 { background-position: -146px -242px } -.tty_80col .a2c58 { background-position: -146px -268px } -.tty_80col .a2c59 { background-position: -146px -294px } -.tty_80col .a2c60 { background-position: -146px -320px } -.tty_80col .a2c61 { background-position: -146px -346px } -.tty_80col .a2c62 { background-position: -146px -372px } -.tty_80col .a2c63 { background-position: -146px -398px } -.tty_80col .a2c64 { background-position: -55px -8px } -.tty_80col .a2c65 { background-position: -55px -34px } -.tty_80col .a2c66 { background-position: -55px -60px } -.tty_80col .a2c67 { background-position: -55px -86px } -.tty_80col .a2c68 { background-position: -55px -112px } -.tty_80col .a2c69 { background-position: -55px -138px } -.tty_80col .a2c70 { background-position: -55px -164px } -.tty_80col .a2c71 { background-position: -55px -190px } -.tty_80col .a2c72 { background-position: -55px -216px } -.tty_80col .a2c73 { background-position: -55px -242px } -.tty_80col .a2c74 { background-position: -55px -268px } -.tty_80col .a2c75 { background-position: -55px -294px } -.tty_80col .a2c76 { background-position: -55px -320px } -.tty_80col .a2c77 { background-position: -55px -346px } -.tty_80col .a2c78 { background-position: -55px -372px } -.tty_80col .a2c79 { background-position: -55px -398px } -.tty_80col .a2c80 { background-position: -68px -8px } -.tty_80col .a2c81 { background-position: -68px -34px } -.tty_80col .a2c82 { background-position: -68px -60px } -.tty_80col .a2c83 { background-position: -68px -86px } -.tty_80col .a2c84 { background-position: -68px -112px } -.tty_80col .a2c85 { background-position: -68px -138px } -.tty_80col .a2c86 { background-position: -68px -164px } -.tty_80col .a2c87 { background-position: -68px -190px } -.tty_80col .a2c88 { background-position: -68px -216px } -.tty_80col .a2c89 { background-position: -68px -242px } -.tty_80col .a2c90 { background-position: -68px -268px } -.tty_80col .a2c91 { background-position: -68px -294px } -.tty_80col .a2c92 { background-position: -68px -320px } -.tty_80col .a2c93 { background-position: -68px -346px } -.tty_80col .a2c94 { background-position: -68px -372px } -.tty_80col .a2c95 { background-position: -68px -398px } -.tty_80col.flash .a2c64 { background-position: -159px -8px } -.tty_80col.flash .a2c65 { background-position: -159px -34px } -.tty_80col.flash .a2c66 { background-position: -159px -60px } -.tty_80col.flash .a2c67 { background-position: -159px -86px } -.tty_80col.flash .a2c68 { background-position: -159px -112px } -.tty_80col.flash .a2c69 { background-position: -159px -138px } -.tty_80col.flash .a2c70 { background-position: -159px -164px } -.tty_80col.flash .a2c71 { background-position: -159px -190px } -.tty_80col.flash .a2c72 { background-position: -159px -216px } -.tty_80col.flash .a2c73 { background-position: -159px -242px } -.tty_80col.flash .a2c74 { background-position: -159px -268px } -.tty_80col.flash .a2c75 { background-position: -159px -294px } -.tty_80col.flash .a2c76 { background-position: -159px -320px } -.tty_80col.flash .a2c77 { background-position: -159px -346px } -.tty_80col.flash .a2c78 { background-position: -159px -372px } -.tty_80col.flash .a2c79 { background-position: -159px -398px } -.tty_80col.flash .a2c80 { background-position: -172px -8px } -.tty_80col.flash .a2c81 { background-position: -172px -34px } -.tty_80col.flash .a2c82 { background-position: -172px -60px } -.tty_80col.flash .a2c83 { background-position: -172px -86px } -.tty_80col.flash .a2c84 { background-position: -172px -112px } -.tty_80col.flash .a2c85 { background-position: -172px -138px } -.tty_80col.flash .a2c86 { background-position: -172px -164px } -.tty_80col.flash .a2c87 { background-position: -172px -190px } -.tty_80col.flash .a2c88 { background-position: -172px -216px } -.tty_80col.flash .a2c89 { background-position: -172px -242px } -.tty_80col.flash .a2c90 { background-position: -172px -268px } -.tty_80col.flash .a2c91 { background-position: -172px -294px } -.tty_80col.flash .a2c92 { background-position: -172px -320px } -.tty_80col.flash .a2c93 { background-position: -172px -346px } -.tty_80col.flash .a2c94 { background-position: -172px -372px } -.tty_80col.flash .a2c95 { background-position: -172px -398px } -.tty_80col .a2c96 { background-position: -29px -8px } -.tty_80col .a2c97 { background-position: -29px -34px } -.tty_80col .a2c98 { background-position: -29px -60px } -.tty_80col .a2c99 { background-position: -29px -86px } -.tty_80col .a2c100 { background-position: -29px -112px } -.tty_80col .a2c101 { background-position: -29px -138px } -.tty_80col .a2c102 { background-position: -29px -164px } -.tty_80col .a2c103 { background-position: -29px -190px } -.tty_80col .a2c104 { background-position: -29px -216px } -.tty_80col .a2c105 { background-position: -29px -242px } -.tty_80col .a2c106 { background-position: -29px -268px } -.tty_80col .a2c107 { background-position: -29px -294px } -.tty_80col .a2c108 { background-position: -29px -320px } -.tty_80col .a2c109 { background-position: -29px -346px } -.tty_80col .a2c110 { background-position: -29px -372px } -.tty_80col .a2c111 { background-position: -29px -398px } -.tty_80col .a2c112 { background-position: -42px -8px } -.tty_80col .a2c113 { background-position: -42px -34px } -.tty_80col .a2c114 { background-position: -42px -60px } -.tty_80col .a2c115 { background-position: -42px -86px } -.tty_80col .a2c116 { background-position: -42px -112px } -.tty_80col .a2c117 { background-position: -42px -138px } -.tty_80col .a2c118 { background-position: -42px -164px } -.tty_80col .a2c119 { background-position: -42px -190px } -.tty_80col .a2c120 { background-position: -42px -216px } -.tty_80col .a2c121 { background-position: -42px -242px } -.tty_80col .a2c122 { background-position: -42px -268px } -.tty_80col .a2c123 { background-position: -42px -294px } -.tty_80col .a2c124 { background-position: -42px -320px } -.tty_80col .a2c125 { background-position: -42px -346px } -.tty_80col .a2c126 { background-position: -42px -372px } -.tty_80col .a2c127 { background-position: -42px -398px } -.tty_80col.flash .a2c96 { background-position: -133px -8px } -.tty_80col.flash .a2c97 { background-position: -133px -34px } -.tty_80col.flash .a2c98 { background-position: -133px -60px } -.tty_80col.flash .a2c99 { background-position: -133px -86px } -.tty_80col.flash .a2c100 { background-position: -133px -112px } -.tty_80col.flash .a2c101 { background-position: -133px -138px } -.tty_80col.flash .a2c102 { background-position: -133px -164px } -.tty_80col.flash .a2c103 { background-position: -133px -190px } -.tty_80col.flash .a2c104 { background-position: -133px -216px } -.tty_80col.flash .a2c105 { background-position: -133px -242px } -.tty_80col.flash .a2c106 { background-position: -133px -268px } -.tty_80col.flash .a2c107 { background-position: -133px -294px } -.tty_80col.flash .a2c108 { background-position: -133px -320px } -.tty_80col.flash .a2c109 { background-position: -133px -346px } -.tty_80col.flash .a2c110 { background-position: -133px -372px } -.tty_80col.flash .a2c111 { background-position: -133px -398px } -.tty_80col.flash .a2c112 { background-position: -146px -8px } -.tty_80col.flash .a2c113 { background-position: -146px -34px } -.tty_80col.flash .a2c114 { background-position: -146px -60px } -.tty_80col.flash .a2c115 { background-position: -146px -86px } -.tty_80col.flash .a2c116 { background-position: -146px -112px } -.tty_80col.flash .a2c117 { background-position: -146px -138px } -.tty_80col.flash .a2c118 { background-position: -146px -164px } -.tty_80col.flash .a2c119 { background-position: -146px -190px } -.tty_80col.flash .a2c120 { background-position: -146px -216px } -.tty_80col.flash .a2c121 { background-position: -146px -242px } -.tty_80col.flash .a2c122 { background-position: -146px -268px } -.tty_80col.flash .a2c123 { background-position: -146px -294px } -.tty_80col.flash .a2c124 { background-position: -146px -320px } -.tty_80col.flash .a2c125 { background-position: -146px -346px } -.tty_80col.flash .a2c126 { background-position: -146px -372px } -.tty_80col.flash .a2c127 { background-position: -146px -398px } -.tty_80col.active .a2c64 { background-position: -107px -8px } -.tty_80col.active .a2c65 { background-position: -107px -34px } -.tty_80col.active .a2c66 { background-position: -107px -60px } -.tty_80col.active .a2c67 { background-position: -107px -86px } -.tty_80col.active .a2c68 { background-position: -107px -112px } -.tty_80col.active .a2c69 { background-position: -107px -138px } -.tty_80col.active .a2c70 { background-position: -107px -164px } -.tty_80col.active .a2c71 { background-position: -107px -190px } -.tty_80col.active .a2c72 { background-position: -107px -216px } -.tty_80col.active .a2c73 { background-position: -107px -242px } -.tty_80col.active .a2c74 { background-position: -107px -268px } -.tty_80col.active .a2c75 { background-position: -107px -294px } -.tty_80col.active .a2c76 { background-position: -107px -320px } -.tty_80col.active .a2c77 { background-position: -107px -346px } -.tty_80col.active .a2c78 { background-position: -107px -372px } -.tty_80col.active .a2c79 { background-position: -107px -398px } -.tty_80col.active .a2c80 { background-position: -120px -8px } -.tty_80col.active .a2c81 { background-position: -120px -34px } -.tty_80col.active .a2c82 { background-position: -120px -60px } -.tty_80col.active .a2c83 { background-position: -120px -86px } -.tty_80col.active .a2c84 { background-position: -120px -112px } -.tty_80col.active .a2c85 { background-position: -120px -138px } -.tty_80col.active .a2c86 { background-position: -120px -164px } -.tty_80col.active .a2c87 { background-position: -120px -190px } -.tty_80col.active .a2c88 { background-position: -120px -216px } -.tty_80col.active .a2c89 { background-position: -120px -242px } -.tty_80col.active .a2c90 { background-position: -120px -268px } -.tty_80col.active .a2c91 { background-position: -120px -294px } -.tty_80col.active .a2c92 { background-position: -120px -320px } -.tty_80col.active .a2c93 { background-position: -120px -346px } -.tty_80col.active .a2c94 { background-position: -120px -372px } -.tty_80col.active .a2c95 { background-position: -120px -398px } -.tty_80col.active .a2c96 { background-position: -185px -8px } -.tty_80col.active .a2c97 { background-position: -185px -34px } -.tty_80col.active .a2c98 { background-position: -185px -60px } -.tty_80col.active .a2c99 { background-position: -185px -86px } -.tty_80col.active .a2c100 { background-position: -185px -112px } -.tty_80col.active .a2c101 { background-position: -185px -138px } -.tty_80col.active .a2c102 { background-position: -185px -164px } -.tty_80col.active .a2c103 { background-position: -185px -190px } -.tty_80col.active .a2c104 { background-position: -185px -216px } -.tty_80col.active .a2c105 { background-position: -185px -242px } -.tty_80col.active .a2c106 { background-position: -185px -268px } -.tty_80col.active .a2c107 { background-position: -185px -294px } -.tty_80col.active .a2c108 { background-position: -185px -320px } -.tty_80col.active .a2c109 { background-position: -185px -346px } -.tty_80col.active .a2c110 { background-position: -185px -372px } -.tty_80col.active .a2c111 { background-position: -185px -398px } -.tty_80col.active .a2c112 { background-position: -198px -8px } -.tty_80col.active .a2c113 { background-position: -198px -34px } -.tty_80col.active .a2c114 { background-position: -198px -60px } -.tty_80col.active .a2c115 { background-position: -198px -86px } -.tty_80col.active .a2c116 { background-position: -198px -112px } -.tty_80col.active .a2c117 { background-position: -198px -138px } -.tty_80col.active .a2c118 { background-position: -198px -164px } -.tty_80col.active .a2c119 { background-position: -198px -190px } -.tty_80col.active .a2c120 { background-position: -198px -216px } -.tty_80col.active .a2c121 { background-position: -198px -242px } -.tty_80col.active .a2c122 { background-position: -198px -268px } -.tty_80col.active .a2c123 { background-position: -198px -294px } -.tty_80col.active .a2c124 { background-position: -198px -320px } -.tty_80col.active .a2c125 { background-position: -198px -346px } -.tty_80col.active .a2c126 { background-position: -198px -372px } -.tty_80col.active .a2c127 { background-position: -198px -398px } -.tty_80col .a2c128 { background-position: -55px -8px } -.tty_80col .a2c129 { background-position: -55px -34px } -.tty_80col .a2c130 { background-position: -55px -60px } -.tty_80col .a2c131 { background-position: -55px -86px } -.tty_80col .a2c132 { background-position: -55px -112px } -.tty_80col .a2c133 { background-position: -55px -138px } -.tty_80col .a2c134 { background-position: -55px -164px } -.tty_80col .a2c135 { background-position: -55px -190px } -.tty_80col .a2c136 { background-position: -55px -216px } -.tty_80col .a2c137 { background-position: -55px -242px } -.tty_80col .a2c138 { background-position: -55px -268px } -.tty_80col .a2c139 { background-position: -55px -294px } -.tty_80col .a2c140 { background-position: -55px -320px } -.tty_80col .a2c141 { background-position: -55px -346px } -.tty_80col .a2c142 { background-position: -55px -372px } -.tty_80col .a2c143 { background-position: -55px -398px } -.tty_80col .a2c144 { background-position: -68px -8px } -.tty_80col .a2c145 { background-position: -68px -34px } -.tty_80col .a2c146 { background-position: -68px -60px } -.tty_80col .a2c147 { background-position: -68px -86px } -.tty_80col .a2c148 { background-position: -68px -112px } -.tty_80col .a2c149 { background-position: -68px -138px } -.tty_80col .a2c150 { background-position: -68px -164px } -.tty_80col .a2c151 { background-position: -68px -190px } -.tty_80col .a2c152 { background-position: -68px -216px } -.tty_80col .a2c153 { background-position: -68px -242px } -.tty_80col .a2c154 { background-position: -68px -268px } -.tty_80col .a2c155 { background-position: -68px -294px } -.tty_80col .a2c156 { background-position: -68px -320px } -.tty_80col .a2c157 { background-position: -68px -346px } -.tty_80col .a2c158 { background-position: -68px -372px } -.tty_80col .a2c159 { background-position: -68px -398px } -.tty_80col .a2c160 { background-position: -29px -8px } -.tty_80col .a2c161 { background-position: -29px -34px } -.tty_80col .a2c162 { background-position: -29px -60px } -.tty_80col .a2c163 { background-position: -29px -86px } -.tty_80col .a2c164 { background-position: -29px -112px } -.tty_80col .a2c165 { background-position: -29px -138px } -.tty_80col .a2c166 { background-position: -29px -164px } -.tty_80col .a2c167 { background-position: -29px -190px } -.tty_80col .a2c168 { background-position: -29px -216px } -.tty_80col .a2c169 { background-position: -29px -242px } -.tty_80col .a2c170 { background-position: -29px -268px } -.tty_80col .a2c171 { background-position: -29px -294px } -.tty_80col .a2c172 { background-position: -29px -320px } -.tty_80col .a2c173 { background-position: -29px -346px } -.tty_80col .a2c174 { background-position: -29px -372px } -.tty_80col .a2c175 { background-position: -29px -398px } -.tty_80col .a2c176 { background-position: -42px -8px } -.tty_80col .a2c177 { background-position: -42px -34px } -.tty_80col .a2c178 { background-position: -42px -60px } -.tty_80col .a2c179 { background-position: -42px -86px } -.tty_80col .a2c180 { background-position: -42px -112px } -.tty_80col .a2c181 { background-position: -42px -138px } -.tty_80col .a2c182 { background-position: -42px -164px } -.tty_80col .a2c183 { background-position: -42px -190px } -.tty_80col .a2c184 { background-position: -42px -216px } -.tty_80col .a2c185 { background-position: -42px -242px } -.tty_80col .a2c186 { background-position: -42px -268px } -.tty_80col .a2c187 { background-position: -42px -294px } -.tty_80col .a2c188 { background-position: -42px -320px } -.tty_80col .a2c189 { background-position: -42px -346px } -.tty_80col .a2c190 { background-position: -42px -372px } -.tty_80col .a2c191 { background-position: -42px -398px } -.tty_80col .a2c192 { background-position: -55px -8px } -.tty_80col .a2c193 { background-position: -55px -34px } -.tty_80col .a2c194 { background-position: -55px -60px } -.tty_80col .a2c195 { background-position: -55px -86px } -.tty_80col .a2c196 { background-position: -55px -112px } -.tty_80col .a2c197 { background-position: -55px -138px } -.tty_80col .a2c198 { background-position: -55px -164px } -.tty_80col .a2c199 { background-position: -55px -190px } -.tty_80col .a2c200 { background-position: -55px -216px } -.tty_80col .a2c201 { background-position: -55px -242px } -.tty_80col .a2c202 { background-position: -55px -268px } -.tty_80col .a2c203 { background-position: -55px -294px } -.tty_80col .a2c204 { background-position: -55px -320px } -.tty_80col .a2c205 { background-position: -55px -346px } -.tty_80col .a2c206 { background-position: -55px -372px } -.tty_80col .a2c207 { background-position: -55px -398px } -.tty_80col .a2c208 { background-position: -68px -8px } -.tty_80col .a2c209 { background-position: -68px -34px } -.tty_80col .a2c210 { background-position: -68px -60px } -.tty_80col .a2c211 { background-position: -68px -86px } -.tty_80col .a2c212 { background-position: -68px -112px } -.tty_80col .a2c213 { background-position: -68px -138px } -.tty_80col .a2c214 { background-position: -68px -164px } -.tty_80col .a2c215 { background-position: -68px -190px } -.tty_80col .a2c216 { background-position: -68px -216px } -.tty_80col .a2c217 { background-position: -68px -242px } -.tty_80col .a2c218 { background-position: -68px -268px } -.tty_80col .a2c219 { background-position: -68px -294px } -.tty_80col .a2c220 { background-position: -68px -320px } -.tty_80col .a2c221 { background-position: -68px -346px } -.tty_80col .a2c222 { background-position: -68px -372px } -.tty_80col .a2c223 { background-position: -68px -398px } -.tty_80col .a2c224 { background-position: -81px -8px } -.tty_80col .a2c225 { background-position: -81px -34px } -.tty_80col .a2c226 { background-position: -81px -60px } -.tty_80col .a2c227 { background-position: -81px -86px } -.tty_80col .a2c228 { background-position: -81px -112px } -.tty_80col .a2c229 { background-position: -81px -138px } -.tty_80col .a2c230 { background-position: -81px -164px } -.tty_80col .a2c231 { background-position: -81px -190px } -.tty_80col .a2c232 { background-position: -81px -216px } -.tty_80col .a2c233 { background-position: -81px -242px } -.tty_80col .a2c234 { background-position: -81px -268px } -.tty_80col .a2c235 { background-position: -81px -294px } -.tty_80col .a2c236 { background-position: -81px -320px } -.tty_80col .a2c237 { background-position: -81px -346px } -.tty_80col .a2c238 { background-position: -81px -372px } -.tty_80col .a2c239 { background-position: -81px -398px } -.tty_80col .a2c240 { background-position: -94px -8px } -.tty_80col .a2c241 { background-position: -94px -34px } -.tty_80col .a2c242 { background-position: -94px -60px } -.tty_80col .a2c243 { background-position: -94px -86px } -.tty_80col .a2c244 { background-position: -94px -112px } -.tty_80col .a2c245 { background-position: -94px -138px } -.tty_80col .a2c246 { background-position: -94px -164px } -.tty_80col .a2c247 { background-position: -94px -190px } -.tty_80col .a2c248 { background-position: -94px -216px } -.tty_80col .a2c249 { background-position: -94px -242px } -.tty_80col .a2c250 { background-position: -94px -268px } -.tty_80col .a2c251 { background-position: -94px -294px } -.tty_80col .a2c252 { background-position: -94px -320px } -.tty_80col .a2c253 { background-position: -94px -346px } -.tty_80col .a2c254 { background-position: -94px -372px } -.tty_80col .a2c255 { background-position: -94px -398px } - -.tty_80col .a2c256 { background-position: -211px -8px } -.tty_80col .a2c257 { background-position: -211px -34px } -.tty_80col .a2c258 { background-position: -211px -60px } -.tty_80col .a2c259 { background-position: -211px -86px } -.tty_80col .a2c260 { background-position: -211px -112px } -.tty_80col .a2c261 { background-position: -211px -138px } -.tty_80col .a2c262 { background-position: -211px -164px } -.tty_80col .a2c263 { background-position: -211px -190px } -.tty_80col .a2c264 { background-position: -211px -216px } -.tty_80col .a2c265 { background-position: -211px -242px } -.tty_80col .a2c266 { background-position: -211px -268px } -.tty_80col .a2c267 { background-position: -211px -294px } -.tty_80col .a2c268 { background-position: -211px -320px } -.tty_80col .a2c269 { background-position: -211px -346px } -.tty_80col .a2c270 { background-position: -211px -372px } -.tty_80col .a2c271 { background-position: -211px -398px } -.tty_80col .a2c272 { background-position: -224px -8px } -.tty_80col .a2c273 { background-position: -224px -34px } -.tty_80col .a2c274 { background-position: -224px -60px } -.tty_80col .a2c275 { background-position: -224px -86px } -.tty_80col .a2c276 { background-position: -224px -112px } -.tty_80col .a2c277 { background-position: -224px -138px } -.tty_80col .a2c278 { background-position: -224px -164px } -.tty_80col .a2c279 { background-position: -224px -190px } -.tty_80col .a2c280 { background-position: -224px -216px } -.tty_80col .a2c281 { background-position: -224px -242px } -.tty_80col .a2c282 { background-position: -224px -268px } -.tty_80col .a2c283 { background-position: -224px -294px } -.tty_80col .a2c284 { background-position: -224px -320px } -.tty_80col .a2c285 { background-position: -224px -346px } -.tty_80col .a2c286 { background-position: -224px -372px } -.tty_80col .a2c287 { background-position: -224px -398px } - -/* For non-canvas based hires implementations */ -.hiresPixel { width: 4px; height: 2px; background-color: black; } - -.loresPixel { width: 14px; height: 8px; margin: 0; padding: 0; } -.loresRow { height: 8px; margin: 0; padding: 0; } -.loresDisplay { table-layout: fixed; border-collapse: collapse; } - - - -/* Input Devices */ -.keyboard { width: 1px; height: 1px; border: none; position: absolute; left: -100px; top: -100px; } -td.controlbox { vertical-align: middle; padding-left: 12px; padding-right: 12px; } - -#paddle0, #paddle1 -{ - border-style: inset; - border-width: 2px; - border-color: gray; - background-color: #c0c0c0; - height: 20px; - width: 245px; - text-align: center; -} -#knob0, #knob1 -{ - border-style: outset; - border-width: 2px; - border-color: gray; - background-color: #404040; - height: 16px; - width: 10px; -} - -#joystick -{ - border-style: inset; - border-width: 2px; - border-color: gray; - background-color: #c0c0c0; - width: 100px; - height: 100px; - text-align: center; - vertical-align: middle; - line-height: 100px; -} -#stick -{ - border-style: outset; - border-width: 2px; - border-color: gray; - background-color: #404040; - height: 16px; - width: 16px; -} + +body { background-color: #EEEACD; } +h1, h2, h3, p { margin-bottom: 6pt; margin-top: 6pt; } +td { vertical-align: top; } + + +/* Apple II Screen */ +.frame {width: 560px; height: 384px; border-style: ridge; border-width: 10px; border-color: gray; padding: 10px; background-color: #202020; } +.frame.focused { background-color: #204020; } +.wrapper { width: 560px; height: 384px; overflow: hidden; padding: 0; margin: 0; position: relative; } +.lores {position: absolute; z-index: 2; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; visibility: hidden; } +.hires {position: absolute; z-index: 2; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; visibility: hidden; background-color: #000000; } +.tty {position: absolute; z-index: 3; left: 0; top: 0; width: 560px; height: 384px; overflow: hidden; } + + +/* Character cells */ +.normal { background-color: #000000; color: #80ff80; } +.inverse { background-color: #80ff80; color: #000000; } +.row { background-color: #000000; } /* Background leaks through on safari even with margin/padding/border of 0 */ + +.a2c { height: 16px; line-height: 0; border: 0; padding: 0; margin: 0; background-repeat:no-repeat; } +.a2c-cursor { position: relative; display: inline-block; } +.tty_tr { height: 16px; } +.tty_table { table-layout: fixed; border-collapse: collapse; } + +/* + function pos(code, sw) { + var x = Math.floor(code / 16); + var y = code % 16; + x = -( sw > 40 ? 1 : 2 )* (3 + 13 * x); + y = -2 * (4 + 13 * y); + return String(x) + 'px ' + String(y) + 'px'; + } + function gen(cc, sc, sw, styles) { + console.log('.tty_'+sw+'col'+styles+' .a2c'+cc+' { background-position: '+ pos(sc,sw)+' }'); + } + for( sw = 40; sw <= 80; sw += 40) { + // 0x00-0x1F = INVERSE @ABC…XYZ[\]^_ + // 0x20-0x3F = INVERSE !"#$…<=>? + + for (i = 0x00; i < 0x20; i++) { gen(i+0x00, i+0xC0, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0x20, i+0xA0, sw, ''); } + + // 80-column firmware inactive + // 0x40-0x5F = FLASH @ABC…XYZ[\]^_ + // 0x60-0x7F = FLASH !"#$…<=>? + + for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0x40, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0xC0, sw, '.flash'); } + for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0x20, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0xA0, sw, '.flash'); } + + // 80-column firmware active + // 0x40-0x5F = MOUSETEXT + // 0x60-0x7F = INVERSE `abc…{|}~ + + for (i = 0x00; i < 0x20; i++) { gen(i+0x40, i+0x80, sw, '.active'); } + for (i = 0x00; i < 0x20; i++) { gen(i+0x60, i+0xE0, sw, '.active'); } + + // 0x80-0x9F = NORMAL @ABC…XYZ[\]^_ + // 0xA0-0xBF = NORMAL !"#$…<=>? + // 0xC0-0xDF = NORMAL @ABC…XYZ[\]^_ + // 0xE0-0xFF = NORMAL `abc…{|}~ + + for (i = 0x00; i < 0x20; i++) { gen(i+0x80, i+0x40, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0xA0, i+0x20, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0xC0, i+0x40, sw, ''); } + for (i = 0x00; i < 0x20; i++) { gen(i+0xE0, i+0x60, sw, ''); } + + // Extension symbols + for (i = 0x00; i < 0x20; i++) { gen(i+0x100, i+0x100, sw, ''); } + } +*/ + +/* 40 Column Text */ +.tty_40col .a2c { background-image: url(font-40col.png); width: 14px; } + +/* 80 Column Text */ +.tty_80col .a2c { background-image: url(font-80col.png); width: 7px; } + +.tty_40col .a2c0 { background-position: -318px -8px } +.tty_40col .a2c1 { background-position: -318px -34px } +.tty_40col .a2c2 { background-position: -318px -60px } +.tty_40col .a2c3 { background-position: -318px -86px } +.tty_40col .a2c4 { background-position: -318px -112px } +.tty_40col .a2c5 { background-position: -318px -138px } +.tty_40col .a2c6 { background-position: -318px -164px } +.tty_40col .a2c7 { background-position: -318px -190px } +.tty_40col .a2c8 { background-position: -318px -216px } +.tty_40col .a2c9 { background-position: -318px -242px } +.tty_40col .a2c10 { background-position: -318px -268px } +.tty_40col .a2c11 { background-position: -318px -294px } +.tty_40col .a2c12 { background-position: -318px -320px } +.tty_40col .a2c13 { background-position: -318px -346px } +.tty_40col .a2c14 { background-position: -318px -372px } +.tty_40col .a2c15 { background-position: -318px -398px } +.tty_40col .a2c16 { background-position: -344px -8px } +.tty_40col .a2c17 { background-position: -344px -34px } +.tty_40col .a2c18 { background-position: -344px -60px } +.tty_40col .a2c19 { background-position: -344px -86px } +.tty_40col .a2c20 { background-position: -344px -112px } +.tty_40col .a2c21 { background-position: -344px -138px } +.tty_40col .a2c22 { background-position: -344px -164px } +.tty_40col .a2c23 { background-position: -344px -190px } +.tty_40col .a2c24 { background-position: -344px -216px } +.tty_40col .a2c25 { background-position: -344px -242px } +.tty_40col .a2c26 { background-position: -344px -268px } +.tty_40col .a2c27 { background-position: -344px -294px } +.tty_40col .a2c28 { background-position: -344px -320px } +.tty_40col .a2c29 { background-position: -344px -346px } +.tty_40col .a2c30 { background-position: -344px -372px } +.tty_40col .a2c31 { background-position: -344px -398px } +.tty_40col .a2c32 { background-position: -266px -8px } +.tty_40col .a2c33 { background-position: -266px -34px } +.tty_40col .a2c34 { background-position: -266px -60px } +.tty_40col .a2c35 { background-position: -266px -86px } +.tty_40col .a2c36 { background-position: -266px -112px } +.tty_40col .a2c37 { background-position: -266px -138px } +.tty_40col .a2c38 { background-position: -266px -164px } +.tty_40col .a2c39 { background-position: -266px -190px } +.tty_40col .a2c40 { background-position: -266px -216px } +.tty_40col .a2c41 { background-position: -266px -242px } +.tty_40col .a2c42 { background-position: -266px -268px } +.tty_40col .a2c43 { background-position: -266px -294px } +.tty_40col .a2c44 { background-position: -266px -320px } +.tty_40col .a2c45 { background-position: -266px -346px } +.tty_40col .a2c46 { background-position: -266px -372px } +.tty_40col .a2c47 { background-position: -266px -398px } +.tty_40col .a2c48 { background-position: -292px -8px } +.tty_40col .a2c49 { background-position: -292px -34px } +.tty_40col .a2c50 { background-position: -292px -60px } +.tty_40col .a2c51 { background-position: -292px -86px } +.tty_40col .a2c52 { background-position: -292px -112px } +.tty_40col .a2c53 { background-position: -292px -138px } +.tty_40col .a2c54 { background-position: -292px -164px } +.tty_40col .a2c55 { background-position: -292px -190px } +.tty_40col .a2c56 { background-position: -292px -216px } +.tty_40col .a2c57 { background-position: -292px -242px } +.tty_40col .a2c58 { background-position: -292px -268px } +.tty_40col .a2c59 { background-position: -292px -294px } +.tty_40col .a2c60 { background-position: -292px -320px } +.tty_40col .a2c61 { background-position: -292px -346px } +.tty_40col .a2c62 { background-position: -292px -372px } +.tty_40col .a2c63 { background-position: -292px -398px } +.tty_40col .a2c64 { background-position: -110px -8px } +.tty_40col .a2c65 { background-position: -110px -34px } +.tty_40col .a2c66 { background-position: -110px -60px } +.tty_40col .a2c67 { background-position: -110px -86px } +.tty_40col .a2c68 { background-position: -110px -112px } +.tty_40col .a2c69 { background-position: -110px -138px } +.tty_40col .a2c70 { background-position: -110px -164px } +.tty_40col .a2c71 { background-position: -110px -190px } +.tty_40col .a2c72 { background-position: -110px -216px } +.tty_40col .a2c73 { background-position: -110px -242px } +.tty_40col .a2c74 { background-position: -110px -268px } +.tty_40col .a2c75 { background-position: -110px -294px } +.tty_40col .a2c76 { background-position: -110px -320px } +.tty_40col .a2c77 { background-position: -110px -346px } +.tty_40col .a2c78 { background-position: -110px -372px } +.tty_40col .a2c79 { background-position: -110px -398px } +.tty_40col .a2c80 { background-position: -136px -8px } +.tty_40col .a2c81 { background-position: -136px -34px } +.tty_40col .a2c82 { background-position: -136px -60px } +.tty_40col .a2c83 { background-position: -136px -86px } +.tty_40col .a2c84 { background-position: -136px -112px } +.tty_40col .a2c85 { background-position: -136px -138px } +.tty_40col .a2c86 { background-position: -136px -164px } +.tty_40col .a2c87 { background-position: -136px -190px } +.tty_40col .a2c88 { background-position: -136px -216px } +.tty_40col .a2c89 { background-position: -136px -242px } +.tty_40col .a2c90 { background-position: -136px -268px } +.tty_40col .a2c91 { background-position: -136px -294px } +.tty_40col .a2c92 { background-position: -136px -320px } +.tty_40col .a2c93 { background-position: -136px -346px } +.tty_40col .a2c94 { background-position: -136px -372px } +.tty_40col .a2c95 { background-position: -136px -398px } +.tty_40col.flash .a2c64 { background-position: -318px -8px } +.tty_40col.flash .a2c65 { background-position: -318px -34px } +.tty_40col.flash .a2c66 { background-position: -318px -60px } +.tty_40col.flash .a2c67 { background-position: -318px -86px } +.tty_40col.flash .a2c68 { background-position: -318px -112px } +.tty_40col.flash .a2c69 { background-position: -318px -138px } +.tty_40col.flash .a2c70 { background-position: -318px -164px } +.tty_40col.flash .a2c71 { background-position: -318px -190px } +.tty_40col.flash .a2c72 { background-position: -318px -216px } +.tty_40col.flash .a2c73 { background-position: -318px -242px } +.tty_40col.flash .a2c74 { background-position: -318px -268px } +.tty_40col.flash .a2c75 { background-position: -318px -294px } +.tty_40col.flash .a2c76 { background-position: -318px -320px } +.tty_40col.flash .a2c77 { background-position: -318px -346px } +.tty_40col.flash .a2c78 { background-position: -318px -372px } +.tty_40col.flash .a2c79 { background-position: -318px -398px } +.tty_40col.flash .a2c80 { background-position: -344px -8px } +.tty_40col.flash .a2c81 { background-position: -344px -34px } +.tty_40col.flash .a2c82 { background-position: -344px -60px } +.tty_40col.flash .a2c83 { background-position: -344px -86px } +.tty_40col.flash .a2c84 { background-position: -344px -112px } +.tty_40col.flash .a2c85 { background-position: -344px -138px } +.tty_40col.flash .a2c86 { background-position: -344px -164px } +.tty_40col.flash .a2c87 { background-position: -344px -190px } +.tty_40col.flash .a2c88 { background-position: -344px -216px } +.tty_40col.flash .a2c89 { background-position: -344px -242px } +.tty_40col.flash .a2c90 { background-position: -344px -268px } +.tty_40col.flash .a2c91 { background-position: -344px -294px } +.tty_40col.flash .a2c92 { background-position: -344px -320px } +.tty_40col.flash .a2c93 { background-position: -344px -346px } +.tty_40col.flash .a2c94 { background-position: -344px -372px } +.tty_40col.flash .a2c95 { background-position: -344px -398px } +.tty_40col .a2c96 { background-position: -58px -8px } +.tty_40col .a2c97 { background-position: -58px -34px } +.tty_40col .a2c98 { background-position: -58px -60px } +.tty_40col .a2c99 { background-position: -58px -86px } +.tty_40col .a2c100 { background-position: -58px -112px } +.tty_40col .a2c101 { background-position: -58px -138px } +.tty_40col .a2c102 { background-position: -58px -164px } +.tty_40col .a2c103 { background-position: -58px -190px } +.tty_40col .a2c104 { background-position: -58px -216px } +.tty_40col .a2c105 { background-position: -58px -242px } +.tty_40col .a2c106 { background-position: -58px -268px } +.tty_40col .a2c107 { background-position: -58px -294px } +.tty_40col .a2c108 { background-position: -58px -320px } +.tty_40col .a2c109 { background-position: -58px -346px } +.tty_40col .a2c110 { background-position: -58px -372px } +.tty_40col .a2c111 { background-position: -58px -398px } +.tty_40col .a2c112 { background-position: -84px -8px } +.tty_40col .a2c113 { background-position: -84px -34px } +.tty_40col .a2c114 { background-position: -84px -60px } +.tty_40col .a2c115 { background-position: -84px -86px } +.tty_40col .a2c116 { background-position: -84px -112px } +.tty_40col .a2c117 { background-position: -84px -138px } +.tty_40col .a2c118 { background-position: -84px -164px } +.tty_40col .a2c119 { background-position: -84px -190px } +.tty_40col .a2c120 { background-position: -84px -216px } +.tty_40col .a2c121 { background-position: -84px -242px } +.tty_40col .a2c122 { background-position: -84px -268px } +.tty_40col .a2c123 { background-position: -84px -294px } +.tty_40col .a2c124 { background-position: -84px -320px } +.tty_40col .a2c125 { background-position: -84px -346px } +.tty_40col .a2c126 { background-position: -84px -372px } +.tty_40col .a2c127 { background-position: -84px -398px } +.tty_40col.flash .a2c96 { background-position: -266px -8px } +.tty_40col.flash .a2c97 { background-position: -266px -34px } +.tty_40col.flash .a2c98 { background-position: -266px -60px } +.tty_40col.flash .a2c99 { background-position: -266px -86px } +.tty_40col.flash .a2c100 { background-position: -266px -112px } +.tty_40col.flash .a2c101 { background-position: -266px -138px } +.tty_40col.flash .a2c102 { background-position: -266px -164px } +.tty_40col.flash .a2c103 { background-position: -266px -190px } +.tty_40col.flash .a2c104 { background-position: -266px -216px } +.tty_40col.flash .a2c105 { background-position: -266px -242px } +.tty_40col.flash .a2c106 { background-position: -266px -268px } +.tty_40col.flash .a2c107 { background-position: -266px -294px } +.tty_40col.flash .a2c108 { background-position: -266px -320px } +.tty_40col.flash .a2c109 { background-position: -266px -346px } +.tty_40col.flash .a2c110 { background-position: -266px -372px } +.tty_40col.flash .a2c111 { background-position: -266px -398px } +.tty_40col.flash .a2c112 { background-position: -292px -8px } +.tty_40col.flash .a2c113 { background-position: -292px -34px } +.tty_40col.flash .a2c114 { background-position: -292px -60px } +.tty_40col.flash .a2c115 { background-position: -292px -86px } +.tty_40col.flash .a2c116 { background-position: -292px -112px } +.tty_40col.flash .a2c117 { background-position: -292px -138px } +.tty_40col.flash .a2c118 { background-position: -292px -164px } +.tty_40col.flash .a2c119 { background-position: -292px -190px } +.tty_40col.flash .a2c120 { background-position: -292px -216px } +.tty_40col.flash .a2c121 { background-position: -292px -242px } +.tty_40col.flash .a2c122 { background-position: -292px -268px } +.tty_40col.flash .a2c123 { background-position: -292px -294px } +.tty_40col.flash .a2c124 { background-position: -292px -320px } +.tty_40col.flash .a2c125 { background-position: -292px -346px } +.tty_40col.flash .a2c126 { background-position: -292px -372px } +.tty_40col.flash .a2c127 { background-position: -292px -398px } +.tty_40col.active .a2c64 { background-position: -214px -8px } +.tty_40col.active .a2c65 { background-position: -214px -34px } +.tty_40col.active .a2c66 { background-position: -214px -60px } +.tty_40col.active .a2c67 { background-position: -214px -86px } +.tty_40col.active .a2c68 { background-position: -214px -112px } +.tty_40col.active .a2c69 { background-position: -214px -138px } +.tty_40col.active .a2c70 { background-position: -214px -164px } +.tty_40col.active .a2c71 { background-position: -214px -190px } +.tty_40col.active .a2c72 { background-position: -214px -216px } +.tty_40col.active .a2c73 { background-position: -214px -242px } +.tty_40col.active .a2c74 { background-position: -214px -268px } +.tty_40col.active .a2c75 { background-position: -214px -294px } +.tty_40col.active .a2c76 { background-position: -214px -320px } +.tty_40col.active .a2c77 { background-position: -214px -346px } +.tty_40col.active .a2c78 { background-position: -214px -372px } +.tty_40col.active .a2c79 { background-position: -214px -398px } +.tty_40col.active .a2c80 { background-position: -240px -8px } +.tty_40col.active .a2c81 { background-position: -240px -34px } +.tty_40col.active .a2c82 { background-position: -240px -60px } +.tty_40col.active .a2c83 { background-position: -240px -86px } +.tty_40col.active .a2c84 { background-position: -240px -112px } +.tty_40col.active .a2c85 { background-position: -240px -138px } +.tty_40col.active .a2c86 { background-position: -240px -164px } +.tty_40col.active .a2c87 { background-position: -240px -190px } +.tty_40col.active .a2c88 { background-position: -240px -216px } +.tty_40col.active .a2c89 { background-position: -240px -242px } +.tty_40col.active .a2c90 { background-position: -240px -268px } +.tty_40col.active .a2c91 { background-position: -240px -294px } +.tty_40col.active .a2c92 { background-position: -240px -320px } +.tty_40col.active .a2c93 { background-position: -240px -346px } +.tty_40col.active .a2c94 { background-position: -240px -372px } +.tty_40col.active .a2c95 { background-position: -240px -398px } +.tty_40col.active .a2c96 { background-position: -370px -8px } +.tty_40col.active .a2c97 { background-position: -370px -34px } +.tty_40col.active .a2c98 { background-position: -370px -60px } +.tty_40col.active .a2c99 { background-position: -370px -86px } +.tty_40col.active .a2c100 { background-position: -370px -112px } +.tty_40col.active .a2c101 { background-position: -370px -138px } +.tty_40col.active .a2c102 { background-position: -370px -164px } +.tty_40col.active .a2c103 { background-position: -370px -190px } +.tty_40col.active .a2c104 { background-position: -370px -216px } +.tty_40col.active .a2c105 { background-position: -370px -242px } +.tty_40col.active .a2c106 { background-position: -370px -268px } +.tty_40col.active .a2c107 { background-position: -370px -294px } +.tty_40col.active .a2c108 { background-position: -370px -320px } +.tty_40col.active .a2c109 { background-position: -370px -346px } +.tty_40col.active .a2c110 { background-position: -370px -372px } +.tty_40col.active .a2c111 { background-position: -370px -398px } +.tty_40col.active .a2c112 { background-position: -396px -8px } +.tty_40col.active .a2c113 { background-position: -396px -34px } +.tty_40col.active .a2c114 { background-position: -396px -60px } +.tty_40col.active .a2c115 { background-position: -396px -86px } +.tty_40col.active .a2c116 { background-position: -396px -112px } +.tty_40col.active .a2c117 { background-position: -396px -138px } +.tty_40col.active .a2c118 { background-position: -396px -164px } +.tty_40col.active .a2c119 { background-position: -396px -190px } +.tty_40col.active .a2c120 { background-position: -396px -216px } +.tty_40col.active .a2c121 { background-position: -396px -242px } +.tty_40col.active .a2c122 { background-position: -396px -268px } +.tty_40col.active .a2c123 { background-position: -396px -294px } +.tty_40col.active .a2c124 { background-position: -396px -320px } +.tty_40col.active .a2c125 { background-position: -396px -346px } +.tty_40col.active .a2c126 { background-position: -396px -372px } +.tty_40col.active .a2c127 { background-position: -396px -398px } +.tty_40col .a2c128 { background-position: -110px -8px } +.tty_40col .a2c129 { background-position: -110px -34px } +.tty_40col .a2c130 { background-position: -110px -60px } +.tty_40col .a2c131 { background-position: -110px -86px } +.tty_40col .a2c132 { background-position: -110px -112px } +.tty_40col .a2c133 { background-position: -110px -138px } +.tty_40col .a2c134 { background-position: -110px -164px } +.tty_40col .a2c135 { background-position: -110px -190px } +.tty_40col .a2c136 { background-position: -110px -216px } +.tty_40col .a2c137 { background-position: -110px -242px } +.tty_40col .a2c138 { background-position: -110px -268px } +.tty_40col .a2c139 { background-position: -110px -294px } +.tty_40col .a2c140 { background-position: -110px -320px } +.tty_40col .a2c141 { background-position: -110px -346px } +.tty_40col .a2c142 { background-position: -110px -372px } +.tty_40col .a2c143 { background-position: -110px -398px } +.tty_40col .a2c144 { background-position: -136px -8px } +.tty_40col .a2c145 { background-position: -136px -34px } +.tty_40col .a2c146 { background-position: -136px -60px } +.tty_40col .a2c147 { background-position: -136px -86px } +.tty_40col .a2c148 { background-position: -136px -112px } +.tty_40col .a2c149 { background-position: -136px -138px } +.tty_40col .a2c150 { background-position: -136px -164px } +.tty_40col .a2c151 { background-position: -136px -190px } +.tty_40col .a2c152 { background-position: -136px -216px } +.tty_40col .a2c153 { background-position: -136px -242px } +.tty_40col .a2c154 { background-position: -136px -268px } +.tty_40col .a2c155 { background-position: -136px -294px } +.tty_40col .a2c156 { background-position: -136px -320px } +.tty_40col .a2c157 { background-position: -136px -346px } +.tty_40col .a2c158 { background-position: -136px -372px } +.tty_40col .a2c159 { background-position: -136px -398px } +.tty_40col .a2c160 { background-position: -58px -8px } +.tty_40col .a2c161 { background-position: -58px -34px } +.tty_40col .a2c162 { background-position: -58px -60px } +.tty_40col .a2c163 { background-position: -58px -86px } +.tty_40col .a2c164 { background-position: -58px -112px } +.tty_40col .a2c165 { background-position: -58px -138px } +.tty_40col .a2c166 { background-position: -58px -164px } +.tty_40col .a2c167 { background-position: -58px -190px } +.tty_40col .a2c168 { background-position: -58px -216px } +.tty_40col .a2c169 { background-position: -58px -242px } +.tty_40col .a2c170 { background-position: -58px -268px } +.tty_40col .a2c171 { background-position: -58px -294px } +.tty_40col .a2c172 { background-position: -58px -320px } +.tty_40col .a2c173 { background-position: -58px -346px } +.tty_40col .a2c174 { background-position: -58px -372px } +.tty_40col .a2c175 { background-position: -58px -398px } +.tty_40col .a2c176 { background-position: -84px -8px } +.tty_40col .a2c177 { background-position: -84px -34px } +.tty_40col .a2c178 { background-position: -84px -60px } +.tty_40col .a2c179 { background-position: -84px -86px } +.tty_40col .a2c180 { background-position: -84px -112px } +.tty_40col .a2c181 { background-position: -84px -138px } +.tty_40col .a2c182 { background-position: -84px -164px } +.tty_40col .a2c183 { background-position: -84px -190px } +.tty_40col .a2c184 { background-position: -84px -216px } +.tty_40col .a2c185 { background-position: -84px -242px } +.tty_40col .a2c186 { background-position: -84px -268px } +.tty_40col .a2c187 { background-position: -84px -294px } +.tty_40col .a2c188 { background-position: -84px -320px } +.tty_40col .a2c189 { background-position: -84px -346px } +.tty_40col .a2c190 { background-position: -84px -372px } +.tty_40col .a2c191 { background-position: -84px -398px } +.tty_40col .a2c192 { background-position: -110px -8px } +.tty_40col .a2c193 { background-position: -110px -34px } +.tty_40col .a2c194 { background-position: -110px -60px } +.tty_40col .a2c195 { background-position: -110px -86px } +.tty_40col .a2c196 { background-position: -110px -112px } +.tty_40col .a2c197 { background-position: -110px -138px } +.tty_40col .a2c198 { background-position: -110px -164px } +.tty_40col .a2c199 { background-position: -110px -190px } +.tty_40col .a2c200 { background-position: -110px -216px } +.tty_40col .a2c201 { background-position: -110px -242px } +.tty_40col .a2c202 { background-position: -110px -268px } +.tty_40col .a2c203 { background-position: -110px -294px } +.tty_40col .a2c204 { background-position: -110px -320px } +.tty_40col .a2c205 { background-position: -110px -346px } +.tty_40col .a2c206 { background-position: -110px -372px } +.tty_40col .a2c207 { background-position: -110px -398px } +.tty_40col .a2c208 { background-position: -136px -8px } +.tty_40col .a2c209 { background-position: -136px -34px } +.tty_40col .a2c210 { background-position: -136px -60px } +.tty_40col .a2c211 { background-position: -136px -86px } +.tty_40col .a2c212 { background-position: -136px -112px } +.tty_40col .a2c213 { background-position: -136px -138px } +.tty_40col .a2c214 { background-position: -136px -164px } +.tty_40col .a2c215 { background-position: -136px -190px } +.tty_40col .a2c216 { background-position: -136px -216px } +.tty_40col .a2c217 { background-position: -136px -242px } +.tty_40col .a2c218 { background-position: -136px -268px } +.tty_40col .a2c219 { background-position: -136px -294px } +.tty_40col .a2c220 { background-position: -136px -320px } +.tty_40col .a2c221 { background-position: -136px -346px } +.tty_40col .a2c222 { background-position: -136px -372px } +.tty_40col .a2c223 { background-position: -136px -398px } +.tty_40col .a2c224 { background-position: -162px -8px } +.tty_40col .a2c225 { background-position: -162px -34px } +.tty_40col .a2c226 { background-position: -162px -60px } +.tty_40col .a2c227 { background-position: -162px -86px } +.tty_40col .a2c228 { background-position: -162px -112px } +.tty_40col .a2c229 { background-position: -162px -138px } +.tty_40col .a2c230 { background-position: -162px -164px } +.tty_40col .a2c231 { background-position: -162px -190px } +.tty_40col .a2c232 { background-position: -162px -216px } +.tty_40col .a2c233 { background-position: -162px -242px } +.tty_40col .a2c234 { background-position: -162px -268px } +.tty_40col .a2c235 { background-position: -162px -294px } +.tty_40col .a2c236 { background-position: -162px -320px } +.tty_40col .a2c237 { background-position: -162px -346px } +.tty_40col .a2c238 { background-position: -162px -372px } +.tty_40col .a2c239 { background-position: -162px -398px } +.tty_40col .a2c240 { background-position: -188px -8px } +.tty_40col .a2c241 { background-position: -188px -34px } +.tty_40col .a2c242 { background-position: -188px -60px } +.tty_40col .a2c243 { background-position: -188px -86px } +.tty_40col .a2c244 { background-position: -188px -112px } +.tty_40col .a2c245 { background-position: -188px -138px } +.tty_40col .a2c246 { background-position: -188px -164px } +.tty_40col .a2c247 { background-position: -188px -190px } +.tty_40col .a2c248 { background-position: -188px -216px } +.tty_40col .a2c249 { background-position: -188px -242px } +.tty_40col .a2c250 { background-position: -188px -268px } +.tty_40col .a2c251 { background-position: -188px -294px } +.tty_40col .a2c252 { background-position: -188px -320px } +.tty_40col .a2c253 { background-position: -188px -346px } +.tty_40col .a2c254 { background-position: -188px -372px } +.tty_40col .a2c255 { background-position: -188px -398px } + +.tty_40col .a2c256 { background-position: -422px -8px } +.tty_40col .a2c257 { background-position: -422px -34px } +.tty_40col .a2c258 { background-position: -422px -60px } +.tty_40col .a2c259 { background-position: -422px -86px } +.tty_40col .a2c260 { background-position: -422px -112px } +.tty_40col .a2c261 { background-position: -422px -138px } +.tty_40col .a2c262 { background-position: -422px -164px } +.tty_40col .a2c263 { background-position: -422px -190px } +.tty_40col .a2c264 { background-position: -422px -216px } +.tty_40col .a2c265 { background-position: -422px -242px } +.tty_40col .a2c266 { background-position: -422px -268px } +.tty_40col .a2c267 { background-position: -422px -294px } +.tty_40col .a2c268 { background-position: -422px -320px } +.tty_40col .a2c269 { background-position: -422px -346px } +.tty_40col .a2c270 { background-position: -422px -372px } +.tty_40col .a2c271 { background-position: -422px -398px } +.tty_40col .a2c272 { background-position: -448px -8px } +.tty_40col .a2c273 { background-position: -448px -34px } +.tty_40col .a2c274 { background-position: -448px -60px } +.tty_40col .a2c275 { background-position: -448px -86px } +.tty_40col .a2c276 { background-position: -448px -112px } +.tty_40col .a2c277 { background-position: -448px -138px } +.tty_40col .a2c278 { background-position: -448px -164px } +.tty_40col .a2c279 { background-position: -448px -190px } +.tty_40col .a2c280 { background-position: -448px -216px } +.tty_40col .a2c281 { background-position: -448px -242px } +.tty_40col .a2c282 { background-position: -448px -268px } +.tty_40col .a2c283 { background-position: -448px -294px } +.tty_40col .a2c284 { background-position: -448px -320px } +.tty_40col .a2c285 { background-position: -448px -346px } +.tty_40col .a2c286 { background-position: -448px -372px } +.tty_40col .a2c287 { background-position: -448px -398px } + +.tty_80col .a2c0 { background-position: -159px -8px } +.tty_80col .a2c1 { background-position: -159px -34px } +.tty_80col .a2c2 { background-position: -159px -60px } +.tty_80col .a2c3 { background-position: -159px -86px } +.tty_80col .a2c4 { background-position: -159px -112px } +.tty_80col .a2c5 { background-position: -159px -138px } +.tty_80col .a2c6 { background-position: -159px -164px } +.tty_80col .a2c7 { background-position: -159px -190px } +.tty_80col .a2c8 { background-position: -159px -216px } +.tty_80col .a2c9 { background-position: -159px -242px } +.tty_80col .a2c10 { background-position: -159px -268px } +.tty_80col .a2c11 { background-position: -159px -294px } +.tty_80col .a2c12 { background-position: -159px -320px } +.tty_80col .a2c13 { background-position: -159px -346px } +.tty_80col .a2c14 { background-position: -159px -372px } +.tty_80col .a2c15 { background-position: -159px -398px } +.tty_80col .a2c16 { background-position: -172px -8px } +.tty_80col .a2c17 { background-position: -172px -34px } +.tty_80col .a2c18 { background-position: -172px -60px } +.tty_80col .a2c19 { background-position: -172px -86px } +.tty_80col .a2c20 { background-position: -172px -112px } +.tty_80col .a2c21 { background-position: -172px -138px } +.tty_80col .a2c22 { background-position: -172px -164px } +.tty_80col .a2c23 { background-position: -172px -190px } +.tty_80col .a2c24 { background-position: -172px -216px } +.tty_80col .a2c25 { background-position: -172px -242px } +.tty_80col .a2c26 { background-position: -172px -268px } +.tty_80col .a2c27 { background-position: -172px -294px } +.tty_80col .a2c28 { background-position: -172px -320px } +.tty_80col .a2c29 { background-position: -172px -346px } +.tty_80col .a2c30 { background-position: -172px -372px } +.tty_80col .a2c31 { background-position: -172px -398px } +.tty_80col .a2c32 { background-position: -133px -8px } +.tty_80col .a2c33 { background-position: -133px -34px } +.tty_80col .a2c34 { background-position: -133px -60px } +.tty_80col .a2c35 { background-position: -133px -86px } +.tty_80col .a2c36 { background-position: -133px -112px } +.tty_80col .a2c37 { background-position: -133px -138px } +.tty_80col .a2c38 { background-position: -133px -164px } +.tty_80col .a2c39 { background-position: -133px -190px } +.tty_80col .a2c40 { background-position: -133px -216px } +.tty_80col .a2c41 { background-position: -133px -242px } +.tty_80col .a2c42 { background-position: -133px -268px } +.tty_80col .a2c43 { background-position: -133px -294px } +.tty_80col .a2c44 { background-position: -133px -320px } +.tty_80col .a2c45 { background-position: -133px -346px } +.tty_80col .a2c46 { background-position: -133px -372px } +.tty_80col .a2c47 { background-position: -133px -398px } +.tty_80col .a2c48 { background-position: -146px -8px } +.tty_80col .a2c49 { background-position: -146px -34px } +.tty_80col .a2c50 { background-position: -146px -60px } +.tty_80col .a2c51 { background-position: -146px -86px } +.tty_80col .a2c52 { background-position: -146px -112px } +.tty_80col .a2c53 { background-position: -146px -138px } +.tty_80col .a2c54 { background-position: -146px -164px } +.tty_80col .a2c55 { background-position: -146px -190px } +.tty_80col .a2c56 { background-position: -146px -216px } +.tty_80col .a2c57 { background-position: -146px -242px } +.tty_80col .a2c58 { background-position: -146px -268px } +.tty_80col .a2c59 { background-position: -146px -294px } +.tty_80col .a2c60 { background-position: -146px -320px } +.tty_80col .a2c61 { background-position: -146px -346px } +.tty_80col .a2c62 { background-position: -146px -372px } +.tty_80col .a2c63 { background-position: -146px -398px } +.tty_80col .a2c64 { background-position: -55px -8px } +.tty_80col .a2c65 { background-position: -55px -34px } +.tty_80col .a2c66 { background-position: -55px -60px } +.tty_80col .a2c67 { background-position: -55px -86px } +.tty_80col .a2c68 { background-position: -55px -112px } +.tty_80col .a2c69 { background-position: -55px -138px } +.tty_80col .a2c70 { background-position: -55px -164px } +.tty_80col .a2c71 { background-position: -55px -190px } +.tty_80col .a2c72 { background-position: -55px -216px } +.tty_80col .a2c73 { background-position: -55px -242px } +.tty_80col .a2c74 { background-position: -55px -268px } +.tty_80col .a2c75 { background-position: -55px -294px } +.tty_80col .a2c76 { background-position: -55px -320px } +.tty_80col .a2c77 { background-position: -55px -346px } +.tty_80col .a2c78 { background-position: -55px -372px } +.tty_80col .a2c79 { background-position: -55px -398px } +.tty_80col .a2c80 { background-position: -68px -8px } +.tty_80col .a2c81 { background-position: -68px -34px } +.tty_80col .a2c82 { background-position: -68px -60px } +.tty_80col .a2c83 { background-position: -68px -86px } +.tty_80col .a2c84 { background-position: -68px -112px } +.tty_80col .a2c85 { background-position: -68px -138px } +.tty_80col .a2c86 { background-position: -68px -164px } +.tty_80col .a2c87 { background-position: -68px -190px } +.tty_80col .a2c88 { background-position: -68px -216px } +.tty_80col .a2c89 { background-position: -68px -242px } +.tty_80col .a2c90 { background-position: -68px -268px } +.tty_80col .a2c91 { background-position: -68px -294px } +.tty_80col .a2c92 { background-position: -68px -320px } +.tty_80col .a2c93 { background-position: -68px -346px } +.tty_80col .a2c94 { background-position: -68px -372px } +.tty_80col .a2c95 { background-position: -68px -398px } +.tty_80col.flash .a2c64 { background-position: -159px -8px } +.tty_80col.flash .a2c65 { background-position: -159px -34px } +.tty_80col.flash .a2c66 { background-position: -159px -60px } +.tty_80col.flash .a2c67 { background-position: -159px -86px } +.tty_80col.flash .a2c68 { background-position: -159px -112px } +.tty_80col.flash .a2c69 { background-position: -159px -138px } +.tty_80col.flash .a2c70 { background-position: -159px -164px } +.tty_80col.flash .a2c71 { background-position: -159px -190px } +.tty_80col.flash .a2c72 { background-position: -159px -216px } +.tty_80col.flash .a2c73 { background-position: -159px -242px } +.tty_80col.flash .a2c74 { background-position: -159px -268px } +.tty_80col.flash .a2c75 { background-position: -159px -294px } +.tty_80col.flash .a2c76 { background-position: -159px -320px } +.tty_80col.flash .a2c77 { background-position: -159px -346px } +.tty_80col.flash .a2c78 { background-position: -159px -372px } +.tty_80col.flash .a2c79 { background-position: -159px -398px } +.tty_80col.flash .a2c80 { background-position: -172px -8px } +.tty_80col.flash .a2c81 { background-position: -172px -34px } +.tty_80col.flash .a2c82 { background-position: -172px -60px } +.tty_80col.flash .a2c83 { background-position: -172px -86px } +.tty_80col.flash .a2c84 { background-position: -172px -112px } +.tty_80col.flash .a2c85 { background-position: -172px -138px } +.tty_80col.flash .a2c86 { background-position: -172px -164px } +.tty_80col.flash .a2c87 { background-position: -172px -190px } +.tty_80col.flash .a2c88 { background-position: -172px -216px } +.tty_80col.flash .a2c89 { background-position: -172px -242px } +.tty_80col.flash .a2c90 { background-position: -172px -268px } +.tty_80col.flash .a2c91 { background-position: -172px -294px } +.tty_80col.flash .a2c92 { background-position: -172px -320px } +.tty_80col.flash .a2c93 { background-position: -172px -346px } +.tty_80col.flash .a2c94 { background-position: -172px -372px } +.tty_80col.flash .a2c95 { background-position: -172px -398px } +.tty_80col .a2c96 { background-position: -29px -8px } +.tty_80col .a2c97 { background-position: -29px -34px } +.tty_80col .a2c98 { background-position: -29px -60px } +.tty_80col .a2c99 { background-position: -29px -86px } +.tty_80col .a2c100 { background-position: -29px -112px } +.tty_80col .a2c101 { background-position: -29px -138px } +.tty_80col .a2c102 { background-position: -29px -164px } +.tty_80col .a2c103 { background-position: -29px -190px } +.tty_80col .a2c104 { background-position: -29px -216px } +.tty_80col .a2c105 { background-position: -29px -242px } +.tty_80col .a2c106 { background-position: -29px -268px } +.tty_80col .a2c107 { background-position: -29px -294px } +.tty_80col .a2c108 { background-position: -29px -320px } +.tty_80col .a2c109 { background-position: -29px -346px } +.tty_80col .a2c110 { background-position: -29px -372px } +.tty_80col .a2c111 { background-position: -29px -398px } +.tty_80col .a2c112 { background-position: -42px -8px } +.tty_80col .a2c113 { background-position: -42px -34px } +.tty_80col .a2c114 { background-position: -42px -60px } +.tty_80col .a2c115 { background-position: -42px -86px } +.tty_80col .a2c116 { background-position: -42px -112px } +.tty_80col .a2c117 { background-position: -42px -138px } +.tty_80col .a2c118 { background-position: -42px -164px } +.tty_80col .a2c119 { background-position: -42px -190px } +.tty_80col .a2c120 { background-position: -42px -216px } +.tty_80col .a2c121 { background-position: -42px -242px } +.tty_80col .a2c122 { background-position: -42px -268px } +.tty_80col .a2c123 { background-position: -42px -294px } +.tty_80col .a2c124 { background-position: -42px -320px } +.tty_80col .a2c125 { background-position: -42px -346px } +.tty_80col .a2c126 { background-position: -42px -372px } +.tty_80col .a2c127 { background-position: -42px -398px } +.tty_80col.flash .a2c96 { background-position: -133px -8px } +.tty_80col.flash .a2c97 { background-position: -133px -34px } +.tty_80col.flash .a2c98 { background-position: -133px -60px } +.tty_80col.flash .a2c99 { background-position: -133px -86px } +.tty_80col.flash .a2c100 { background-position: -133px -112px } +.tty_80col.flash .a2c101 { background-position: -133px -138px } +.tty_80col.flash .a2c102 { background-position: -133px -164px } +.tty_80col.flash .a2c103 { background-position: -133px -190px } +.tty_80col.flash .a2c104 { background-position: -133px -216px } +.tty_80col.flash .a2c105 { background-position: -133px -242px } +.tty_80col.flash .a2c106 { background-position: -133px -268px } +.tty_80col.flash .a2c107 { background-position: -133px -294px } +.tty_80col.flash .a2c108 { background-position: -133px -320px } +.tty_80col.flash .a2c109 { background-position: -133px -346px } +.tty_80col.flash .a2c110 { background-position: -133px -372px } +.tty_80col.flash .a2c111 { background-position: -133px -398px } +.tty_80col.flash .a2c112 { background-position: -146px -8px } +.tty_80col.flash .a2c113 { background-position: -146px -34px } +.tty_80col.flash .a2c114 { background-position: -146px -60px } +.tty_80col.flash .a2c115 { background-position: -146px -86px } +.tty_80col.flash .a2c116 { background-position: -146px -112px } +.tty_80col.flash .a2c117 { background-position: -146px -138px } +.tty_80col.flash .a2c118 { background-position: -146px -164px } +.tty_80col.flash .a2c119 { background-position: -146px -190px } +.tty_80col.flash .a2c120 { background-position: -146px -216px } +.tty_80col.flash .a2c121 { background-position: -146px -242px } +.tty_80col.flash .a2c122 { background-position: -146px -268px } +.tty_80col.flash .a2c123 { background-position: -146px -294px } +.tty_80col.flash .a2c124 { background-position: -146px -320px } +.tty_80col.flash .a2c125 { background-position: -146px -346px } +.tty_80col.flash .a2c126 { background-position: -146px -372px } +.tty_80col.flash .a2c127 { background-position: -146px -398px } +.tty_80col.active .a2c64 { background-position: -107px -8px } +.tty_80col.active .a2c65 { background-position: -107px -34px } +.tty_80col.active .a2c66 { background-position: -107px -60px } +.tty_80col.active .a2c67 { background-position: -107px -86px } +.tty_80col.active .a2c68 { background-position: -107px -112px } +.tty_80col.active .a2c69 { background-position: -107px -138px } +.tty_80col.active .a2c70 { background-position: -107px -164px } +.tty_80col.active .a2c71 { background-position: -107px -190px } +.tty_80col.active .a2c72 { background-position: -107px -216px } +.tty_80col.active .a2c73 { background-position: -107px -242px } +.tty_80col.active .a2c74 { background-position: -107px -268px } +.tty_80col.active .a2c75 { background-position: -107px -294px } +.tty_80col.active .a2c76 { background-position: -107px -320px } +.tty_80col.active .a2c77 { background-position: -107px -346px } +.tty_80col.active .a2c78 { background-position: -107px -372px } +.tty_80col.active .a2c79 { background-position: -107px -398px } +.tty_80col.active .a2c80 { background-position: -120px -8px } +.tty_80col.active .a2c81 { background-position: -120px -34px } +.tty_80col.active .a2c82 { background-position: -120px -60px } +.tty_80col.active .a2c83 { background-position: -120px -86px } +.tty_80col.active .a2c84 { background-position: -120px -112px } +.tty_80col.active .a2c85 { background-position: -120px -138px } +.tty_80col.active .a2c86 { background-position: -120px -164px } +.tty_80col.active .a2c87 { background-position: -120px -190px } +.tty_80col.active .a2c88 { background-position: -120px -216px } +.tty_80col.active .a2c89 { background-position: -120px -242px } +.tty_80col.active .a2c90 { background-position: -120px -268px } +.tty_80col.active .a2c91 { background-position: -120px -294px } +.tty_80col.active .a2c92 { background-position: -120px -320px } +.tty_80col.active .a2c93 { background-position: -120px -346px } +.tty_80col.active .a2c94 { background-position: -120px -372px } +.tty_80col.active .a2c95 { background-position: -120px -398px } +.tty_80col.active .a2c96 { background-position: -185px -8px } +.tty_80col.active .a2c97 { background-position: -185px -34px } +.tty_80col.active .a2c98 { background-position: -185px -60px } +.tty_80col.active .a2c99 { background-position: -185px -86px } +.tty_80col.active .a2c100 { background-position: -185px -112px } +.tty_80col.active .a2c101 { background-position: -185px -138px } +.tty_80col.active .a2c102 { background-position: -185px -164px } +.tty_80col.active .a2c103 { background-position: -185px -190px } +.tty_80col.active .a2c104 { background-position: -185px -216px } +.tty_80col.active .a2c105 { background-position: -185px -242px } +.tty_80col.active .a2c106 { background-position: -185px -268px } +.tty_80col.active .a2c107 { background-position: -185px -294px } +.tty_80col.active .a2c108 { background-position: -185px -320px } +.tty_80col.active .a2c109 { background-position: -185px -346px } +.tty_80col.active .a2c110 { background-position: -185px -372px } +.tty_80col.active .a2c111 { background-position: -185px -398px } +.tty_80col.active .a2c112 { background-position: -198px -8px } +.tty_80col.active .a2c113 { background-position: -198px -34px } +.tty_80col.active .a2c114 { background-position: -198px -60px } +.tty_80col.active .a2c115 { background-position: -198px -86px } +.tty_80col.active .a2c116 { background-position: -198px -112px } +.tty_80col.active .a2c117 { background-position: -198px -138px } +.tty_80col.active .a2c118 { background-position: -198px -164px } +.tty_80col.active .a2c119 { background-position: -198px -190px } +.tty_80col.active .a2c120 { background-position: -198px -216px } +.tty_80col.active .a2c121 { background-position: -198px -242px } +.tty_80col.active .a2c122 { background-position: -198px -268px } +.tty_80col.active .a2c123 { background-position: -198px -294px } +.tty_80col.active .a2c124 { background-position: -198px -320px } +.tty_80col.active .a2c125 { background-position: -198px -346px } +.tty_80col.active .a2c126 { background-position: -198px -372px } +.tty_80col.active .a2c127 { background-position: -198px -398px } +.tty_80col .a2c128 { background-position: -55px -8px } +.tty_80col .a2c129 { background-position: -55px -34px } +.tty_80col .a2c130 { background-position: -55px -60px } +.tty_80col .a2c131 { background-position: -55px -86px } +.tty_80col .a2c132 { background-position: -55px -112px } +.tty_80col .a2c133 { background-position: -55px -138px } +.tty_80col .a2c134 { background-position: -55px -164px } +.tty_80col .a2c135 { background-position: -55px -190px } +.tty_80col .a2c136 { background-position: -55px -216px } +.tty_80col .a2c137 { background-position: -55px -242px } +.tty_80col .a2c138 { background-position: -55px -268px } +.tty_80col .a2c139 { background-position: -55px -294px } +.tty_80col .a2c140 { background-position: -55px -320px } +.tty_80col .a2c141 { background-position: -55px -346px } +.tty_80col .a2c142 { background-position: -55px -372px } +.tty_80col .a2c143 { background-position: -55px -398px } +.tty_80col .a2c144 { background-position: -68px -8px } +.tty_80col .a2c145 { background-position: -68px -34px } +.tty_80col .a2c146 { background-position: -68px -60px } +.tty_80col .a2c147 { background-position: -68px -86px } +.tty_80col .a2c148 { background-position: -68px -112px } +.tty_80col .a2c149 { background-position: -68px -138px } +.tty_80col .a2c150 { background-position: -68px -164px } +.tty_80col .a2c151 { background-position: -68px -190px } +.tty_80col .a2c152 { background-position: -68px -216px } +.tty_80col .a2c153 { background-position: -68px -242px } +.tty_80col .a2c154 { background-position: -68px -268px } +.tty_80col .a2c155 { background-position: -68px -294px } +.tty_80col .a2c156 { background-position: -68px -320px } +.tty_80col .a2c157 { background-position: -68px -346px } +.tty_80col .a2c158 { background-position: -68px -372px } +.tty_80col .a2c159 { background-position: -68px -398px } +.tty_80col .a2c160 { background-position: -29px -8px } +.tty_80col .a2c161 { background-position: -29px -34px } +.tty_80col .a2c162 { background-position: -29px -60px } +.tty_80col .a2c163 { background-position: -29px -86px } +.tty_80col .a2c164 { background-position: -29px -112px } +.tty_80col .a2c165 { background-position: -29px -138px } +.tty_80col .a2c166 { background-position: -29px -164px } +.tty_80col .a2c167 { background-position: -29px -190px } +.tty_80col .a2c168 { background-position: -29px -216px } +.tty_80col .a2c169 { background-position: -29px -242px } +.tty_80col .a2c170 { background-position: -29px -268px } +.tty_80col .a2c171 { background-position: -29px -294px } +.tty_80col .a2c172 { background-position: -29px -320px } +.tty_80col .a2c173 { background-position: -29px -346px } +.tty_80col .a2c174 { background-position: -29px -372px } +.tty_80col .a2c175 { background-position: -29px -398px } +.tty_80col .a2c176 { background-position: -42px -8px } +.tty_80col .a2c177 { background-position: -42px -34px } +.tty_80col .a2c178 { background-position: -42px -60px } +.tty_80col .a2c179 { background-position: -42px -86px } +.tty_80col .a2c180 { background-position: -42px -112px } +.tty_80col .a2c181 { background-position: -42px -138px } +.tty_80col .a2c182 { background-position: -42px -164px } +.tty_80col .a2c183 { background-position: -42px -190px } +.tty_80col .a2c184 { background-position: -42px -216px } +.tty_80col .a2c185 { background-position: -42px -242px } +.tty_80col .a2c186 { background-position: -42px -268px } +.tty_80col .a2c187 { background-position: -42px -294px } +.tty_80col .a2c188 { background-position: -42px -320px } +.tty_80col .a2c189 { background-position: -42px -346px } +.tty_80col .a2c190 { background-position: -42px -372px } +.tty_80col .a2c191 { background-position: -42px -398px } +.tty_80col .a2c192 { background-position: -55px -8px } +.tty_80col .a2c193 { background-position: -55px -34px } +.tty_80col .a2c194 { background-position: -55px -60px } +.tty_80col .a2c195 { background-position: -55px -86px } +.tty_80col .a2c196 { background-position: -55px -112px } +.tty_80col .a2c197 { background-position: -55px -138px } +.tty_80col .a2c198 { background-position: -55px -164px } +.tty_80col .a2c199 { background-position: -55px -190px } +.tty_80col .a2c200 { background-position: -55px -216px } +.tty_80col .a2c201 { background-position: -55px -242px } +.tty_80col .a2c202 { background-position: -55px -268px } +.tty_80col .a2c203 { background-position: -55px -294px } +.tty_80col .a2c204 { background-position: -55px -320px } +.tty_80col .a2c205 { background-position: -55px -346px } +.tty_80col .a2c206 { background-position: -55px -372px } +.tty_80col .a2c207 { background-position: -55px -398px } +.tty_80col .a2c208 { background-position: -68px -8px } +.tty_80col .a2c209 { background-position: -68px -34px } +.tty_80col .a2c210 { background-position: -68px -60px } +.tty_80col .a2c211 { background-position: -68px -86px } +.tty_80col .a2c212 { background-position: -68px -112px } +.tty_80col .a2c213 { background-position: -68px -138px } +.tty_80col .a2c214 { background-position: -68px -164px } +.tty_80col .a2c215 { background-position: -68px -190px } +.tty_80col .a2c216 { background-position: -68px -216px } +.tty_80col .a2c217 { background-position: -68px -242px } +.tty_80col .a2c218 { background-position: -68px -268px } +.tty_80col .a2c219 { background-position: -68px -294px } +.tty_80col .a2c220 { background-position: -68px -320px } +.tty_80col .a2c221 { background-position: -68px -346px } +.tty_80col .a2c222 { background-position: -68px -372px } +.tty_80col .a2c223 { background-position: -68px -398px } +.tty_80col .a2c224 { background-position: -81px -8px } +.tty_80col .a2c225 { background-position: -81px -34px } +.tty_80col .a2c226 { background-position: -81px -60px } +.tty_80col .a2c227 { background-position: -81px -86px } +.tty_80col .a2c228 { background-position: -81px -112px } +.tty_80col .a2c229 { background-position: -81px -138px } +.tty_80col .a2c230 { background-position: -81px -164px } +.tty_80col .a2c231 { background-position: -81px -190px } +.tty_80col .a2c232 { background-position: -81px -216px } +.tty_80col .a2c233 { background-position: -81px -242px } +.tty_80col .a2c234 { background-position: -81px -268px } +.tty_80col .a2c235 { background-position: -81px -294px } +.tty_80col .a2c236 { background-position: -81px -320px } +.tty_80col .a2c237 { background-position: -81px -346px } +.tty_80col .a2c238 { background-position: -81px -372px } +.tty_80col .a2c239 { background-position: -81px -398px } +.tty_80col .a2c240 { background-position: -94px -8px } +.tty_80col .a2c241 { background-position: -94px -34px } +.tty_80col .a2c242 { background-position: -94px -60px } +.tty_80col .a2c243 { background-position: -94px -86px } +.tty_80col .a2c244 { background-position: -94px -112px } +.tty_80col .a2c245 { background-position: -94px -138px } +.tty_80col .a2c246 { background-position: -94px -164px } +.tty_80col .a2c247 { background-position: -94px -190px } +.tty_80col .a2c248 { background-position: -94px -216px } +.tty_80col .a2c249 { background-position: -94px -242px } +.tty_80col .a2c250 { background-position: -94px -268px } +.tty_80col .a2c251 { background-position: -94px -294px } +.tty_80col .a2c252 { background-position: -94px -320px } +.tty_80col .a2c253 { background-position: -94px -346px } +.tty_80col .a2c254 { background-position: -94px -372px } +.tty_80col .a2c255 { background-position: -94px -398px } + +.tty_80col .a2c256 { background-position: -211px -8px } +.tty_80col .a2c257 { background-position: -211px -34px } +.tty_80col .a2c258 { background-position: -211px -60px } +.tty_80col .a2c259 { background-position: -211px -86px } +.tty_80col .a2c260 { background-position: -211px -112px } +.tty_80col .a2c261 { background-position: -211px -138px } +.tty_80col .a2c262 { background-position: -211px -164px } +.tty_80col .a2c263 { background-position: -211px -190px } +.tty_80col .a2c264 { background-position: -211px -216px } +.tty_80col .a2c265 { background-position: -211px -242px } +.tty_80col .a2c266 { background-position: -211px -268px } +.tty_80col .a2c267 { background-position: -211px -294px } +.tty_80col .a2c268 { background-position: -211px -320px } +.tty_80col .a2c269 { background-position: -211px -346px } +.tty_80col .a2c270 { background-position: -211px -372px } +.tty_80col .a2c271 { background-position: -211px -398px } +.tty_80col .a2c272 { background-position: -224px -8px } +.tty_80col .a2c273 { background-position: -224px -34px } +.tty_80col .a2c274 { background-position: -224px -60px } +.tty_80col .a2c275 { background-position: -224px -86px } +.tty_80col .a2c276 { background-position: -224px -112px } +.tty_80col .a2c277 { background-position: -224px -138px } +.tty_80col .a2c278 { background-position: -224px -164px } +.tty_80col .a2c279 { background-position: -224px -190px } +.tty_80col .a2c280 { background-position: -224px -216px } +.tty_80col .a2c281 { background-position: -224px -242px } +.tty_80col .a2c282 { background-position: -224px -268px } +.tty_80col .a2c283 { background-position: -224px -294px } +.tty_80col .a2c284 { background-position: -224px -320px } +.tty_80col .a2c285 { background-position: -224px -346px } +.tty_80col .a2c286 { background-position: -224px -372px } +.tty_80col .a2c287 { background-position: -224px -398px } + +/* For non-canvas based hires implementations */ +.hiresPixel { width: 4px; height: 2px; background-color: black; } + +.loresPixel { width: 14px; height: 8px; margin: 0; padding: 0; } +.loresRow { height: 8px; margin: 0; padding: 0; } +.loresDisplay { table-layout: fixed; border-collapse: collapse; } + + + +/* Input Devices */ +.keyboard { width: 1px; height: 1px; border: none; position: absolute; left: -100px; top: -100px; } +td.controlbox { vertical-align: middle; padding-left: 12px; padding-right: 12px; } + +#paddle0, #paddle1 +{ + border-style: inset; + border-width: 2px; + border-color: gray; + background-color: #c0c0c0; + height: 20px; + width: 245px; + text-align: center; +} +#knob0, #knob1 +{ + border-style: outset; + border-width: 2px; + border-color: gray; + background-color: #404040; + height: 16px; + width: 10px; +} + +#joystick +{ + border-style: inset; + border-width: 2px; + border-color: gray; + background-color: #c0c0c0; + width: 100px; + height: 100px; + text-align: center; + vertical-align: middle; + line-height: 100px; +} +#stick +{ + border-style: outset; + border-width: 2px; + border-color: gray; + background-color: #404040; + height: 16px; + width: 16px; +} diff --git a/tty.js b/tty.js index 30bc7e0..d5d0501 100644 --- a/tty.js +++ b/tty.js @@ -1,904 +1,904 @@ -// -// Applesoft BASIC in Javascript -// TTY Emulation -// - -// Copyright (C) 2009-2011 Joshua Bell -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Usage: -// -// tty = new TTY( screen_element, keyboard_element, bell ); -// tty.clearScreen() -// tty.scrollScreen() -// tty.setTextStyle( textStyle ) -// { width: w, height: h } = tty.getScreenSize() -// { x: x, y: y } = tty.getCursorPosition() -// tty.setFirmwareActive( bool ) -// tty.setCursorPosition( x, y ) -// tty.showCursor() -// tty.hideCursor() -// tty.focus() -// -// This just calls writeChar() in a loop; no need to hook it -// tty.writeString( string ) -// -// The following can be hooked: -// tty.readLine( callback_function, prompt ) -// tty.readChar( callback_function ) -// tty.writeChar( c ) -// -// tty.TEXT_STYLE_NORMAL = 0 -// tty.TEXT_STYLE_INVERSE = 1 -// tty.TEXT_STYLE_FLASH = 2 -// -// Example: -// -// -// -//
- -/*global getClassList*/ // from polyfill.js -/*global identifyKey */ // From keyboard.js - -function TTY(screenElement, keyboardElement, bell) { - /*jslint browser: true, white: false, bitwise: false */ - - - // Constants - - - this.TEXT_STYLE_NORMAL = 0; - this.TEXT_STYLE_INVERSE = 1; - this.TEXT_STYLE_FLASH = 2; - - - // Internal Fields - - - // For references to "this" within callbacks and closures - var self = this, - - // Display - cursorX = 0, - cursorY = 0, - cursorVisible = false, - cursorElement = null, - styleElem, - screenGrid, - screenRow = [], - splitPos = 0, - screenWidth, - screenHeight, - curStyle = this.TEXT_STYLE_NORMAL, - cursorState = true, - cursorInterval, - firmwareActive = true, // 80-column firmware - mousetext = false, - - // Input - lineCallback, - charCallback, - inputBuffer = [], - keyboardRegister = 0, - keyDown = false, - capsLock = true, // Caps lock state is tracked unique to the TTY - buttonState = [0, 0, 0, 0]; - - this.autoScroll = true; - - - - // - // Display - // - - function setCellByte(x, y, byte) { - var cell = screenGrid[x + screenWidth * y]; - if (cell && cell.byte !== byte) { - cell.byte = byte; - cell.elem.className = 'a2c a2c' + String(byte); - } - } - - // Apple II Character Generator (byte->character map) - // 0x00-0x1F = INVERSE @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ - // 0x20-0x3F = INVERSE !"#$%&'()*+,-./0123456789:;<=>? - // 0x40-0x5F = FLASH @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ (80-col firmware active: mousetext symbols) - // 0x60-0x7F = FLASH !"#$%&'()*+,-./0123456789:;<=>? (80-col firmware active: inverse lowercase) - // 0x80-0x9F = NORMAL @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ - // 0xA0-0xBF = NORMAL !"#$%&'()*+,-./0123456789:;<=>? - // 0xC0-0xDF = NORMAL @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ - // 0xE0-0xFF = NORMAL `abcdefghijklmnopqrstuvwxyz{|}~ - - - function setCellChar(x, y, c) { - var byte; - - if (c > 0xff) { - // Extension characters - byte = c; - } else { - // Lock to 7-bit; Control characters should be filtered already - c = (c >>> 0) & 0x7f; - - if (firmwareActive) { - if (curStyle === self.TEXT_STYLE_INVERSE) { - if (0x20 <= c && c < 0x40) { byte = c; } - else if (0x40 <= c && c < 0x60) { byte = c - (mousetext ? 0 : 0x40); } - else if (0x60 <= c && c < 0x80) { byte = c; } - } else if (curStyle === self.TEXT_STYLE_FLASH) { - if (0x20 <= c && c < 0x40) { byte = c + 0x40; } - else if (0x40 <= c && c < 0x60) { byte = c - 0x40; } - else if (0x60 <= c && c < 0x80) { byte = c; } - } else { - if (0x20 <= c && c < 0x40) { byte = c + 0x80; } - else if (0x40 <= c && c < 0x60) { byte = c + 0x80; } - else if (0x60 <= c && c < 0x80) { byte = c + 0x80; } - } - } else { - if (curStyle === self.TEXT_STYLE_INVERSE) { - if (0x20 <= c && c < 0x40) { byte = c; } - else if (0x40 <= c && c < 0x60) { byte = c - 0x40; } - else if (0x60 <= c && c < 0x80) { byte = c - 0x40; } // no inverse lowercase - } else if (curStyle === self.TEXT_STYLE_FLASH) { - if (0x20 <= c && c < 0x40) { byte = c + 0x40; } - else if (0x40 <= c && c < 0x60) { byte = c; } - else if (0x60 <= c && c < 0x80) { byte = c; } // no lowercase flash - } else { - if (0x20 <= c && c < 0x40) { byte = c + 0x80; } - else if (0x40 <= c && c < 0x60) { byte = c + 0x80; } - else if (0x60 <= c && c < 0x80) { byte = c + 0x80; } - } - } - } - setCellByte(x, y, byte); - } - - this.reset = function _reset() { - this.hideCursor(); - lineCallback = undefined; - charCallback = undefined; - - inputBuffer = []; - keyboardRegister = 0; - buttonState = [0, 0, 0, 0]; - - }; // reset - - function init(active, rows, columns) { - firmwareActive = active; - screenWidth = columns; - screenHeight = rows; - - // Reset parameters - self.textWindow = {}; - self.textWindow.left = 0; - self.textWindow.top = 0; - self.textWindow.width = screenWidth; - self.textWindow.height = screenHeight; - self.setTextStyle(self.TEXT_STYLE_NORMAL); - - // Build character cell grid - var x, y, table, tbody, tr, td; - screenGrid = []; - screenGrid.length = screenWidth * screenHeight; - - table = document.createElement('table'); - table.className = 'tty_table'; - - tbody = document.createElement('tbody'); - styleElem = tbody; - - getClassList(styleElem).add(screenWidth === 40 ? 'tty_40col' : 'tty_80col'); - if (firmwareActive) { getClassList(styleElem).add('active'); } - - for (y = 0; y < screenHeight; y += 1) { - tr = document.createElement('tr'); - tr.className = 'tty_tr'; - tr.style.visibility = (y < splitPos) ? "hidden" : ""; - screenRow[y] = tr; - - for (x = 0; x < screenWidth; x += 1) { - td = document.createElement('td'); - screenGrid[screenWidth * y + x] = { - elem: td - }; - tr.appendChild(td); - } - - tbody.appendChild(tr); - } - table.appendChild(tbody); - screenElement.innerHTML = ""; - screenElement.appendChild(table); - - self.clearScreen(); - - // Create cursor - cursorElement = document.createElement('span'); - cursorElement.className = 'a2c a2c-cursor a2c255'; - self.setCursorPosition(0, 0); - } - - this.clearScreen = function _clearScreen() { - var x, y; - cursorX = self.textWindow.left; - cursorY = self.textWindow.top; - for (y = self.textWindow.top; y < self.textWindow.top + self.textWindow.height; y += 1) { - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, y, 0x20); - } - } - }; - - - this.clearEOL = function _clearEOL() { - var x; - for (x = cursorX; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, cursorY, 0x20); - } - }; - - - this.setFirmwareActive = function _setFirmwareActive(active) { - init(active, 24, active ? 80 : 40); - }; - - - function scrollUp() { - var x, y, cell; - - for (y = self.textWindow.top; y < self.textWindow.top + self.textWindow.height - 1; y += 1) { - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - - cell = screenGrid[x + screenWidth * (y + 1)]; - setCellByte(x, y, cell.byte); - } - } - - y = self.textWindow.top + (self.textWindow.height - 1); - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, y, 0x20); - } - } - - function scrollDown() { - var x, y, cell; - - for (y = self.textWindow.top + self.textWindow.height - 1; y > self.textWindow.top; y -= 1) { - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - - cell = screenGrid[x + screenWidth * (y - 1)]; - setCellByte(x, y, cell.byte); - } - } - - y = self.textWindow.top; - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, y, 0x20); - } - } - - this.scrollScreen = function _scrollScreen() { - scrollUp(); - }; - - this.setTextStyle = function _setTextStyle(style) { - curStyle = style; - }; - - // Internal - function updateCursor() { - if (cursorVisible && cursorState) { - var elem = screenGrid[cursorY * screenWidth + cursorX].elem; - if (elem !== cursorElement.parentNode) { - elem.appendChild(cursorElement); - } - } else if (cursorElement.parentNode) { - cursorElement.parentNode.removeChild(cursorElement); - } - } - - // Internal - function lineFeed() { - cursorY += 1; - if (cursorY >= self.textWindow.top + self.textWindow.height) { - cursorY = self.textWindow.top + self.textWindow.height - 1; - - if (self.autoScroll) { - self.scrollScreen(); - } - } - - updateCursor(); - } - - // Internal - function advanceCursor() { - // Advance the cursor - cursorX += 1; - - if (cursorX >= self.textWindow.left + self.textWindow.width) { - cursorX = self.textWindow.left; - lineFeed(); - } - - updateCursor(); - } - - // Hookable - this.writeChar = function _writeChar(c) { - var code = c.charCodeAt(0), - x, y; - - switch (code) { - case 0: - case 1: - case 2: - case 3: - case 4: // DOS hook takes care of CHR$(4) - case 5: - case 6: - // no-op - break; - - case 7: // (BEL) bell - if (bell) { - bell(); - } - break; - - case 8: // (BS) backspace - cursorX -= 1; - if (cursorX < self.textWindow.left) { - cursorX += self.textWindow.width; - cursorY -= 1; - if (cursorY < self.textWindow.top) { - cursorY = self.textWindow.top; - } - } - break; - - case 9: - break; - - case 10: // (LF) line feed - lineFeed(); - break; - - case 11: // (VT) clear EOS - if (firmwareActive) { - // Clears from the cursor position to the end of the window - for (x = cursorX; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, cursorY, 0x20); - } - for (y = cursorY + 1; y < self.textWindow.top + self.textWindow.height; y += 1) { - for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { - setCellChar(x, y, 0x20); - } - } - } - break; - - case 12: // (FF) clear - if (firmwareActive) { - // move cursor to upper left and clear window - self.clearScreen(); - } - break; - - case 13: // (CR) return - cursorX = self.textWindow.left; - lineFeed(); - break; - - case 14: // (SO) normal - if (firmwareActive) { - curStyle = self.TEXT_STYLE_NORMAL; - } - break; - - case 15: // (SI) inverse - if (firmwareActive) { - curStyle = self.TEXT_STYLE_INVERSE; - } - break; - - case 16: - break; - - case 17: // (DC1) 40-column - if (firmwareActive) { - // set display to 40 columns - init(true, 24, 40); - } - break; - - case 18: // (DC2) 80-column - if (firmwareActive) { - // set display to 80 columns - init(true, 24, 80); - } - break; - - case 19: // (DC3) stop list - case 20: - break; - - case 21: // (NAK) quit - if (firmwareActive) { - // deactivate, home, clear screen - init(false, 24, 40); - } - break; - - case 22: // (SYN) scroll down - if (firmwareActive) { - // scroll display down, leaving cursor - scrollDown(); - } - break; - - case 23: // (ETB) scroll up - if (firmwareActive) { - // scroll display up, leaving cursor - scrollUp(); - } - break; - - case 24: // (CAN) disable mousetext - if (firmwareActive) { - // http://www.umich.edu/~archive/apple2/technotes/tn/mous/TN.MOUS.006 - mousetext = false; - } - break; - - case 25: // (EM) home - if (firmwareActive) { - // Moves cursor to upper-left corner of window (but doesn't clear) - cursorX = self.textWindow.left; - cursorY = self.textWindow.top; - } - break; - - case 26: // (SUB) clear line - if (firmwareActive) { - // Clears the line the cursor position is on - for (x = 0; x < self.textWindow.width; x += 1) { - setCellChar(self.textWindow.left + x, cursorY, 0x20); - } - } - break; - - case 27: // (ESC) enable mousetext - if (firmwareActive) { - // http://www.umich.edu/~archive/apple2/technotes/tn/mous/TN.MOUS.006 - mousetext = true; - } - break; - - case 28: // (FS) fwd. space - if (firmwareActive) { - // Moves cursor position one space to the right; from right edge - // of window, moves it to left end of line below - cursorX += 1; - if (cursorX > (self.textWindow.left + self.textWindow.width)) { - cursorX -= self.textWindow.width; - cursorY += 1; - if (cursorY > self.textWindow.top + self.textWindow.height) { - cursorY = self.textWindow.top + self.textWindow.height; - } - } - } - break; - - case 29: // (GS) clear EOL - if (firmwareActive) { - // Clear line rom cursor position to the right edge of the window - self.clearEOL(); - } - break; - - case 30: // RS - gotoXY (not supported from BASIC) - case 31: - break; - - default: - setCellChar(cursorX, cursorY, code); - advanceCursor(); - break; - } - }; - - // Hookable - this.writeString = function _writeString(s) { - var i; - for (i = 0; i < s.length; i += 1) { - this.writeChar(s.charAt(i)); - } - }; - - this.getScreenSize = function _getScreenSize() { - return { width: screenWidth, height: screenHeight }; - }; - - this.getCursorPosition = function _getCursorPosition() { - return { x: cursorX, y: cursorY }; - }; - - this.setCursorPosition = function _setCursorPosition(x, y) { - if (x !== undefined) { - x = Math.min(Math.max(Math.floor(x), 0), screenWidth - 1); - } else { - x = cursorX; - } - - if (y !== undefined) { - y = Math.min(Math.max(Math.floor(y), 0), screenHeight - 1); - } else { - y = cursorY; - } - - if (x === cursorX && y === cursorY) { - // no-op - return; - } - - cursorX = x; - cursorY = y; - updateCursor(); - }; - - this.showCursor = function _showCursor() { - cursorVisible = true; - cursorInterval = setInterval(function() { - cursorState = !cursorState; - updateCursor(); - }, 500); - }; - - this.hideCursor = function _hideCursor() { - clearInterval(cursorInterval); - cursorVisible = false; - updateCursor(); - - }; - - this.splitScreen = function _splitScreen(splitAt) { - splitPos = splitAt; - - var y; - - for (y = 0; y < screenHeight; y += 1) { - screenRow[y].style.visibility = (y < splitPos) ? "hidden" : ""; - } - - }; - - - // - // Input - // - - - // Internal - function onKey(code) { - var cb, c, s; - - keyboardRegister = code | 0x80; - - if (charCallback) { - keyboardRegister = keyboardRegister & 0x7f; - - cb = charCallback; - charCallback = undefined; - self.hideCursor(); - cb(String.fromCharCode(code)); - } else if (lineCallback) { - keyboardRegister = keyboardRegister & 0x7f; - - if (code >= 32 && code <= 127) { - c = String.fromCharCode(code); - inputBuffer.push(c); - self.writeChar(c); // echo - } else { - switch (code) { - case 8: // Left Arrow - if (inputBuffer.length > 0) { - inputBuffer.pop(); - self.setCursorPosition(Math.max(self.getCursorPosition().x - 1, 0), self.getCursorPosition().y); - } - break; - - case 13: // Enter - // Respond to INPUT callback, if defined - s = inputBuffer.join(""); - inputBuffer = []; - self.writeString("\r"); - - cb = lineCallback; - lineCallback = undefined; - self.hideCursor(); - cb(s); - break; - } - } - } - // else: nothing - key stays in the keyboard register - - } // onKey - - function toAppleKey(e) { - - function ord(c) { return c.charCodeAt(0); } - - switch (e.keyName) { - - // Non-Printables - case 'Backspace': return 127; - case 'Tab': return 9; // NOTE: Blocked elsewhere, for web page accessibility - case 'Enter': return 13; - case 'Escape': return 27; - case 'Left': return 8; - case 'Up': return 11; - case 'Right': return 21; - case 'Down': return 10; - case 'Delete': return 127; - case 'Clear': return 24; // ctrl-X - used on the IIgs - - // Numeric - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (e.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) { - // Numpad, presumably - allow but nothing special - return ord(e.keyName); - } else if (e.ctrlKey) { - if (e.shiftKey) { - switch (e.keyName) { - case '2': return 0; // ctrl-@ - case '6': return 30; // ctrl-^ - } - } - return; - } else if (e.shiftKey) { - return ')!@#$%^&*('.charCodeAt(ord(e.keyName) - ord('0')); - } else { - return ord(e.keyName); - } - - // Alphabetic - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': - case 'H': - case 'I': - case 'J': - case 'K': - case 'L': - case 'M': - case 'N': - case 'O': - case 'P': - case 'Q': - case 'R': - case 'S': - case 'T': - case 'U': - case 'V': - case 'W': - case 'X': - case 'Y': - case 'Z': - if (e.ctrlKey) { - return ord(e.keyName) - 64; // Control keys, Apple II-style - } else if (capsLock || e.shiftKey) { - return ord(e.keyName); // Upper case - } else { - return ord(e.keyName) + 32; // Lower case - } - - // Symbol and Punctuation - case 'Spacebar': return ord(' '); - case 'Semicolon': return e.shiftKey ? ord(':') : ord(';'); - case 'Equals': return e.shiftKey ? ord('+') : ord('='); - case 'Comma': return e.shiftKey ? ord('<') : ord(','); - case 'Minus': return e.ctrlKey ? 31 : e.shiftKey ? ord('_') : ord('-'); - case 'Period': return e.shiftKey ? ord('>') : ord('.'); - case 'Solidus': return e.shiftKey ? ord('?') : ord('/'); - case 'Grave': return e.shiftKey ? ord('~') : ord('`'); - case 'LeftSquareBracket': return e.ctrlKey ? 27 : e.shiftKey ? ord('{') : ord('['); - case 'Backslash': return e.ctrlKey ? 28 : e.shiftKey ? ord('|') : ord('\\'); - case 'RightSquareBracket': return e.ctrlKey ? 29 : e.shiftKey ? ord('}') : ord(']'); - case 'Apostrophe': return e.shiftKey ? ord('"') : ord('\''); - - // not present on Apple II keyboard - default: - break; - } - - return -1; - } - - function isBrowserKey(e) { - return e.keyName === 'Tab' || e.keyName === 'F5' || e.metaKey; - } - - - // Internal - function handleKeyDown(e) { - identifyKey(e); - - if (!e.keyName || isBrowserKey(e)) { - return true; - } - - var handled = false, code; - - switch (e.keyName) { - - case 'CapsLock': - capsLock = !capsLock; - handled = true; - break; - - // Used as paddle buttons (0=Open Apple, 1=Solid Apple) - case 'Home': buttonState[0] = 255; handled = true; break; - case 'End': buttonState[1] = 255; handled = true; break; - case 'PageUp': buttonState[2] = 255; handled = true; break; - case 'Shift': buttonState[2] = 255; handled = true; break; - case 'PageDown': buttonState[3] = 255; handled = true; break; - - default: - code = toAppleKey(e); - if (code !== -1) { - keyDown = true; - onKey(code); - handled = true; - } - break; - } - - if (handled) { - e.stopPropagation(); - e.preventDefault(); - } - - return !handled; - } - - - // Internal - function handleKeyUp(e) { - identifyKey(e); - - if (!e.keyName || isBrowserKey(e)) { - return true; - } - - var handled = false, - code; - - switch (e.keyName) { - - case 'CapsLock': - handled = true; - break; - - // Used as paddle buttons (0=Open Apple, 1=Solid Apple) - case 'Home': buttonState[0] = 0; handled = true; break; - case 'End': buttonState[1] = 0; handled = true; break; - case 'PageUp': buttonState[2] = 0; handled = true; break; - case 'Shift': buttonState[2] = 0; handled = true; break; - case 'PageDown': buttonState[3] = 0; handled = true; break; - - default: - code = toAppleKey(e); - if (code !== -1) { - keyDown = false; - handled = true; - } - break; - } - - if (handled) { - e.stopPropagation(); - e.preventDefault(); - } - - return !handled; - } - - - this.getButtonState = function _getButtonState(btn) { - return buttonState[btn]; - }; - - - this.focus = function _focus() { - keyboardElement.focus(); - }; - - - // Hookable - this.readLine = function _readLine(callback, prompt) { - self.writeString(prompt); - - lineCallback = callback; - self.showCursor(); - self.focus(); - }; - - - // Hookable - this.readChar = function _readChar(callback) { - // If there is a key ready, deliver it immediately - if (keyboardRegister & 0x80) { - keyboardRegister = keyboardRegister & 0x7f; - - // Non-blocking return - setTimeout(function() { callback(String.fromCharCode(keyboardRegister)); }, 0); - } else { - charCallback = callback; - self.showCursor(); - self.focus(); - } - }; - - - this.getKeyboardRegister = function _getKeyboardRegister() { - return keyboardRegister; - }; - - - this.clearKeyboardStrobe = function _clearKeyboardStrobe() { - keyboardRegister = keyboardRegister & 0x7f; - return keyboardRegister | (keyDown ? 0x80 : 0x00); - }; - - - // - // Constructor Logic - // - - - init(false, 24, 40); - - window.addEvent(keyboardElement, 'keydown', handleKeyDown); - window.addEvent(keyboardElement, 'keyup', handleKeyUp); - - setInterval(function _blinkFlash() { - window.getClassList(styleElem).toggle('flash'); - }, 250); -} - - +// +// Applesoft BASIC in Javascript +// TTY Emulation +// + +// Copyright (C) 2009-2011 Joshua Bell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Usage: +// +// tty = new TTY( screen_element, keyboard_element, bell ); +// tty.clearScreen() +// tty.scrollScreen() +// tty.setTextStyle( textStyle ) +// { width: w, height: h } = tty.getScreenSize() +// { x: x, y: y } = tty.getCursorPosition() +// tty.setFirmwareActive( bool ) +// tty.setCursorPosition( x, y ) +// tty.showCursor() +// tty.hideCursor() +// tty.focus() +// +// This just calls writeChar() in a loop; no need to hook it +// tty.writeString( string ) +// +// The following can be hooked: +// tty.readLine( callback_function, prompt ) +// tty.readChar( callback_function ) +// tty.writeChar( c ) +// +// tty.TEXT_STYLE_NORMAL = 0 +// tty.TEXT_STYLE_INVERSE = 1 +// tty.TEXT_STYLE_FLASH = 2 +// +// Example: +// +// +// +//
+ +/*global getClassList*/ // from polyfill.js +/*global identifyKey */ // From keyboard.js + +function TTY(screenElement, keyboardElement, bell) { + /*jslint browser: true, white: false, bitwise: false */ + + + // Constants + + + this.TEXT_STYLE_NORMAL = 0; + this.TEXT_STYLE_INVERSE = 1; + this.TEXT_STYLE_FLASH = 2; + + + // Internal Fields + + + // For references to "this" within callbacks and closures + var self = this, + + // Display + cursorX = 0, + cursorY = 0, + cursorVisible = false, + cursorElement = null, + styleElem, + screenGrid, + screenRow = [], + splitPos = 0, + screenWidth, + screenHeight, + curStyle = this.TEXT_STYLE_NORMAL, + cursorState = true, + cursorInterval, + firmwareActive = true, // 80-column firmware + mousetext = false, + + // Input + lineCallback, + charCallback, + inputBuffer = [], + keyboardRegister = 0, + keyDown = false, + capsLock = true, // Caps lock state is tracked unique to the TTY + buttonState = [0, 0, 0, 0]; + + this.autoScroll = true; + + + + // + // Display + // + + function setCellByte(x, y, byte) { + var cell = screenGrid[x + screenWidth * y]; + if (cell && cell.byte !== byte) { + cell.byte = byte; + cell.elem.className = 'a2c a2c' + String(byte); + } + } + + // Apple II Character Generator (byte->character map) + // 0x00-0x1F = INVERSE @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + // 0x20-0x3F = INVERSE !"#$%&'()*+,-./0123456789:;<=>? + // 0x40-0x5F = FLASH @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ (80-col firmware active: mousetext symbols) + // 0x60-0x7F = FLASH !"#$%&'()*+,-./0123456789:;<=>? (80-col firmware active: inverse lowercase) + // 0x80-0x9F = NORMAL @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + // 0xA0-0xBF = NORMAL !"#$%&'()*+,-./0123456789:;<=>? + // 0xC0-0xDF = NORMAL @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ + // 0xE0-0xFF = NORMAL `abcdefghijklmnopqrstuvwxyz{|}~ + + + function setCellChar(x, y, c) { + var byte; + + if (c > 0xff) { + // Extension characters + byte = c; + } else { + // Lock to 7-bit; Control characters should be filtered already + c = (c >>> 0) & 0x7f; + + if (firmwareActive) { + if (curStyle === self.TEXT_STYLE_INVERSE) { + if (0x20 <= c && c < 0x40) { byte = c; } + else if (0x40 <= c && c < 0x60) { byte = c - (mousetext ? 0 : 0x40); } + else if (0x60 <= c && c < 0x80) { byte = c; } + } else if (curStyle === self.TEXT_STYLE_FLASH) { + if (0x20 <= c && c < 0x40) { byte = c + 0x40; } + else if (0x40 <= c && c < 0x60) { byte = c - 0x40; } + else if (0x60 <= c && c < 0x80) { byte = c; } + } else { + if (0x20 <= c && c < 0x40) { byte = c + 0x80; } + else if (0x40 <= c && c < 0x60) { byte = c + 0x80; } + else if (0x60 <= c && c < 0x80) { byte = c + 0x80; } + } + } else { + if (curStyle === self.TEXT_STYLE_INVERSE) { + if (0x20 <= c && c < 0x40) { byte = c; } + else if (0x40 <= c && c < 0x60) { byte = c - 0x40; } + else if (0x60 <= c && c < 0x80) { byte = c - 0x40; } // no inverse lowercase + } else if (curStyle === self.TEXT_STYLE_FLASH) { + if (0x20 <= c && c < 0x40) { byte = c + 0x40; } + else if (0x40 <= c && c < 0x60) { byte = c; } + else if (0x60 <= c && c < 0x80) { byte = c; } // no lowercase flash + } else { + if (0x20 <= c && c < 0x40) { byte = c + 0x80; } + else if (0x40 <= c && c < 0x60) { byte = c + 0x80; } + else if (0x60 <= c && c < 0x80) { byte = c + 0x80; } + } + } + } + setCellByte(x, y, byte); + } + + this.reset = function _reset() { + this.hideCursor(); + lineCallback = undefined; + charCallback = undefined; + + inputBuffer = []; + keyboardRegister = 0; + buttonState = [0, 0, 0, 0]; + + }; // reset + + function init(active, rows, columns) { + firmwareActive = active; + screenWidth = columns; + screenHeight = rows; + + // Reset parameters + self.textWindow = {}; + self.textWindow.left = 0; + self.textWindow.top = 0; + self.textWindow.width = screenWidth; + self.textWindow.height = screenHeight; + self.setTextStyle(self.TEXT_STYLE_NORMAL); + + // Build character cell grid + var x, y, table, tbody, tr, td; + screenGrid = []; + screenGrid.length = screenWidth * screenHeight; + + table = document.createElement('table'); + table.className = 'tty_table'; + + tbody = document.createElement('tbody'); + styleElem = tbody; + + getClassList(styleElem).add(screenWidth === 40 ? 'tty_40col' : 'tty_80col'); + if (firmwareActive) { getClassList(styleElem).add('active'); } + + for (y = 0; y < screenHeight; y += 1) { + tr = document.createElement('tr'); + tr.className = 'tty_tr'; + tr.style.visibility = (y < splitPos) ? "hidden" : ""; + screenRow[y] = tr; + + for (x = 0; x < screenWidth; x += 1) { + td = document.createElement('td'); + screenGrid[screenWidth * y + x] = { + elem: td + }; + tr.appendChild(td); + } + + tbody.appendChild(tr); + } + table.appendChild(tbody); + screenElement.innerHTML = ""; + screenElement.appendChild(table); + + self.clearScreen(); + + // Create cursor + cursorElement = document.createElement('span'); + cursorElement.className = 'a2c a2c-cursor a2c255'; + self.setCursorPosition(0, 0); + } + + this.clearScreen = function _clearScreen() { + var x, y; + cursorX = self.textWindow.left; + cursorY = self.textWindow.top; + for (y = self.textWindow.top; y < self.textWindow.top + self.textWindow.height; y += 1) { + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, y, 0x20); + } + } + }; + + + this.clearEOL = function _clearEOL() { + var x; + for (x = cursorX; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, cursorY, 0x20); + } + }; + + + this.setFirmwareActive = function _setFirmwareActive(active) { + init(active, 24, active ? 80 : 40); + }; + + + function scrollUp() { + var x, y, cell; + + for (y = self.textWindow.top; y < self.textWindow.top + self.textWindow.height - 1; y += 1) { + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + + cell = screenGrid[x + screenWidth * (y + 1)]; + setCellByte(x, y, cell.byte); + } + } + + y = self.textWindow.top + (self.textWindow.height - 1); + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, y, 0x20); + } + } + + function scrollDown() { + var x, y, cell; + + for (y = self.textWindow.top + self.textWindow.height - 1; y > self.textWindow.top; y -= 1) { + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + + cell = screenGrid[x + screenWidth * (y - 1)]; + setCellByte(x, y, cell.byte); + } + } + + y = self.textWindow.top; + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, y, 0x20); + } + } + + this.scrollScreen = function _scrollScreen() { + scrollUp(); + }; + + this.setTextStyle = function _setTextStyle(style) { + curStyle = style; + }; + + // Internal + function updateCursor() { + if (cursorVisible && cursorState) { + var elem = screenGrid[cursorY * screenWidth + cursorX].elem; + if (elem !== cursorElement.parentNode) { + elem.appendChild(cursorElement); + } + } else if (cursorElement.parentNode) { + cursorElement.parentNode.removeChild(cursorElement); + } + } + + // Internal + function lineFeed() { + cursorY += 1; + if (cursorY >= self.textWindow.top + self.textWindow.height) { + cursorY = self.textWindow.top + self.textWindow.height - 1; + + if (self.autoScroll) { + self.scrollScreen(); + } + } + + updateCursor(); + } + + // Internal + function advanceCursor() { + // Advance the cursor + cursorX += 1; + + if (cursorX >= self.textWindow.left + self.textWindow.width) { + cursorX = self.textWindow.left; + lineFeed(); + } + + updateCursor(); + } + + // Hookable + this.writeChar = function _writeChar(c) { + var code = c.charCodeAt(0), + x, y; + + switch (code) { + case 0: + case 1: + case 2: + case 3: + case 4: // DOS hook takes care of CHR$(4) + case 5: + case 6: + // no-op + break; + + case 7: // (BEL) bell + if (bell) { + bell(); + } + break; + + case 8: // (BS) backspace + cursorX -= 1; + if (cursorX < self.textWindow.left) { + cursorX += self.textWindow.width; + cursorY -= 1; + if (cursorY < self.textWindow.top) { + cursorY = self.textWindow.top; + } + } + break; + + case 9: + break; + + case 10: // (LF) line feed + lineFeed(); + break; + + case 11: // (VT) clear EOS + if (firmwareActive) { + // Clears from the cursor position to the end of the window + for (x = cursorX; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, cursorY, 0x20); + } + for (y = cursorY + 1; y < self.textWindow.top + self.textWindow.height; y += 1) { + for (x = self.textWindow.left; x < self.textWindow.left + self.textWindow.width; x += 1) { + setCellChar(x, y, 0x20); + } + } + } + break; + + case 12: // (FF) clear + if (firmwareActive) { + // move cursor to upper left and clear window + self.clearScreen(); + } + break; + + case 13: // (CR) return + cursorX = self.textWindow.left; + lineFeed(); + break; + + case 14: // (SO) normal + if (firmwareActive) { + curStyle = self.TEXT_STYLE_NORMAL; + } + break; + + case 15: // (SI) inverse + if (firmwareActive) { + curStyle = self.TEXT_STYLE_INVERSE; + } + break; + + case 16: + break; + + case 17: // (DC1) 40-column + if (firmwareActive) { + // set display to 40 columns + init(true, 24, 40); + } + break; + + case 18: // (DC2) 80-column + if (firmwareActive) { + // set display to 80 columns + init(true, 24, 80); + } + break; + + case 19: // (DC3) stop list + case 20: + break; + + case 21: // (NAK) quit + if (firmwareActive) { + // deactivate, home, clear screen + init(false, 24, 40); + } + break; + + case 22: // (SYN) scroll down + if (firmwareActive) { + // scroll display down, leaving cursor + scrollDown(); + } + break; + + case 23: // (ETB) scroll up + if (firmwareActive) { + // scroll display up, leaving cursor + scrollUp(); + } + break; + + case 24: // (CAN) disable mousetext + if (firmwareActive) { + // http://www.umich.edu/~archive/apple2/technotes/tn/mous/TN.MOUS.006 + mousetext = false; + } + break; + + case 25: // (EM) home + if (firmwareActive) { + // Moves cursor to upper-left corner of window (but doesn't clear) + cursorX = self.textWindow.left; + cursorY = self.textWindow.top; + } + break; + + case 26: // (SUB) clear line + if (firmwareActive) { + // Clears the line the cursor position is on + for (x = 0; x < self.textWindow.width; x += 1) { + setCellChar(self.textWindow.left + x, cursorY, 0x20); + } + } + break; + + case 27: // (ESC) enable mousetext + if (firmwareActive) { + // http://www.umich.edu/~archive/apple2/technotes/tn/mous/TN.MOUS.006 + mousetext = true; + } + break; + + case 28: // (FS) fwd. space + if (firmwareActive) { + // Moves cursor position one space to the right; from right edge + // of window, moves it to left end of line below + cursorX += 1; + if (cursorX > (self.textWindow.left + self.textWindow.width)) { + cursorX -= self.textWindow.width; + cursorY += 1; + if (cursorY > self.textWindow.top + self.textWindow.height) { + cursorY = self.textWindow.top + self.textWindow.height; + } + } + } + break; + + case 29: // (GS) clear EOL + if (firmwareActive) { + // Clear line rom cursor position to the right edge of the window + self.clearEOL(); + } + break; + + case 30: // RS - gotoXY (not supported from BASIC) + case 31: + break; + + default: + setCellChar(cursorX, cursorY, code); + advanceCursor(); + break; + } + }; + + // Hookable + this.writeString = function _writeString(s) { + var i; + for (i = 0; i < s.length; i += 1) { + this.writeChar(s.charAt(i)); + } + }; + + this.getScreenSize = function _getScreenSize() { + return { width: screenWidth, height: screenHeight }; + }; + + this.getCursorPosition = function _getCursorPosition() { + return { x: cursorX, y: cursorY }; + }; + + this.setCursorPosition = function _setCursorPosition(x, y) { + if (x !== undefined) { + x = Math.min(Math.max(Math.floor(x), 0), screenWidth - 1); + } else { + x = cursorX; + } + + if (y !== undefined) { + y = Math.min(Math.max(Math.floor(y), 0), screenHeight - 1); + } else { + y = cursorY; + } + + if (x === cursorX && y === cursorY) { + // no-op + return; + } + + cursorX = x; + cursorY = y; + updateCursor(); + }; + + this.showCursor = function _showCursor() { + cursorVisible = true; + cursorInterval = setInterval(function() { + cursorState = !cursorState; + updateCursor(); + }, 500); + }; + + this.hideCursor = function _hideCursor() { + clearInterval(cursorInterval); + cursorVisible = false; + updateCursor(); + + }; + + this.splitScreen = function _splitScreen(splitAt) { + splitPos = splitAt; + + var y; + + for (y = 0; y < screenHeight; y += 1) { + screenRow[y].style.visibility = (y < splitPos) ? "hidden" : ""; + } + + }; + + + // + // Input + // + + + // Internal + function onKey(code) { + var cb, c, s; + + keyboardRegister = code | 0x80; + + if (charCallback) { + keyboardRegister = keyboardRegister & 0x7f; + + cb = charCallback; + charCallback = undefined; + self.hideCursor(); + cb(String.fromCharCode(code)); + } else if (lineCallback) { + keyboardRegister = keyboardRegister & 0x7f; + + if (code >= 32 && code <= 127) { + c = String.fromCharCode(code); + inputBuffer.push(c); + self.writeChar(c); // echo + } else { + switch (code) { + case 8: // Left Arrow + if (inputBuffer.length > 0) { + inputBuffer.pop(); + self.setCursorPosition(Math.max(self.getCursorPosition().x - 1, 0), self.getCursorPosition().y); + } + break; + + case 13: // Enter + // Respond to INPUT callback, if defined + s = inputBuffer.join(""); + inputBuffer = []; + self.writeString("\r"); + + cb = lineCallback; + lineCallback = undefined; + self.hideCursor(); + cb(s); + break; + } + } + } + // else: nothing - key stays in the keyboard register + + } // onKey + + function toAppleKey(e) { + + function ord(c) { return c.charCodeAt(0); } + + switch (e.keyName) { + + // Non-Printables + case 'Backspace': return 127; + case 'Tab': return 9; // NOTE: Blocked elsewhere, for web page accessibility + case 'Enter': return 13; + case 'Escape': return 27; + case 'Left': return 8; + case 'Up': return 11; + case 'Right': return 21; + case 'Down': return 10; + case 'Delete': return 127; + case 'Clear': return 24; // ctrl-X - used on the IIgs + + // Numeric + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (e.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD) { + // Numpad, presumably - allow but nothing special + return ord(e.keyName); + } else if (e.ctrlKey) { + if (e.shiftKey) { + switch (e.keyName) { + case '2': return 0; // ctrl-@ + case '6': return 30; // ctrl-^ + } + } + return; + } else if (e.shiftKey) { + return ')!@#$%^&*('.charCodeAt(ord(e.keyName) - ord('0')); + } else { + return ord(e.keyName); + } + + // Alphabetic + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + if (e.ctrlKey) { + return ord(e.keyName) - 64; // Control keys, Apple II-style + } else if (capsLock || e.shiftKey) { + return ord(e.keyName); // Upper case + } else { + return ord(e.keyName) + 32; // Lower case + } + + // Symbol and Punctuation + case 'Spacebar': return ord(' '); + case 'Semicolon': return e.shiftKey ? ord(':') : ord(';'); + case 'Equals': return e.shiftKey ? ord('+') : ord('='); + case 'Comma': return e.shiftKey ? ord('<') : ord(','); + case 'Minus': return e.ctrlKey ? 31 : e.shiftKey ? ord('_') : ord('-'); + case 'Period': return e.shiftKey ? ord('>') : ord('.'); + case 'Solidus': return e.shiftKey ? ord('?') : ord('/'); + case 'Grave': return e.shiftKey ? ord('~') : ord('`'); + case 'LeftSquareBracket': return e.ctrlKey ? 27 : e.shiftKey ? ord('{') : ord('['); + case 'Backslash': return e.ctrlKey ? 28 : e.shiftKey ? ord('|') : ord('\\'); + case 'RightSquareBracket': return e.ctrlKey ? 29 : e.shiftKey ? ord('}') : ord(']'); + case 'Apostrophe': return e.shiftKey ? ord('"') : ord('\''); + + // not present on Apple II keyboard + default: + break; + } + + return -1; + } + + function isBrowserKey(e) { + return e.keyName === 'Tab' || e.keyName === 'F5' || e.metaKey; + } + + + // Internal + function handleKeyDown(e) { + identifyKey(e); + + if (!e.keyName || isBrowserKey(e)) { + return true; + } + + var handled = false, code; + + switch (e.keyName) { + + case 'CapsLock': + capsLock = !capsLock; + handled = true; + break; + + // Used as paddle buttons (0=Open Apple, 1=Solid Apple) + case 'Home': buttonState[0] = 255; handled = true; break; + case 'End': buttonState[1] = 255; handled = true; break; + case 'PageUp': buttonState[2] = 255; handled = true; break; + case 'Shift': buttonState[2] = 255; handled = true; break; + case 'PageDown': buttonState[3] = 255; handled = true; break; + + default: + code = toAppleKey(e); + if (code !== -1) { + keyDown = true; + onKey(code); + handled = true; + } + break; + } + + if (handled) { + e.stopPropagation(); + e.preventDefault(); + } + + return !handled; + } + + + // Internal + function handleKeyUp(e) { + identifyKey(e); + + if (!e.keyName || isBrowserKey(e)) { + return true; + } + + var handled = false, + code; + + switch (e.keyName) { + + case 'CapsLock': + handled = true; + break; + + // Used as paddle buttons (0=Open Apple, 1=Solid Apple) + case 'Home': buttonState[0] = 0; handled = true; break; + case 'End': buttonState[1] = 0; handled = true; break; + case 'PageUp': buttonState[2] = 0; handled = true; break; + case 'Shift': buttonState[2] = 0; handled = true; break; + case 'PageDown': buttonState[3] = 0; handled = true; break; + + default: + code = toAppleKey(e); + if (code !== -1) { + keyDown = false; + handled = true; + } + break; + } + + if (handled) { + e.stopPropagation(); + e.preventDefault(); + } + + return !handled; + } + + + this.getButtonState = function _getButtonState(btn) { + return buttonState[btn]; + }; + + + this.focus = function _focus() { + keyboardElement.focus(); + }; + + + // Hookable + this.readLine = function _readLine(callback, prompt) { + self.writeString(prompt); + + lineCallback = callback; + self.showCursor(); + self.focus(); + }; + + + // Hookable + this.readChar = function _readChar(callback) { + // If there is a key ready, deliver it immediately + if (keyboardRegister & 0x80) { + keyboardRegister = keyboardRegister & 0x7f; + + // Non-blocking return + setTimeout(function() { callback(String.fromCharCode(keyboardRegister)); }, 0); + } else { + charCallback = callback; + self.showCursor(); + self.focus(); + } + }; + + + this.getKeyboardRegister = function _getKeyboardRegister() { + return keyboardRegister; + }; + + + this.clearKeyboardStrobe = function _clearKeyboardStrobe() { + keyboardRegister = keyboardRegister & 0x7f; + return keyboardRegister | (keyDown ? 0x80 : 0x00); + }; + + + // + // Constructor Logic + // + + + init(false, 24, 40); + + window.addEvent(keyboardElement, 'keydown', handleKeyDown); + window.addEvent(keyboardElement, 'keyup', handleKeyUp); + + setInterval(function _blinkFlash() { + window.getClassList(styleElem).toggle('flash'); + }, 250); +} + +