Monday, April 21, 2025

A Little expriment. If this works well enough I might be able to get some fun little applets on the blog. New.BAS 15 (prod 2025-04-21 19:03 utc)

EDIT.... disconnected the script.... it wasn't behaving in some folks browsers as I would have liked.

<\p> // Copyright 2022 CJ Veniot // Derivative of wwwbasic.js (https://github.com/google/wwwbasic/blob/master/wwwbasic.js) // subject to the same license as the source last committed on Github Dec 27, 2018 // // Copyright 2018 Google LLC // // 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 // // https://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. 'use strict'; var sub_check = 0; var fn_check = 0; var draw_angle = 0; var keyState = {}; var keyPressed = 0; const gmap = new Map(); var page0; var page1; var page2; var constants_list=''; var NoEekOnSkip=0; var SleepUntilKey=0; var openwindow; var autodisplay=1; var display_now=0; var loop_count= 0; var spool_name=''; var spool_text=''; var hiddenElement = document.createElement('a'); //var acontext = new (window.AudioContext || window.webkitAudioContext)(); var globalAudioContext; var sndwave = "sine"; var sndfade = "exponential"; var audio_queue = []; var audio_queue_timer = 0; // https://stackoverflow.com/questions/69516768/web-audio-api-sound-interaction-stops-working-after-a-while class Audio { constructor() { // instantiate web audio api object // create gain node, gain corresponds with volume this.gainNode = globalAudioContext.createGain(); //this.gainNode.gain.setValueAtTime(0.8, 0); // allows volume to decrease with time // this.gainNode.gain.exponentialRampToValueAtTime(0.001, globalAudioContext.currentTime ); } createNotes(f,d) { let oscillator = globalAudioContext.createOscillator(); oscillator.type=sndwave; this.gainNode.gain.setValueAtTime(64/(Math.log(f)**2), globalAudioContext.currentTime); oscillator.frequency.setValueAtTime(f, globalAudioContext.currentTime ); if (sndfade=='no') {this.gainNode.gain.setValueAtTime(0.001, globalAudioContext.currentTime + d/18.2 );} else if (sndfade=='linear') {this.gainNode.gain.linearRampToValueAtTime(0.001, globalAudioContext.currentTime + d/18.2 );} else {this.gainNode.gain.exponentialRampToValueAtTime(0.001, globalAudioContext.currentTime + d/18.2 );} oscillator.connect(this.gainNode); this.gainNode.connect(globalAudioContext.destination); oscillator.start(0); oscillator.stop(globalAudioContext.currentTime + d/18.2 ); } } var g; var o; //var o; var curr_mode; var curr_width; var curr_height; var readme_modes = [ [0, 640, 200, 16], [1, 320, 200, 4], [2, 640, 200, 2], [3, 640, 200, 16], [4, 0, 0, 0], [5, 0, 0, 0], [6, 0, 0, 0], [7, 320, 200, 16], [8, 640, 200, 16], [9, 640, 350, 16], [10, 640, 350, 16], [11, 640, 480, 2], [12, 640, 480, 16], [13, 320, 200, 256], [14, 320, 200, 256], [15, 320, 200, 256], [16, 320, 200, 256], [17, 320, 200, 256], [23, 320, 200, 0], [24, 320, 200, 0], [25, 320, 200, 0], [26, 320, 200, 0], [27, 320, 200, 0] ]; (function() { var DYNAMIC_HEAP_SIZE = 1024 * 1024 * 16; var STACK_SIZE = 64 * 1024; var MAX_DIMENSIONS = 7; var BLACK = 0xff000000; var WHITE = 0xffffffff; var SIMPLE_TYPE_INFO = { 'byte': {array: 'Int8Array', size: 1, shift: 0, view: 'b'}, 'ubyte': {array: 'Uint8Array', size: 1, shift: 0, view: 'ub'}, 'short': {array: 'Int16Array', size: 2, shift: 1, view: 'i1'}, 'ushort': {array: 'Uint16Array', size: 2, shift: 1, view: 'iu1'}, 'long': {array: 'Int32Array', size: 4, shift: 2, view: 'i'}, 'ulong': {array: 'Uint32Array', size: 4, shift: 2, view: 'iu'}, 'single': {array: 'Float64Array', size: 8, shift: 3, view: 's'}, 'double': {array: 'Float64Array', size: 8, shift: 3, view: 'd'}, 'string': {array: 'Array', size: 1, shift: 0, view: 'str'}, }; var IMPLICIT_TYPE_MAP = { '$': 'string', '%%': 'byte', '~%%': 'ubyte', '%': 'short', '~%': 'ushort', '&': 'long', '~&': 'ulong', '!': 'single', '#': 'double', }; function NextChar(ch) { return String.fromCharCode(ch.charCodeAt(0) + 1); } function RenderFont(ctx, height) { var data = new Uint8Array(256 * 8 * height); var pos = 0; for (var i = 0; i < 256; ++i) { ctx.fillStyle = '#000'; ctx.fillRect(0, 0, 16, 32); ctx.textBaseline = 'top'; ctx.font = 'bold 16px monospace'; ctx.save(); ctx.scale(1, height / 16); ctx.fillStyle = '#fff'; ctx.fillText(CHARSET.charAt(i), 0, 0); ctx.restore(); var pix = ctx.getImageData(0, 0, 8, height); var pdata = pix.data; for (var j = 0; j < pdata.length; j += 4) { var level = pdata[j] * 0.1140 + pdata[j + 1] * 0.5870 + pdata[j + 2] * 0.2989; data[pos++] = level > 128 ? 255 : 0; } } return data; } function LoadFont(s, dup) { var data = new Uint8Array(s.length * dup); var pos = 0; for (var i = 0; i < 256; ++i) { var row = Math.floor(i / 8); var col = i % 8; for (var y = 0; y < 8; ++y) { for (var d = 0; d < dup; ++d) { for (var x = 0; x < 8; ++x) { data[pos++] = s[x + y * 8 * 8 + col * 8 + row * 64 * 8] != ' ' ? 255 : 0; } } } } return data; } function CreateFont(ctx, height) { if (height == 8) { return LoadFont(FONT8, 1); } else if (height == 16) { return LoadFont(FONT8, 2); } else { return RenderFont(ctx, height); } } function Interpret(code, canvas, from_tag) { // Display Info (in browser only). var screen_mode = 0; var screen_bpp = 4; var text_width = 80; var text_height = 60; var font_height = 16; var screen_aspect = 1; var font_data; var ctx; var display; var display_data; var scale_canvas; function SetupDisplay(width, height, aspect, fheight) { if (!canvas) { return; } ctx = canvas.getContext('2d', { alpha: false }); display = ctx.createImageData(width, height); display_data = new Uint32Array(display.data.buffer); if (!scale_canvas) { scale_canvas = document.createElement('canvas'); } scale_canvas.width = width; scale_canvas.height = height; text_width = Math.floor(width / 8); text_height = Math.floor(height / fheight); screen_aspect = aspect; font_height = fheight; var sctx = scale_canvas.getContext('2d', { alpha: false}); font_data = CreateFont(sctx, font_height); } Screen(0); var debugging_mode = typeof debug == 'boolean' && debug; // Parsing and Run State. var labels = {}; var data_labels = {}; var flow = []; var types = {}; var functions = {}; var global_vars = {}; var vars = global_vars; var allocated = 0; var const_count = 0; var temp_count = 0; var inside_type = false; var inside_function = false; var var_decls = ''; var data = []; var data_pos = 0; var ops = []; var curop = ''; var ip = 0; var function_define_pos = 0; var function_old_allocated = 0; var function_name = null; // Call stack var stack = 0; var sp = 0; var bp = 0; // Input State var keys = []; var input_string = ''; var mouse_x = 0; var mouse_y = 0; var mouse_buttons = 0; var mouse_wheel = 0; var mouse_clip = 0; // Language Options var option_base = 0; var option_explicit = false; // Variable declaration defaults. var letter_default = {}; // Default is single. var i = 'a'; do { letter_default[i] = 'single'; i = NextChar(i); } while (i != 'z'); // Yield State var yielding = 0; var quitting = 0; var delay = 0; // Drawing and Console State var color_map; var reverse_color_map; var fg_id; var bg_id; var fg_color = WHITE; var bg_color = BLACK; // cjv: canvas background var text_x = 0; var text_y = 0; var pen_x = 0; var pen_y = 0; const toklist = [ ':', ';', ',', '(', ')', '{', '}', '[', ']', '+=', '-=', '*=', '/=', '\\=', '^=', '&=', '+', '-', '*', '/', '\\', '^', '&', '.', '<=', '>=', '<>', '=>', '=', '<', '>', '@', '\n', ]; code = code.replace(/\r/g, ' '); code = code.replace(/\t/g, ' '); if (from_tag) { code = code.replace(/</g, '<'); code = code.replace(/>/g, '>'); code = code.replace(/&/g, '&'); } var tok = null; var tok_count = 0; var line = canvas ? 0 : 1; function Next() { tok = ''; tok_count++; for (;;) { while (code.substr(0, 1) == ' ' || code.substr(0, 1) == '\t') { if (tok != '') { return; } code = code.substr(1); } if (code.search(/^_[ \t]*('[^\n]*)?\n/) != -1) { if (tok != '') { return; } code = code.substr(code.search('\n') + 1); ++line; // cjv continue; } if (code.substr(0, 1) == '\'') { if (tok != '') { return; } while (code.length > 0 && code.substr(0, 1) != '\n') { code = code.substr(1); } continue; } if (code.substr(0, 1) == '"') { if (tok != '') { return; } tok = '"'; code = code.substr(1); while (code.length > 0 && code.substr(0, 1) != '"') { if (code.substr(0, 1) == '\n') { // Allow strings to cut off at end of line. // GW-Basic seems to allow it. tok += '"'; return; } tok += code.substr(0, 1); code = code.substr(1); } tok += '"'; code = code.substr(1); return; } if (tok == '' && /[.0-9][#]?/.test(code.substr(0, 1))) { var n = code.match(/^([0-9]*([.][0-9]*)?([eE][+-]?[0-9]+)?[#]?)/); if (n === null) { Throw('Bad number'); } tok = n[1]; code = code.substr(tok.length); if (tok[tok.length - 1] == '#') { tok = tok.substr(0, tok.length - 1); } return; } for (var i = 0; i < toklist.length; ++i) { if (code.substr(0, toklist[i].length) == toklist[i]) { if (tok != '') { if (code.substr(0, 1) == '&' && code.substr(code.length - 1) != '$') { tok += '&'; code = code.substr(1); } return; } tok = toklist[i]; code = code.substr(toklist[i].length); if (tok == '\n') { ++line; tok = ''; } else if (tok == '&' && code.substr(0, 1).toLowerCase() == 'h') { code = code.substr(1); var n = code.match(/^([0-9a-fA-F]+)/); if (n === null) { Throw('Bad hex number'); } tok = '0x' + n[1]; code = code.substr(n[1].length); } else if (tok == '&' && code.substr(0, 1).toLowerCase() == 'o') { code = code.substr(1); var n = code.match(/^([0-7]+)/); if (n === null) { Throw('Bad octal number'); } tok = (+('0o' + n[1])).toString(); code = code.substr(n[1].length); } else if (tok == '&' && code.substr(0, 1).toLowerCase() == 'b') { code = code.substr(1); var n = code.match(/^([0-1]+)/); if (n === null) { Throw('Bad binary number'); } tok = '0b' + n[1]; code = code.substr(n[1].length); } return; } } tok += code.substr(0, 1).toLowerCase(); code = code.substr(1); if (code == '') { return; } } } Next(); function ConsumeData() { var quote = false; var had_quote = false; var item = ''; for (;;) { var ch = code.substr(0, 1); if (ch == '\n' || ch == '') { if (!had_quote) { if (!quote) { item = item.trim(); } data.push(item); } break; } else if (quote==false && item.trim()=='' && ch=='&') {item += '0';} else if (quote==false && item.trim()=='0' && /^([hH])$/.test(ch) ) {item += 'x';} else if (ch == '"') { if (quote) { data.push(item); item = ''; quote = false; had_quote = true; } else { quote = true; if (item.search(/[^ \t]/) != -1) { Throw('Data statement extra text: "' + item + '"'); } item = ''; } } else if (ch == ',') { if (!quote) { if (!had_quote) { data.push(item.trim()); } else { had_quote = false; if (item.search(/[^ \t]/) != -1) { Throw('Data statement extra text: "' + item + '"'); } } item = ''; } else { item += ','; } } else { item += ch; } code = code.substr(1); } Next(); } function Throw(msg,dotl=true) { if (dotl==true) {var tl=line-5;} else {var tl = 'unknown'}; if (tok==""||tok==""||msg.includes("OUT OF DATA")) {tl-=1;} alert('🛑 ERROR: line '+tl+': '+msg); throw 'line '+tl+': '+msg; } function Skip(t,eek) { if (tok!=t && NoEekOnSkip==0) { Throw(eek+': Expected "'+t+'" found "'+tok+'"'); } Next(); } function EndOfStatement() { return tok==':'||tok==''; } function SkipEndOfStatement() { if (!EndOfStatement()) { Throw('Expected ":" (between statements) or "," (between parameters) or EOL'); } Next(); } function IsKeyword(a) { var d = ''; if (inside_type == true) {d='(TYPE definition error)'} if (':::keywords:::'.includes('.'+a.toLowerCase()+'.')){Throw(a+' is a reserved keyword '+d);} } function NewOp() { ops.push(curop); curop = ''; } function If(e, n) { if (n === undefined) { n = []; } NewOp(); ops[ops.length - 1] += 'if (!(' + e + ')) { ip = '; flow.push(['if', ops.length - 1, []]); } function Else() { var f = flow.pop(); if (f[0] != 'if') { Throw('ELSE unmatched to IF'); } NewOp(); var pos = ops.length - 1; ops[pos] += 'ip = '; NewOp(); ops[f[1]] += ops.length + '; }\n'; flow.push(['else', null, f[2].concat(pos)]); } function ElseIf(e) { var f = flow.pop(); if (f[0] != 'if') { Throw('ELSEIF unmatched to IF'); } NewOp(); var pos = ops.length - 1; ops[pos] += 'ip = '; NewOp(); ops[f[1]] += ops.length + '; }\n'; NewOp(); ops[ops.length - 1] += 'if (!(' + e + ')) { ip = '; flow.push(['if', ops.length - 1, f[2].concat([pos])]); } function EndIf() { NewOp(); var f = flow.pop(); if (f[0] == 'else') { // nothing needed } else if (f[0] == 'if') { ops[f[1]] += ops.length + '; }\n'; } else { Throw('Unmatch end if'); } for (var i = 0; i < f[2].length; ++i) { ops[f[2][i]] += ops.length + ';\n'; } } function VarPtr(vname) { var vinfo; if (vars[vname] !== undefined) {vinfo = vars[vname];} else if (global_vars[vname] !== undefined) {vinfo = global_vars[vname];} if (vinfo === undefined) {Throw('Undefined variable name');} if (vinfo.global) {return vinfo.offset;} else {return '(bp + ' + vinfo.offset + ')';} } function AddLabel(name) { var n2=name.replace(/^0+/, ""); if (labels[n2]!==undefined) {Throw('Label '+n2+' defined twice');} NewOp(); curop+='// LABEL '+n2+':\n'; labels[n2]=ops.length; data_labels[n2]=data.length; } function Factor3() { var ts=''; var te=''; if (tok=='('||tok=='['||tok=='{') { if (tok=='[') {ts='[';te=']';} else if (tok=='{') {ts='{';te='}';} else {ts='(';te=')';} Skip(ts); var ret = Expression(); Skip(te); return ret; } else { var name = tok; Next(); if (name.substr(0, 1) == '"' || /^[0-9]*([.][0-9]*)?([eE][+-]?[0-9]+)?$/.test(name) || /^0x[0-9a-fA-F]+$/.test(name) || /^0b[0-9a-fA-F]+$/.test(name)) { return name; } if (name == 'rnd') { if (tok == '(') { Skip('('); if (tok != ')') { var e = Expression(); } Skip(')'); } return 'Math.random()'; } if (name == '_pi') { var e = 1 if (tok == '(') { Skip('('); if (tok != ')') { e = Expression(); } Skip(')'); } return 'Math.PI * ' + e; } if (name == 'varptr') { Skip('('); var vname = tok; Next(); Skip(')'); return VarPtr(vname); } if (name == 'stackdepth') { Skip('('); Skip(')'); return 'sp'; } if (name == 'basedepth') { Skip('('); Skip(')'); return 'bp'; } if (name == 'true') {return '-1';} if (name == 'false') {return '0';} if (name=='_fontheight') {return 'font_height';} if (name=='screen_aspect') {return 'screen_aspect';} if (name=='_width') {return 'curr_width';} if (name=='_height') {return 'curr_height';} if (name=='xmax') {return 'curr_width - 1';} if (name=='ymax') {return 'curr_height - 1';} if (name=='_windowwidth') {return 'GetWinWidth()';} if (name=='_windowheight') {return 'GetWinHeight()';} if (name=='touchdevice') {return -+(('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));} if (name=='csrlin') {return 'text_y + 1';} if (name=='_mousex') {return 'mouse_x';} if (name=='_mousey') {return 'mouse_y';} if (name=='_mousebutton') { if (tok=='(') {Skip('('); var e=Expression(); Skip(')');} return 'mouse_buttons';} if (name=='_mousewheel') {return 'MouseWheel()';} if (name=='_audiodone') {return 'AudioDone()';} if (name=='urlquerystring$') {return 'decodeURIComponent(window.location.search.substring(1))';} if (name=='now$') {return 'Date().toString()';} if (name=='time$') {return 'Date().toString().split(" ")[4]';} if (name=='day$') {var t=new Date(); var r=['SUN','MON','TUE','WED','THU','FRI','SAT'][t.getDay()]; return '"' + r + '"';} if (name=='date$') { var today = new Date(); var dd = "0".concat(today.getDate().toString()); dd = dd.substr(dd.length-2, 2); var mm = today.getMonth()+1; mm = "0".concat(mm.toString()); mm = mm.substr(mm.length-2, 2); var yyyy = today.getFullYear(); return '"".concat("' + mm + '","-","' + dd + '","-","' + yyyy + '")'; } if (name=='log'||name=='ucase$'||name=='lcase$'|| name=='chr$'||name=='getchr$'||name=='sqr'||name=='hex$'|| name=='oct$'||name=='_bin$'||name=='bin$'|| name=='int'||name=='cint'||name=='cdbl'||name=='csng'||name=='fix'||name=='frac'|| name=='asc'||name=='fre'||name=='sgn'|| name=='abs'||name=='len'||name=='val'|| name=='cos'||name=='sin'||name=='tan'||name=='atn'|| name=='exp'||name=='_d2r'||name=='_r2d'|| name=='str$'||name=='peek'||name=='_red'||name=='_green'||name=='_blue'|| name=='ltrim$'||name=='rtrim$'||name=='spc'|| name=='space$'||name=='tab'||name=='pos'||name=='mapget'|| name=='confirm'||name=='getlocalstorageitem'||name=='getsessionstorageitem') { Skip('('); var e=Expression(); Skip(')'); if (name=='confirm') {curop+='Sleep(0.005);\n'; NewOp();} switch (name) { case 'log': return 'Math.log('+e+')'; case 'ucase$': return '('+e+').toUpperCase()'; case 'lcase$': return '('+e+').toLowerCase()'; case 'chr$': return 'String.fromCharCode('+e+')'; case 'getchr$': return 'GetChr('+e+')'; case 'asc': return '('+e+').charCodeAt(0)'; case 'fre': return '("60300")'; case 'sgn': return 'Math.sign('+e+')'; case 'sqr': return 'Math.sqrt('+e+')'; case 'hex$': return '(Math.round('+e+') >>> 0).toString(16)'; case 'oct$': return '(Math.round('+e+') >>> 0).toString(8)'; case 'bin$': case '_bin$': return '(Math.round('+e+') >>> 0).toString(2)'; case 'int': return 'Math.floor('+e+')'; case 'cint': return 'Math.sign('+e+') * Math.round(Math.abs('+e+'))'; case 'cdbl': return 'Math.fround('+e+')'; case 'csng': return 'Math.fround('+e+')'; case 'fix': return 'Math.trunc('+e+')'; case 'abs': return 'Math.abs('+e+')'; case 'frac': return e + '- Math.trunc('+e+')'; case 'cos': return 'Math.cos('+e+')'; case 'sin': return 'Math.sin('+e+')'; case 'tan': return 'Math.tan('+e+')'; case 'atn': return 'Math.atan('+e+')'; case 'exp': return 'Math.exp('+e+')'; case '_d2r': return 'Number('+e+')*(Math.PI/180)'; case '_r2d': return 'Number('+e+')*(180/Math.PI)'; case 'str$': return 'ToString('+e+')'; case 'val': return '+('+e+')||0'; case 'peek': return 'Peek('+e+').toString()'; case '_red': return '('+e+'>>16)'; case '_green': return '('+e+'>>8)&0xFF'; case '_blue': return '('+e+'&0xFF)'; case 'len': return '(('+e+').length)'; case 'ltrim$': return '(('+e+').trimStart())'; case 'rtrim$': return '(('+e+').trimEnd())'; case 'spc': return 'StringRep(('+e+'), " ")'; case 'space$': return 'StringRep(('+e+'), " ")'; case 'tab': return 'StringRep((text_x < '+e+' ? '+e+'-text_x-1 : Math.floor(curr_width/8)-text_x-('+e+'<0?0:1)+'+e+'), " ")'; // case 'tab': return 'StringRep(('+e+'), "\t")'; case 'pos': return '(text_x+1)'; case 'confirm': return '-+confirm(('+e+'))'; case 'mapget': return 'MapGet('+e+')'; case 'getlocalstorageitem': return 'GetLocalStorageItem('+e+')'; case 'getsessionstorageitem': return 'GetSessionStorageItem('+e+')'; case 'stackdepth': return 'sp'; } Throw('This cannot happen'); } if (name=='instr') { var i = '0'; Skip('('); var a = Expression(); Skip(','); var b = Expression(); if (tok==',') { i=a; a=b; Skip(','); b = Expression();} Skip(')'); return '(('+a+').indexOf('+b+','+i+'-1)+1)'; } if (name=='atan2'||name=='_atan2'||name=='string$'|| name=='left$'||name=='right$'||name=='nvl$') { Skip('('); var a = Expression(); Skip(','); var b = Expression(); Skip(')'); if (name=='atan2' || name=='_atan2') {return 'Math.atan2('+a+','+b+')'; } else if (name=='string$') {return 'StringRep('+a+','+b+')'; } else if (name=='left$') {return '(('+a+').substr(0,('+b+')))'; } else if (name=='right$') {return 'Right(('+a+'),('+b+'))'; } else if (name=='nvl$') {return a+'||'+b; } else {throw 'impossible'; } } if (name=='prompt' || name=='_inputbox$') { curop+='Sleep(0.005);\n'; NewOp(); Skip('('); var a=Expression(); if (name=='_prompt' && tok==')') { Skip(')'); var b='""'; } else { Skip(','); if (name=='_inputbox$') { a=Expression(); if (tok==')') {Skip(')'); var b='""'; } else {Skip(','); var b=Expression(); Skip(')');} } else { var b=Expression(); Skip(')');} } return 'prompt(('+a+'),('+b+'))'; } if (name=='labelexists') { Skip('('); var a=Expression(); Skip(')'); return 'LabelExists('+a+')'; } if (name=='point') { Skip('('); var a=Expression(); if (tok==',') {Skip(',');var b=Expression();Skip(')');return 'Point(('+a+'), ('+b+'))';} else {Skip(')');return 'PointPos('+a+')';} } if (name=='rgb2bgr') { Skip('(');var a=Expression();Skip(')');return 'RGB2BGR('+a+')';} if (name=='_rgb'||name=='_rgb32'||name=='bgr') { Skip('('); var a=Expression(); Skip(','); var b=Expression(); Skip(','); var c=Expression(); Skip(')'); if (name=='bgr') {[a,c]=[c,a];return 'RGB2BGR(RGB2(' + a + ',' + b + ',' + c + '))';} else {return 'RGB2(' + a + ',' + b + ',' + c + ')';} } if (name=='keystate') { Skip('('); var a; if (tok!=')') {a = Expression();} Skip(')'); return 'Keystate('+a+')'; } if (name=='_mousezone') { Skip('('); var a = Expression(); Skip(','); var b = Expression(); var c = '1'; var d = '1'; if (tok==',') { Skip(','); c = Expression(); Skip(','); d = Expression();} Skip(')'); return 'MouseZone ('+a+','+b+','+c+','+d+')'; } if (name=='iff') { Skip('('); var a = Expression(); Skip(','); var b = Expression(); Skip(','); var c = Expression(); Skip(')'); return '('+ a + ' ? ' + b + ' : ' + c + ')'; } if (name=='between') { Skip('('); var a = Expression(); Skip(','); var b = Expression(); Skip(','); var c = Expression(); var d = '0'; if (tok==',') {Skip(','); d = Expression();} Skip(')'); return 'BETWEEN ('+a+','+b+','+c+','+d+')'; } if (name=='min'||name=='max') { // return 'Math.' + name + '(('+a+'),('+b+')) var z = 'Math.' + name + '(('; Skip('('); z = z + Expression(); while (tok==',') { Skip(','); z = z + '),(' + Expression(); } Skip(')'); z = z + '))'; return '(' + z + ')'; } if (name=='choose') { Skip('('); var a = Expression(); var z = '['; while (tok==',') { Skip(','); z = z + Expression() + ','; } Skip(')'); z = z.slice(0,-1) + ']' return '('+z+'['+a+'-1])'; } if (name=='mid$' || name=='replace$') { Skip('('); var a = Expression(); Skip(','); var b = Expression(); var c = ""; if (name == 'mid$') { c = a + '.length + 1 -' + b; }; if (tok == ',') { Skip(','); c = Expression(); } Skip(')'); if (name == 'mid$') { return '((' + a + ').substr((' + b + ') - 1, (' + c + ')))'; } else if (name == 'replace$') { return 'StrReplace(' + a + ', ' + b + ', ' + c + ')'; } } if (name=='inkey$') { return 'Inkey()'; } if (name=='timer') { return 'GetTimer()'; } if (functions[name] !== undefined && !functions[name].is_subroutine) { return FunctionCall(name, {is_subroutine: false}); } return IndexVariable(name); } } function Factor2() { var a = Factor3(); while (tok == '^') { Next(); var n = ''; if (tok=='-') {n=tok;Next();} var b = Factor3(); a = 'Math.pow(' + a + ', ' + n + b + ')'; } return a; } function Factor1() { var ret = ''; while (tok == '+' || tok == '-') { ret += tok; Next(); } return ret + '(' + Factor2() + ')'; } function Factor() { var a = Factor1(); while (tok == '*' || tok == '/'||tok == '\\'||tok == 'mod'||tok == 'div') { var op = tok; Next(); var b = Factor1(); if (op=='\\'||op=='div') {a = 'Math.floor((' + a + ')/(' + b + '))';} else if (op=='mod') {a = '((' + a + ')%(' + b + '))';} else {a = '(' + a + ')' + op + '(' + b + ')';} } return a; } function Term() { var a = Factor(); while (tok == '+' || tok == '-') { var op = tok; Next(); var b = Factor(); a = '(' + a + ')' + op + '(' + b + ')'; } return a; } function Relational() { var a = Term(); while (tok == '=' || tok == '<' || tok == '>' || tok == '<>' || tok == '<=' || tok == '>=' || tok == '=>') { var op = tok; Next(); var op2 = op + tok; if (op2 == '<>' || op2 == '<=' || op2 == '>=' || op2 == '=>') { op = op2; Next(); } if (op == '=>') { op = '>='; } var b = Term(); if (op == '=') { a = '(' + a + ') == (' + b + ') ? -1 : 0'; } else if (op == '<>') { a = '(' + a + ') != (' + b + ') ? -1 : 0'; } else { a = '(' + a + ') ' + op + ' (' + b + ') ? -1 : 0'; } } return a; } function Logical1() { var ret = ''; while (tok == 'not') { Next(); ret += '~'; } return ret + '(' + Relational() + ')'; } function Logical() { var a = Logical1(); while (tok == 'and') { Next(); var b = Logical1(); a = '(' + a + ') & (' + b + ')'; } return a; } function Logical03() { var a = Logical(); while (tok == 'imp') { Next(); var b = Logical(); a = '-Math.abs(+(!(' + a + '|' + b + ') | ' + b + '))'; } return a; } function Logical02() { var a = Logical03(); while (tok == 'eqv') { Next(); var b = Logical03(); a = '-Math.abs(+(' + a + '==' + b + '))'; } return a; } function Logical01() { var a = Logical02(); while (tok == 'xor') { Next(); var b = Logical02(); // a = '-Math.abs(+('+a+'? !'+b+':'+b+'))'; a = '('+a+')^('+b+')'; } return a; } function Expression() { var a = Logical01(); while (tok == 'or') { Next(); var b = Logical01(); a = '(' + a + ') | (' + b + ')'; } return a; } function TypeName() { if (SIMPLE_TYPE_INFO[tok]) { var type = tok; Next(); return type; } else if (tok == 'integer') { Skip('integer'); return 'short'; } else if (tok == 'any') { Skip('any'); // TODO: Handle this properly. return 'string'; } else if (types[tok] !== undefined) { var type_name = tok; if (types[type_name] === undefined) { Throw('Undefined type'); } Next(); return type_name; } Throw('Undefined type "' + tok + '"'); } function ImplicitType(name) { return IMPLICIT_TYPE_MAP[name.slice(-3)] || IMPLICIT_TYPE_MAP[name.slice(-2)] || IMPLICIT_TYPE_MAP[name.slice(-1)] || letter_default[name[0]] || 'single'; } function Align(alignment) { allocated = Math.floor((allocated + alignment - 1) / alignment) * alignment; } function Allocate(size) { Align(size > 8 ? 8 : size); var ret = allocated; allocated += size; return ret; } function DimScalarVariable(name, type_name, defaults) { var info = types[type_name] || SIMPLE_TYPE_INFO[type_name]; if (info === undefined) { Throw('Unknown type'); } var size = info.size; var offset = Allocate(size); vars[name] = { offset: offset, dimensions: 0, type_name: type_name, global: vars === global_vars, }; if (inside_type) { var_decls += '// field ' + name + ' is at ' + offset + '\n'; } else if (vars[name].global) { var_decls += '// ' + name + ' is at ' + offset + '\n'; } else { if (inside_function) { curop += '// ' + name + ' is at (bp + ' + offset + ')\n'; } else { var_decls += '// ' + name + ' is at (bp + ' + offset + ')\n'; } } if (defaults.length > 0) { curop += IndexVariable(name, true) + ' = ' + defaults[0] + ';\n'; } } function MaybeImplicitDimVariable(name, argument_to_function) { // TODO: Handle array variables. if (argument_to_function && argument_to_function.vars[name] !== undefined) { return argument_to_function.vars[name]; } if (vars[name] !== undefined) { return vars[name]; } if (global_vars[name] !== undefined) { return global_vars[name]; } if (option_explicit) { Throw('Undeclared variable ' + name); } var type_name = ImplicitType(name); DimScalarVariable(name, type_name, []); return vars[name]; } function ArrayPart(offset, i) { return SIMPLE_TYPE_INFO['long'].view + '[((' + offset + '>>2)+' + i + ')]'; } function ReserveArrayCell(name) { if (vars[name] === undefined && global_vars[name] == undefined) { var offset = Allocate(4 + MAX_DIMENSIONS * 4 * 2); vars[name] = { offset: offset, dimensions: null, type_name: null, global: vars === global_vars, }; var boffset = offset; if (!vars[name].global) { boffset = '(bp+' + boffset + ')'; } var_decls += '// ' + name + ' is at ' + ArrayPart(boffset, 0) + ' (cell-addr: ' + offset + ')\n'; } if (vars[name] !== undefined) { return vars[name]; } return global_vars[name]; } function DimVariable(default_tname,redim,is_declare) { var name=tok; IsKeyword(name); Next(); // Pick default. if (default_tname===null) {default_tname=ImplicitType(name);} var type_name=default_tname; var dimensions=[]; var defaults=[]; var is_scalar=true; if (tok == '(') { Skip('('); if (tok==')') {Throw(name+' : missing dimension(s) for array?');} is_scalar=false; while (tok!=')') { var e=Expression(); var d='dim'+const_count++; //cjv var_decls+='const '+d+'=('+e+');\n'; curop+='const '+d+'=('+e+');\n'; //cjv if (tok=='to') { Skip('to'); var e1 = Expression(); var d1='dim'+const_count++; //cjv var_decls += 'const ' + d1 + ' = (' + e1 + ');\n'; curop += 'const ' + d1 + ' = (' + e1 + ');\n'; //cjv dimensions.push([d, d1]); } else { dimensions.push([option_base, d]); } if (tok != ',') { break; } Skip(','); } Skip(')'); if (tok == '=') { Skip('='); Skip('{'); var e = Expression(); defaults.push(e); while (tok == ',') { Skip(','); var e = Expression(); defaults.push(e); } Skip('}'); } } else if (tok == '=') { Skip('='); var e = Expression(); defaults.push(e); } if (tok == 'as') { Skip('as'); type_name = TypeName(); } if (vars[name] !== undefined && vars[name].dimensions != null) { if (redim) { return; } Throw('Variable ' + name + ' defined twice'); } if (is_scalar) { DimScalarVariable(name, type_name, defaults); } else { if (dimensions.length > MAX_DIMENSIONS) { Throw('Too many dimensions'); } var offset = ReserveArrayCell(name).offset; var info = types[type_name] || SIMPLE_TYPE_INFO[type_name]; var parts = []; for (var i = 0; i < dimensions.length; i++) { parts.push('((' + dimensions[i][1] + ')-(' + dimensions[i][0] + ')+1)'); } if (!is_declare) { if (!vars[name].global) { offset = '(bp+' + offset + ')'; } curop += '// Allocate ' + name + '\n'; curop += 'if (' + ArrayPart(offset, 0) + ' === 0) {\n'; curop += ' ' + ArrayPart(offset, 0) + ' = Allocate(' + [info.size].concat(parts).join('*') + ');\n'; for (var i = 0; i < dimensions.length; i++) { curop += ' ' + ArrayPart(offset, i * 2 + 1) + ' = ' + dimensions[i][0] + ';\n'; curop += ' ' + ArrayPart(offset, i * 2 + 2) + ' = ' + [info.size].concat(parts).slice(0, i + 1).join('*') + ';\n'; } if (defaults.length > 0) { if (dimensions.length > 1) { Throw('Only 1-d array defaults supported'); } if (!SIMPLE_TYPE_INFO[type_name]) { Throw('Only simple type array defaults supported'); } for (var i = 0; i < defaults.length; i++) { curop += ' ' + info.view + '[' + ' + (' + ArrayPart(offset, 0) + ' >> ' + info.shift + ') + ' + i + '] = (' + defaults[i] + ');\n'; } } curop += '}\n'; } vars[name] = { offset: offset, dimensions: dimensions.length > 0 ? dimensions.length : -1, type_name: type_name, global: vars === global_vars, }; } } function IndexVariable(name, assignable, argument_to_function) { IsKeyword(name); var v = MaybeImplicitDimVariable(name, argument_to_function); var offset = v.offset; if (!v.global) { if (argument_to_function) { offset = '(sp+' + offset + ')'; } else { if ((offset.toString()).indexOf('bp') == -1) {offset = '(bp+' + offset + ')';} //cjv OFFSET BUG FIX ??? } } var type_name = v.type_name; while (!argument_to_function && (tok == '(' || tok == '.')) { if (tok=='(') { Skip('('); var dims=[]; var e=''; while (tok!=')') { e=Expression(); dims.push(e); if (tok!=',') {break;} Skip(','); } if (e=='') {Throw(name+': undeclared function');} Skip(')'); var info=types[type_name]||SIMPLE_TYPE_INFO[type_name]; // Extra indirection for array parameter access. if (v.dimensions === -1) { offset = 'i[' + offset + ']'; } var noffset = '('; noffset += ArrayPart(offset, 0) + ' + ('; if (v.dimensions !== -1 && dims.length != v.dimensions) { if (v.dimensions==0) { // Throw(name+': undeclared array/function'); Throw(name+': missing dimension or parameter'); } else { Throw('Array dimension expected ' + v.dimensions + ' but found ' + dims.length + ', array named: ' + name); } } for (var i = 0; i < dims.length; ++i) { noffset += '(((' + dims[i] + ')|0)-' + ArrayPart(offset, i * 2 + 1) + ')'; noffset += '*' + ArrayPart(offset, i * 2 + 2); if (i != dims.length - 1) { noffset += '+'; } } noffset += '))'; offset = noffset; } else if (tok == '.') { Skip('.'); v = types[type_name]; if (v === undefined) { Throw('Not a struct type'); } var field = v.vars[tok]; if (field === undefined) { Throw('Invalid field name'); } Next(); offset = '(' + offset + ' + ' + field.offset + ')'; type_name = field.type_name; } } var info = SIMPLE_TYPE_INFO[type_name]; if (!info) { Throw('Expected simple type'); } var vname = info.view + '[' + offset + '>>' + info.shift + ']'; if (info.view == 'str' && assignable === undefined) { vname = '((' + vname + ')||"")'; } return vname; } function FunctionDefine(options) { var name = tok; Next(); if (vars !== global_vars) { Throw('Nested SUB/FUNCTION not allowed'); } IsKeyword(name); if (functions[name] !== undefined && !functions[name].is_declaration) { Throw('SUB/FUNCTION/CONST/EQN already defined: ' + name); } NewOp(); var pos = ops.length - 1; var parameters = []; var old_allocated = allocated; allocated = 0; vars = {}; var nfunc = { vars: vars, parameters: parameters, ip: ops.length, allocation: -1, is_subroutine: options.is_subroutine || false, is_declaration: options.is_declaration || false, }; if (nfunc.is_declaration) { if (nfunc.is_subroutine) { var_decls += '// SUB ' + name + '\n'; } else { var_decls += '// FUNCTION ' + name + '\n'; } } else { if (nfunc.is_subroutine) { curop += '// SUB ' + name + '\n'; } else { curop += '// FUNCTION ' + name + '\n'; } } if (!nfunc.is_declaration) { inside_function = true; } DimScalarVariable(name, ImplicitType(name), []); // In case return value gets redefined. Align(8); if (tok == '(') { Skip('('); if (tok != ')') { if (tok=='byref') {Skip(tok);} if (tok=='byval') {Skip(tok);} parameters.push(tok); DimVariable(null, undefined, true); while (tok == ',') { Skip(','); if (tok=='byref') {Skip(tok);} if (tok=='byval') {Skip(tok);} parameters.push(tok); DimVariable(null, undefined, true); } } Skip(')'); Align(8); } nfunc.allocation = allocated; if (options.is_declaration) { vars = global_vars; allocated = old_allocated; if (functions[name] == undefined) { functions[name] = nfunc; } else { // TODO: Check for declaration mismatch in type. if (functions[name].parameters.length != nfunc.parameter.length) { Throw('DECLARE and definition parameters do not match'); } } } else { function_old_allocated = old_allocated; function_define_pos = pos; function_name = name; functions[name] = nfunc; } if (tok == 'as') { Skip('as'); var type_name = TypeName(); if (!SIMPLE_TYPE_INFO[type_name]) { Throw('Expected basic type'); } functions[name].type_name = type_name; } if (tok == 'static') { Skip('static'); // TODO: Implement. } } function FunctionExit() { if (vars === global_vars) { Throw('SUB/FUNCTION EXIT only allowed inside SUB/FUNCTION.'); } curop += 'sp -= 8; ip = i[sp>>2];\n'; NewOp(); } function FunctionEnd() { if (vars === global_vars) { Throw('SUB/FUNCTION END only allowed at end of SUB/FUNCTION.'); } inside_function = false; FunctionExit(); ops[function_define_pos] += 'ip = ' + ops.length + ';\n'; vars = global_vars; Align(8); functions[function_name].allocation = allocated; function_name = null; allocated = function_old_allocated; } function FunctionCall(name,options) { var func=functions[name]; if (func==undefined) {Throw(name+' is undeclared');} if (options.is_subroutine!==func.is_subroutine) { if (options.is_subroutine) { Throw('Expected valid subroutine name, found: ' + name); } else { Throw('Expected valid function name, found: ' + name); } } curop += 'i[sp>>2] = bp; sp += 8;\n'; var has_parens = tok == '(' || func.parameters.length != 0; if ((options.is_call && has_parens) || (!options.is_subroutine && has_parens) || (!options.is_call && options.is_subroutine && has_parens)) { Skip('(',name); } var args = []; for (var i = 0; i < func.parameters.length; ++i) { if (func.vars[func.parameters[i]].dimensions == -1) { var vname = tok; Next(); Skip('('); Skip(')'); args.push(null); curop += 'i[sp + ' + func.vars[func.parameters[i]].offset + '] = ' + VarPtr(vname) + ';\n'; } else { var old_tok_count = tok_count; var old_tok = tok; var e = Expression(); curop += IndexVariable(func.parameters[i], true, func) + ' = ' + e + ';\n'; if (vars[old_tok] && tok_count - old_tok_count == 1) { args.push(old_tok); } else { args.push(null); } } if (i != func.parameters.length - 1) { Skip(','); } } if ((options.is_call && has_parens) || (!options.is_subroutine && has_parens) || (!options.is_call && options.is_subroutine && has_parens)) { Skip(')'); } // Blank return value. if (!options.is_subroutine) { curop += IndexVariable(name, true, func) + ' = 123;\n'; } curop += 'bp = sp;\n'; curop += 'sp += functions["' + name + '"].allocation;\n'; curop += 'i[sp>>2] = ip; sp += 8;\n'; curop += 'ip = functions["' + name + '"].ip;\n'; NewOp(); // TODO: Types? curop += 'sp -= functions["' + name + '"].allocation;\n'; curop += 'bp = i[(sp-8)>>2];\n'; var temp = '#temp' + temp_count; if (!options.is_subroutine) { ++temp_count; DimScalarVariable(temp, func.vars[name].type_name, []); curop += IndexVariable(temp, true) + ' = ' + IndexVariable(name, false, func) + ';\n'; } for (var i = 0; i < args.length; ++i) { if (args[i]) { if (constants_list.indexOf('C'+args[i]+'C')>-1) {args[i] = '(' + args[i] + ')';} curop += IndexVariable(args[i], true) + ' = ' + IndexVariable(func.parameters[i], false, func) + ';\n'; } } curop += 'sp -= 8;\n'; if (!options.is_subroutine) { return IndexVariable(temp, false); } } function End() { Sleep(0.5); yielding=1; autodisplay=1; quitting=1; if (canvas) { console.log('=== BASIC END ==='); setTimeout(function(){alert('=== END OF PROGRAM HAS BEEN REACHED ===')},10); } else { if (output_buffer!='') { PutCh(null); } } throw ''; } function SetClipboardText(t) { navigator.clipboard.writeText(t); } function fOpen(fname,fmode,fnum) { localStorage.setItem('file'+fnum,''); localStorage.setItem('file'+fnum+'_name',fname); } function fPrint(fnum, t, sttmnt) { if (t.length==0) { t = '\n'; } else if (t.slice(-1)!==';' && t.slice(-1)!==',') { t = t + '\n'; } sessionStorage.setItem('file' + fnum, (sessionStorage.getItem('file' + fnum)) + t); } function fClose(fnum) { hiddenElement.href = 'data:attachment/text,' + encodeURIComponent(localStorage.getItem('file' + fnum)); hiddenElement.target = '_blank'; hiddenElement.download = localStorage.getItem('file' + fnum + '_name') || 'BAM_' + fnum + '.txt'; hiddenElement.click(); } function Lprint(t) { spool_text=spool_text+t+'\n'; if (!spool_name) { EndSpool(); } } function EndSpool() { hiddenElement.href = 'data:attachment/text,' + encodeURIComponent(spool_text); hiddenElement.target = '_blank'; hiddenElement.download = spool_name || 'BAM_LPRINT_Output.txt'; hiddenElement.click(); CancelSpool(); } function CancelSpool() { spool_name=''; spool_text=''; } function OpenWindow(c) { if ( window.location == window.parent.location ) { var html = ''; openwindow = window.open('', 'ThisBamOutputWindow', ''); openwindow.document.write(html); openwindow.document.body.innerHTML = c.replace('script',' script'); }} function GetWinWidth() {return innerWidth;} function GetWinHeight() {return innerHeight;} function MapSet(k,v) { gmap.set(k,v); } function MapGet(k) { var v = gmap.get(k); if (v===undefined) {v=""} return v; } function ClearLocalStorage() { localStorage.clear(); } function RemoveLocalStorageItem(k) { localStorage.removeItem(k); } function SetLocalStorageItem(k,v) { localStorage.setItem(k,v); } function GetLocalStorageItem(k) { var v = localStorage.getItem(k); if (v === null) { v = ""} return v; } function SetSessionStorageItem(k,v) { sessionStorage.setItem(k,v); } function GetSessionStorageItem(k) { var v = sessionStorage.getItem(k); if (v === null) { v = ""} return v; } function Sleep(t) { yielding = 1; delay = t * 1000; } function GetChr(k) { var v = ""; var n=0; var f = font_height; for (let r=0;r<8;r++) { for (let c=0;c<8;c++) { n = font_data[k*64*f/8+r*8*f/8+c]; if (n==0) {v=v+'.';} else {v=v+'X';} } } return v; } function LetChr(k,v) { var f = font_height; var n=0; for (let r=0;r<8;r++) { for (let c=0;c<8;c++) { if (v[r*8+c]=='X') {n=255;} else {n=0;} font_data[r*f + c +k*64*f/8]=n; if (f==16) {font_data[(r)*f+8 + c +k*64*f/8]=n;} } } } function MouseWheel() { var mwnow = mouse_wheel; mouse_wheel = 0; return mwnow; } function InitAudio() {globalAudioContext = new (window.AudioContext || window.webkitAudioContext)();} function EndAudio() {yielding = 0; for (var i = 0; i < audio_queue.length; i++) { clearTimeout(audio_queue.pop());} audio_queue_timer = 0;} function AudioDone() {return -(audio_queue_timer 0) { return keys.shift(); } else { return ''; } } function KeyClear() { while (keys.length > 0) {Inkey();} } function Yield() {yielding=1;} function Right(s,n) {return s.substr(s.length-n);} function LabelExists(l) { if (labels[((l).replace(/^0+/, "").toLowerCase())]!==undefined) {return -1;} else {return 0;} } function BadLabel(l) {Throw("Invalid line identifier: "+l,false);} function PointPos(f) { if (f==0||f==2) {return pen_x;} if (f==1||f==3) {return pen_y;} } function Point(x,y) { x=Math.floor(x); y=Math.floor(y); var c=display_data[x+y*display.width]; if (color_map==undefined) { return RGB2((c&0x000000ff),((c&0x0000ff00)>>8),((c&0x00ff0000)>>16)); } if (color_map.length==16||color_map.length==256) { return color_map.indexOf(c-0xff000000-0x1000000); } if (color_map.length==2) { return color_map.indexOf(c); } if (color_map.length==4) { if (c==0xff000000) { return 0; } return color_map.indexOf(c-0xff000000-0x1000000); } return color_map; } function StringRep(n, ch) { var ret = ''; var cch; if (typeof ch == 'string') { cch = ch; } else { cch = String.fromCharCode(ch); } for (var i = 0; i < n; ++i) { ret += cch; } return ret; } function ToString(s) { if (s < 0) { return s.toString(); } else { return ' ' + s.toString(); } } function StrReplace(str,replaceWhat,replaceTo) { replaceWhat = replaceWhat.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); var re = new RegExp(replaceWhat, 'g'); return str.replace(re,replaceTo); } function Peek(addr) { return 0; } function RGB(r, g, b) { return BLACK | r | (g << 8) | (b << 16); } function RGB2(r,g,b) { return +("0x" + ("0" + Math.floor(r).toString(16)).slice(-2) + ("0" + Math.floor(g).toString(16)).slice(-2) + ("0" + Math.floor(b).toString(16)).slice(-2)); } function RGB2BGR(v) { var cr = Math.floor(v/256/256); var cg = Math.floor((v-cr*256*256)/256); var cb = v-cr*256*256-cg*256; return RGB(cr,cg,cb); } function MouseZone(x,y,w,h) { if (mouse_x>=x && mouse_x<=(x+w-1) && mouse_y>=y && mouse_y<=(y+h-1)) {return -1;} return 0; } function BETWEEN(a,b,c,d) { if (d==0) {return ((ac)?0:-1));} else {return ((a<=b)?0:((a>=c)?0:-1));} } function DefModes() { // TODO: Handle color right in CGA, EGA, VGA modes. var L=0x55,M=0xAA,H=0xFF; var p2=[BLACK,WHITE]; var p4=[BLACK,RGB(0,M,M),RGB(M,0,M),RGB(M,M,M)]; var p16=[ RGB(0,0,0),RGB(0,0,M),RGB(0,M,0),RGB(0,M,M),RGB(M,0,0),RGB(M,0,M),RGB(M,L,0),RGB(M,M,M), RGB(L,L,L),RGB(L,L,H),RGB(L,H,L),RGB(L,H,H),RGB(H,L,L),RGB(H,L,H),RGB(H,H,L),RGB(H,H,H)]; var p256=[ RGB(0,0,0),RGB(0,0,M),RGB(0,M,0),RGB(0,M,M),RGB(M,0,0),RGB(M,0,M),RGB(M,M,0),RGB(M,M,M), RGB(0,0,L),RGB(0,0,H),RGB(0,M,L),RGB(0,M,H),RGB(M,0,L),RGB(M,0,H),RGB(M,M,L),RGB(M,M,H), RGB(0,L,0),RGB(0,L,M),RGB(0,H,0),RGB(0,H,M),RGB(M,L,0),RGB(M,L,M),RGB(M,H,0),RGB(M,H,M), RGB(0,L,L),RGB(0,L,H),RGB(0,H,L),RGB(0,H,H),RGB(M,L,L),RGB(M,L,H),RGB(M,H,L),RGB(M,H,H), RGB(L,0,0),RGB(L,0,M),RGB(L,M,0),RGB(L,M,M),RGB(H,0,0),RGB(H,0,M),RGB(H,M,0),RGB(H,M,M), RGB(L,0,L),RGB(L,0,H),RGB(L,M,L),RGB(L,M,H),RGB(H,0,L),RGB(H,0,H),RGB(H,M,L),RGB(H,M,H), RGB(L,L,0),RGB(L,L,M),RGB(L,H,0),RGB(L,H,M),RGB(H,L,0),RGB(H,L,M),RGB(H,H,0),RGB(H,H,M), RGB(L,L,L),RGB(L,L,H),RGB(L,H,L),RGB(L,H,H),RGB(H,L,L),RGB(H,L,H),RGB(H,H,L),RGB(H,H,H), 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,RGB(H,H,H)]; return { 0: [640,200,2.4,8,p16,4], 1: [320,200,1.2,8,p4,2], 2: [640,200,2.4,8,p2,1], 7: [320,200,1.2,8,p16,4], 8: [640,200,2.4,8,p16,4], 9: [640,350,480/350,14,p16,4], 10: [640,350,480/350,14,p2,1], 11: [640,480,1,16,p2,1], 12: [640,480,1,16,p16,4], 13: [320,200,1.2,8,p256,8], 14: [320,200,1,8,p256,8], 15: [320,200,2.4,8,p256,8], 16: [320,200,1.2,16,p256,8], 17: [320,200,1,16,p256,8], 23: [640,400,1.2,8,undefined,24], 24: [640,400,1,8,undefined,24], 25: [640,400,2.4,8,undefined,24], 26: [640,400,1.2,16,undefined,24], 27: [640,400,1,16,undefined,24] } } function InitPalette() { var modes=DefModes(); var m=modes[screen_mode]; color_map=m[4]; // reverse_color_map = {}; if (color_map !== undefined) { for (var i = 0; i < color_map.length; ++i) { reverse_color_map[color_map[i]] = i; } fg_color = color_map[color_map.length - 1]; bg_color = color_map[0]; } else { fg_color = WHITE; bg_color = BLACK; } } function Screen(mode, m0, m1) { if (mode==32) {mode=27;} else if ( [0,1,2,7,8,9,10,11,12,13,14,15,16,17,23,24,25,26,27].indexOf(mode) == -1) { mode = 0; curr_mode= 0; } else {curr_mode = mode;} if (!canvas) {return;} // TODO: Handle color right in CGA, EGA, VGA modes. // var monochrome=DefPal(2); // cjv: adding 16 colors to rgba for gw-basic compatibility was a bad idea; reverted // var p16=DefPal(16); // var p256=DefPal(256); // var screen1=DefPal(4); var modes=DefModes(); var m=modes[mode]; if (m===undefined) { Throw('Invalid mode '+mode); } if (m0!=undefined) { m[0]=m0; } if (m1!=undefined) { m[1]=m1; } curr_width=m[0]; curr_height=m[1]; SetupDisplay(m[0],m[1],m[2],m[3]); window.dispatchEvent(new Event('resize')); color_map=m[4]; screen_bpp=m[5]; reverse_color_map = {}; if (color_map !== undefined) { for (var i = 0; i < color_map.length; ++i) { reverse_color_map[color_map[i]] = i; } fg_color = color_map[color_map.length - 1]; bg_color = color_map[0]; } else { fg_color = WHITE; bg_color = BLACK; } screen_mode = mode; pen_x = display.width / 2; pen_y = display.height / 2; Cls(0); } function Width(w) { //if (screen_mode == 0 && (w == 80 || w == 40)) { // SetupDisplay(w * 8, display.height, w == 80 ? 2.4 : 1.2, font_height); //} var modes=DefModes(); var m=modes[screen_mode]; if (m[0] > 0) { SetupDisplay(w * 8, display.height, ( w * m[2] / (m[0]/8) ), font_height); Cls(0);} } function Height(w) { var modes=DefModes(); var m=modes[screen_mode]; if (m[0] > 0) { var ny = w * m[3]; var nx = display.width * (ny/display.height); SetupDisplay(display.width, ny, ( nx * m[2] / (m[0]/font_height) ), font_height); Cls(0);} } var output_buffer = ''; function PutCh(ch) { if (!canvas) { if (ch == null) { console.log(output_buffer); output_buffer = ''; } else { output_buffer += ch; } return; } if (ch == null) { text_x = 0; text_y++; } else { var fg = fg_color; var bg = bg_color; var chcode = (ch.charCodeAt(0) & 0xff) >>> 0; var chpos = chcode * font_height * 8; for (var y = 0; y < font_height; ++y) { var pos = text_x * 8 + (y + text_y * font_height) * display.width; for (var x = 0; x < 8; ++x) { display_data[pos++] = font_data[chpos++] ? fg : bg; } } text_x++; if (text_x >= text_width) { text_y++; text_x = 0; } } if (text_y >= text_height) { text_y = text_height - 1; for (var i = (font_height*display.width - 1); i < (display.width * display.height - 1); i++) { display_data[i-(font_height*display.width)] = display_data[i]; } for (var i = ((display.width*display.height)-(font_height*display.width)); i < (display.width * display.height - 1); i++) { display_data[i] = bg_color; } } } function Pcopy(x1,y1,x2,y2,s,d) { var srcdata; if (x1==undefined || x1<0) {x1=0;} if (y1==undefined || y1<0) {y1=0;} if (x2==undefined || Math.floor(x2)>display.width-1) {x2=display.width-1;} if (y2==undefined || Math.floor(y2)>display.height-1) {y2=display.height-1;} x1=Math.floor(x1);y1=Math.floor(y1); x2=Math.floor(x2);y2=Math.floor(y2); switch(s) { case -1: srcdata = page0; break; case 0: srcdata = display_data; break; case 1: if (page1==undefined) {alert("Page 1 is undefined");} srcdata = page1; break; case 2: if (page2==undefined) {alert("Page 2 is undefined");} srcdata = page2; break; default: alert(s + " is an invalid source page for PCOPY."); return; } switch(d) { case -1: page0=[...srcdata]; break; case 0: if (x1==0 && y1==0 && x2==display.width-1 && y2==display.height - 1) { for (var i = 0; i < display.width * display.height; i++) { display_data[i] = srcdata[i];}} else {for (var y = y1; y < y2+1; y++) { for (var x = x1; x < x2+1; x++) { display_data[x+y*display.width] = srcdata[x+y*display.width]; }}} break; case 1: if (page1==undefined) {page1=[...srcdata];} else if (x1==0 && y1==0 && x2==display.width-1 && y2==display.height - 1) {page1=[...srcdata];} else {for (var y = y1; y < y2+1; y++) { for (var x = x1; x < x2+1; x++) { page1[x+y*display.width] = srcdata[x+y*display.width]; }}} break; case 2: if (page2==undefined) {page2=[...srcdata];} else if (x1==0 && y1==0 && x2==display.width-1 && y2==display.height - 1) {page2=[...srcdata];} else {for (var y = y1; y < y2+1; y++) { for (var x = x1; x < x2+1; x++) { page2[x+y*display.width] = srcdata[x+y*display.width]; }}} break; default: alert(d + " is an invalid destination page for PCOPY."); return; } } function Scroll(x1,y1,x2,y2,h,v,w) { var tempdata;tempdata = [...display_data]; var wx=0; var wy=0; if (x1==undefined || x1<0) {x1=0;} if (y1==undefined || y1<0) {y1=0;} if (x2==undefined || x2>display.width-1) {x2=display.width-1;} if (y2==undefined || y2>display.height-1) {y2=display.height-1;} x1=Math.floor(x1); y1=Math.floor(y1); x2=Math.floor(x2); y2=Math.floor(y2); h=Math.floor(h);v=Math.floor(v); h=h%(x2-x1);v=v%(y2-y1); var xMin = x1; var xMax = x2; var yMin = y1; var yMax = y2; if (h<0) {xMax += h;} else {xMin += h;} if (v<0) {yMax += v; } else {yMin += v;} // alert('xMin: ' + xMin + 'xMax: ' + xMax + 'yMin: ' + yMin + 'yMax: ' + yMax + '\ndisplay.width:' + display.width + 'display.height:' + display.height); for (var y = y1; y < y2+1; y++) { for (var x = x1; x < x2+1; x++) { if (y>=yMin && y<=yMax && x>=xMin && x<=xMax) { display_data[x + y * display.width] = tempdata[x + y * display.width - h - v*display.width];} else { if (w==0) {display_data[x + y * display.width] = bg_color;} else { if (xxMax) {wx=xMin+(x-xMax)-1;} else {wx=x-h;} if (yyMax) {wy=yMin+(y-yMax)-1;} else {wy=y-v;} display_data[x + y * display.width] = tempdata[wx + wy * display.width];} } } } } function LineInput(crlf=1) { while (keys.length > 0) { const key = keys.shift(); if (key == String.fromCharCode(13)) { --text_x; if (crlf==1) {PutCh(' ');PutCh(null);} return; } if (key == String.fromCharCode(8) && input_string.length > 0) { input_string = input_string.substr(0, input_string.length - 1); --text_x; PutCh(' '); text_x -= 2; PutCh(String.fromCharCode(219)); } if (key.charCodeAt(0) >= 32 && key.charCodeAt(0) <= 126) { --text_x; PutCh(key); PutCh(String.fromCharCode(219)); input_string += key; } } yielding = 1; --ip; } function Print(items, sttmnt='p', fnum=null) { if (items.length==0) { PutCh(null); return; } for (var i=0;i 0 ? 1 : 0); value = Math.abs(value); var before = 0; var after = 0; var found_point = false; for (var i = 0; i < format.length; ++i) { if (format[i] == '.') { found_point = true; } else if (format[i] == '#') { if (found_point) { ++after; } else { ++before; } } } var t = value; var fail = Math.floor(t * Math.pow(10, -before)) > 0; value = value * Math.pow(10, after); var ret = ''; var done = false; for (var i = format.length - 1; i >= 0; --i) { if (format[i] == '#') { if (fail) { ret = '*' + ret; } else if (done) { ret = ' ' + ret; } else { ret = Math.floor(value % 10) + ret; value = Math.floor(value / 10); if (value == 0) { done = true; } } } else if (format[i] == '+' || format[i] == '-') { if (fail) { ret = '*' + ret; } else if (sgn < 0) { ret = '-' + ret; } else if (sgn > 0) { if (format[i] == '+') { ret = '+' + ret; } } else { ret = ' ' + ret; } } else if (format[i] == ',') { if (fail) { ret = '*' + ret; } else if (done) { ret = ' ' + ret; } else { ret = ',' + ret; } } else { ret = format[i] + ret; } } return ret; } function PrintUsing(format, items) { var parts = []; var p = ''; var has_num = false; for (var i = 0; i < format.length; ++i) { if (format[i] == '#' || format[i] == ',' || format[i] == '.') { has_num = true; } else { if (has_num) { parts.push(p) p = ''; has_num = false; } } p += format[i]; } if (p != '') { if (has_num) { parts.push(p); } else { parts[parts.length - 1] += p; } } var values = []; for (var i = 0; i < items.length; i += 2) { if (parts.length * 2 > i) { items[i] = Using(parts[(i / 2) | 0], items[i]); } } Print(items); } function ColorFlip(c) { return BLACK | ((c & 0xff0000) >> 16) | ((c & 0xff) << 16) | (c & 0x00ff00); } function FixupColor(c) { if (c === undefined) {c=fg_id;} if (c === undefined) { if (color_map ) { return color_map[color_map.length - 1]; } else { return WHITE; } } if (color_map !== undefined) { // cjv: gw-basic compatibility hack var cml = color_map.length; c = ((c % cml ) + cml) % cml; return color_map[(c%color_map.length)] || color_map[color_map.length - 1]; // cjv || BLACK; } else { c = c | 0;return ColorFlip(c); } } function Palette(c, v) {if (c < color_map.length) {color_map[c] = v;reverse_color_map[color_map[c]] = c;}} function Color(fg, bg) { if (fg != undefined) {fg_color = FixupColor(fg);fg_id=fg;}; if (bg != undefined) {bg_color = FixupColor(bg);bg_id=bg;}; } // function Color_old(fg, bg) { // if (screen_mode == 0 || screen_mode > 2) { // if (fg != undefined) fg_color = FixupColor(fg); // } else { // fg_color = FixupColor(undefined); // } // if (screen_mode == 0 || screen_mode > 2) { // if (bg != undefined) bg_color = FixupColor(bg); // } else { // bg_color = FixupColor(undefined); // } // } function Locate(x, y) { if (x != 0) { text_x = x - 1;} if (y != 0) {text_y = y - 1;} // Hack to yield more often (for NIBBLES.BAS) if (x == 1 && y == 1) { yielding = 1; } } function Box(x1, y1, x2, y2, c) { x1 = x1 | 0; y1 = y1 | 0; x2 = x2 | 0; y2 = y2 | 0; c = c | 0; if (x1 > x2) { var t = x2; x2 = x1; x1 = t; } if (y1 > y2) { var t = y2; y2 = y1; y1 = t; } if (x1 >= display.width || y1 >= display.height || x2 < 0 || y2 < 0) { return; } if (x1 < 0) x1 = 0; if (x2 > display.width - 1) x2 = display.width - 1; if (y1 < 0) y1 = 0; if (y2 > display.height - 1) y2 = display.height - 1; for (var y = y1; y <= y2; ++y) { var pos = x1 + y * display.width; for (var x = x1; x <= x2; ++x) { display_data[pos++] = c; } } } function RawLine(x1, y1, x2, y2, c) { x1 = x1 | 0; y1 = y1 | 0; x2 = x2 | 0; y2 = y2 | 0; if (x1 == x2 || y1 == y2) { Box(x1, y1, x2, y2, c); return; } if (Math.abs(x1 - x2) > Math.abs(y1 - y2)) { if (x1 > x2) { var tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } for (var x = x1; x <= x2; ++x) { var t = (x - x1) / (x2 - x1); // cjv: changed from Math.floor to Math.round; keep an eye on this var y = y1 + Math.round(t * (y2 - y1)); Box(x, y, x, y, c); } } else { if (y1 > y2) { var tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } for (var y = y1; y <= y2; ++y) { var t = (y - y1) / (y2 - y1); // cjv: changed from Math.floor to Math.round; keep an eye on this var x = x1 + Math.round(t * (x2 - x1)); Box(x, y, x, y, c); } } } function Line(x1, y1, x2, y2, c, fill) { var pen_color = FixupColor(c); if (fill == 0) { // Should be line. RawLine(x1, y1, x2, y2, pen_color); } else if (fill == 1) { Box(x1, y1, x2, y1, pen_color); Box(x1, y2, x2, y2, pen_color); Box(x1, y1, x1, y2, pen_color); Box(x2, y1, x2, y2, pen_color); } else { Box(x1, y1, x2, y2, pen_color); } } var last = 0; function Line2(x1, y1, x2, y2, c, fill) { var pen_color = FixupColor(c); if (fill == 0) { // Should be line. RawLine(x1, y1, x2, y2, pen_color); } else if (fill == 1) { Box(x1, y1, x2, y1, pen_color); Box(x1, y2, x2, y2, pen_color); Box(x1, y1, x1, y2, pen_color); Box(x2, y1, x2, y2, pen_color); } else { Box(x1, y1, x2, y2, pen_color); } pen_x = x2; pen_y = y2; } var last = 0; function Cls(mode) { // TODO: Handle mode. Box(0, 0, display.width, display.height, bg_color); text_x = 0; text_y = 0; } function Pset(x, y, c) { x = Math.floor(x); y = Math.floor(y); if (x<0 || y<0 || x>=display.width || y>=display.height) {} else { var pen_color = FixupColor(c); if (c==-1) {pen_color=bg_color;} else if (c==-2) {pen_color=fg_color;} display_data[x + y * display.width] = pen_color; pen_x = x; pen_y = y; } } function Pset2(x, y, c) { x = Math.floor(x); y = Math.floor(y); if (x<0 || y<0 || x>=display.width || y>=display.height) {} else { display_data[x + y * display.width] = c; }} function Circle(x, y, r, c, start, end, aspect, fill) { x=Math.floor(x); y=Math.floor(y); r=Math.floor(r); var pen_color = FixupColor(c); if (c==-1) {pen_color=bg_color;} else if (c==-2) {pen_color=fg_color;} var complete = false; if (start < 0) { start = -start; complete = true; } if (end < 0) { end = -end; complete = true; } if (end < start) { end += Math.PI * 3; } var rx, ry; if (aspect == null) { rx = r; ry = r / screen_aspect; } else if (aspect > 1) { rx = r / aspect; ry = r; } else { rx = r; ry = r * aspect; } if (start != 0 || (end != Math.PI * 3) || aspect != null) { x += 0.5; y += 0.5; var oxx = x + Math.cos(start) * rx; var oyy = y - Math.sin(start) * ry; if (complete) { RawLine(x, y, oxx, oyy, pen_color); } for (var ang = start; ang <= end; ang += 0.03) { var xx = x + Math.cos(ang) * rx; var yy = y - Math.sin(ang) * ry; if (ang == start) { oxx = xx; oyy = yy; } RawLine(oxx, oyy, xx, yy, pen_color); oxx = xx; oyy = yy; } if (complete) { RawLine(x, y, xx, yy, pen_color); } } else { var a; function DoPset2(x,xy,a,y,asa,xysa) { Pset2(x+xy,y-asa, pen_color); Pset2(x-xy,y-asa, pen_color); Pset2(x+xy,y+asa, pen_color); Pset2(x-xy,y+asa, pen_color); Pset2(x-a,y+xysa, pen_color); Pset2(x-a,y-xysa, pen_color); Pset2(x+a,y+xysa, pen_color); Pset2(x+a,y-xysa, pen_color); if (fill==2) { Pset2(x+xy-1,y-asa, pen_color); Pset2(x-xy+1,y-asa, pen_color); Pset2(x+xy-1,y+asa, pen_color); Pset2(x-xy+1,y+asa, pen_color); Pset2(x-a+1,y+xysa, pen_color); Pset2(x-a+1,y-xysa, pen_color); Pset2(x+a-1,y+xysa, pen_color); Pset2(x+a-1,y-xysa, pen_color); Pset2(x+xy,y-asa+1, pen_color); Pset2(x-xy,y-asa+1, pen_color); Pset2(x+xy,y+asa-1, pen_color); Pset2(x-xy,y+asa-1, pen_color); Pset2(x-a,y+xysa-1, pen_color); Pset2(x-a,y-xysa+1, pen_color); Pset2(x+a,y+xysa-1, pen_color); Pset2(x+a,y-xysa+1, pen_color);} } for (var xy = 0; xy < r*0.8; xy += 1) { a = Math.round(Math.sqrt(r * r - xy * xy)); // DoPset2(x,xy,a,y,(a),xy); DoPset2(x,xy,a,y,(a/screen_aspect),xy/screen_aspect); } } if (fill==1 && start == 0 && end > Math.PI * 2) { Paint(x, y, c, c); } pen_x = x; pen_y = y; } function Circle_OLD(x, y, r, c, start, end, aspect, fill) { x=Math.floor(x); y=Math.floor(y); r=Math.floor(r); var pen_color = FixupColor(c); var complete = false; if (start < 0) { start = -start; complete = true; } if (end < 0) { end = -end; complete = true; } if (end < start) { end += Math.PI * 3; } var rx, ry; if (aspect == null) { rx = r; ry = r / screen_aspect; } else if (aspect > 1) { rx = r / aspect; ry = r; } else { rx = r; ry = r * aspect; } if (start != 0 || (end != Math.PI * 3) || aspect != null) { x += 0.5; y += 0.5; var oxx = x + Math.cos(start) * rx; var oyy = y - Math.sin(start) * ry; if (complete) { RawLine(x, y, oxx, oyy, pen_color); } for (var ang = start; ang <= end; ang += 0.03) { var xx = x + Math.cos(ang) * rx; var yy = y - Math.sin(ang) * ry; if (ang == start) { oxx = xx; oyy = yy; } RawLine(oxx, oyy, xx, yy, pen_color); oxx = xx; oyy = yy; } if (complete) { RawLine(x, y, xx, yy, pen_color); } } else { var a; var a_ar; var xy_ar; for (var xy = 0; xy < r*0.8; xy += 1) { a = Math.round(Math.sqrt(r * r - xy * xy)); Pset(x+xy,y-(a/screen_aspect), c); Pset(x-xy,y-(a/screen_aspect), c); Pset(x+xy,y+(a/screen_aspect), c); Pset(x-xy,y+(a/screen_aspect), c); Pset(x-a,y+(xy/screen_aspect), c); Pset(x-a,y-(xy/screen_aspect), c); Pset(x+a,y+(xy/screen_aspect), c); Pset(x+a,y-(xy/screen_aspect), c); Pset(x+xy-1,y-(a/screen_aspect), c); Pset(x-xy+1,y-(a/screen_aspect), c); Pset(x+xy-1,y+(a/screen_aspect), c); Pset(x-xy+1,y+(a/screen_aspect), c); Pset(x-a+1,y+(xy/screen_aspect), c); Pset(x-a+1,y-(xy/screen_aspect), c); Pset(x+a-1,y+(xy/screen_aspect), c); Pset(x+a-1,y-(xy/screen_aspect), c); Pset(x+xy,y-(a/screen_aspect)+1, c); Pset(x-xy,y-(a/screen_aspect)+1, c); Pset(x+xy,y+(a/screen_aspect)-1, c); Pset(x-xy,y+(a/screen_aspect)-1, c); Pset(x-a,y+(xy/screen_aspect)-1, c); Pset(x-a,y-(xy/screen_aspect)+1, c); Pset(x+a,y+(xy/screen_aspect)-1, c); Pset(x+a,y-(xy/screen_aspect)+1, c); } } if (fill && start == 0 && end > Math.PI * 2) { Paint(x, y, c, c); } pen_x = x; pen_y = y; } function GetImage(x1, y1, x2, y2, buffer, offset) { x1 = x1 | 0; y1 = y1 | 0; x2 = x2 | 0; y2 = y2 | 0; if (x1>x2) {[x1,x2]=[x2,x1];} if (y1>y2) {[y1,y2]=[y2,y1];} var d16 = new Uint16Array(buffer); if (screen_bpp <= 2) { d16[(offset >> 1) + 0] = (x2 - x1 + 1) * screen_bpp; } else { d16[(offset >> 1) + 0] = (x2 - x1 + 1); } d16[(offset >> 1) + 1] = y2 - y1 + 1; var d = new Uint8Array(buffer); var src = display_data; if (screen_bpp > 8) { var dstpos = offset + 4; for (var y = y1; y <= y2; ++y) { var srcpos = x1 + y * display.width; for (var x = x1; x <= x2; ++x) { var v = src[srcpos++]; d[dstpos++] = v; d[dstpos++] = (v >> 8); d[dstpos++] = (v >> 16); } } } else { var dstpos = offset + 4; var shift = 8; var v = 0; for (var y = y1; y <= y2; ++y) { var srcpos = x1 + y * display.width; for (var x = x1; x <= x2; ++x) { shift -= screen_bpp; var cc = reverse_color_map[src[srcpos++] | BLACK] | 0; v |= (cc << shift); if (shift == 0) { d[dstpos++] = v; v = 0; shift = 8; } } if (shift != 8) { d[dstpos++] = v; v = 0; shift = 8; } } } } function PutImage(x1, y1, buffer, offset, mode) { x1 = x1 | 0; y1 = y1 | 0; var s16 = new Uint16Array(buffer); var x2; if (screen_bpp <= 2) { x2 = x1 + (s16[(offset >> 1) + 0] / screen_bpp) - 1; } else { x2 = x1 + s16[(offset >> 1)] - 1; } var y2 = y1 + s16[(offset >> 1) + 1] - 1; var s = new Uint8Array(buffer); var dst = display_data; if (screen_bpp > 8) { var srcpos = offset + 4; for (var y = y1; y <= y2; ++y) { var dstpos = x1 + y * display.width; for (var x = x1; x <= x2; ++x) { var v = s[srcpos] | (s[srcpos + 1] << 8) | (s[srcpos + 2] << 16); srcpos += 3; // TODO: Optimize if (x < 0 || x >= display.width || y < 0 || y >= display.height) { dstpos++; continue; } if (mode == 'xor') { dst[dstpos++] = (dst[dstpos] ^ v) | BLACK; } else if (mode == 'preset') { dst[dstpos++] = (~v) | BLACK; } else if (mode == 'and') { dst[dstpos++] = (dst[dstpos] & v) | BLACK; } else if (mode == 'or') { dst[dstpos++] = (dst[dstpos] | v) | BLACK; } else { dst[dstpos++] = v | BLACK; } } } } else { var srcpos = offset + 4; var mask = (1 << screen_bpp) - 1; for (var y = y1; y <= y2; ++y) { var dstpos = x1 + y * display.width; var v = 0; var shift = 8; for (var x = x1; x <= x2; ++x) { if (shift == 8) { v = s[srcpos++]; } shift -= screen_bpp; var cc = (v >> shift) & mask; var old = reverse_color_map[dst[dstpos] | BLACK] | 0; if (mode == 'xor') { cc ^= old; } else if (mode == 'preset') { cc = cc ^ mask; } else if (mode == 'and') { cc &= old; } else if (mode == 'or') { cc |= old; } // alert('color_map[cc]: ' + color_map[cc]); var px = color_map[cc] | 0; // TODO: Optimize if (y >= 0 && y < display.height && x >= 0 && x < display.width) { dst[dstpos++] = px; } else { dstpos++; } if (shift == 0) { shift = 8; } } } } } var draw_state = { noplot: false, nomove: false, angle: 0, turn_angle: 0, color: undefined, scale: 1, }; function StepUnscaled(dx, dy) { if (!draw_state.noplot) { Line(pen_x, pen_y, pen_x + dx, pen_y + dy, draw_state.color, 0); } if (!draw_state.nomove) { pen_x += dx; pen_y += dy; } draw_state.noplot = false; draw_state.nomove = false; } function Step(dx, dy) { StepUnscaled(dx * draw_state.scale, dy * draw_state.scale); } function Draw(cmds) { function AngleStep(a_inc) { var a_tot = draw_angle*Math.PI/180+a_inc*Math.PI/180; Step(Math.round(Math.cos(a_tot)*n),Math.round(-Math.sin(a_tot)*n)); } cmds = cmds.toLowerCase(); cmds = cmds.replace(/\s+/g, ''); cmds = cmds.replace(/;/g, ''); cmds = cmds.replace(/=/g, ''); var m; while (cmds.length) { if (m = cmds.match(/^(u|d|l|r|e|f|g|h|w|x|y|z|a|ta|c|s)([0-9.+-]+)?/)) { var op = m[1]; var n = m[2] == '' ? 1 : m[2]; if (op == 'c') { draw_state.color = n; } else if (op=='a') { draw_angle=n*90; } else if (op=='ta') { draw_angle=n; } else if (op=='s') { draw_state.scale = n / 4; } else if (op=='u') {AngleStep(90);} else if (op=='d') {AngleStep(270);} else if (op=='l') {AngleStep(180);} else if (op=='r') {AngleStep(0);} else if (op=='e') {n=Math.sqrt(n*n+n*n);AngleStep(45);} else if (op=='f') {n=Math.sqrt(n*n+n*n);AngleStep(315);} else if (op=='g') {n=Math.sqrt(n*n+n*n);AngleStep(225);} else if (op=='h') {n=Math.sqrt(n*n+n*n);AngleStep(135);} else if (op=='w') {AngleStep(45);} else if (op=='x') {AngleStep(315);} else if (op=='y') {AngleStep(225);} else if (op=='z') {AngleStep(135);} } else if (m = cmds.match(/^(m)([+-]?)([0-9]+)[,]([+-]?[0-9]+)/)) { var op = m[1]; var sx = m[2]; var x = parseInt(m[3]); var y = parseInt(m[4]); if (sx) { x = parseInt(sx + '1') * x; Step(x, y); } else { StepUnscaled(x - pen_x, y - pen_y); } } else if (m = cmds.match(/^(p)([0-9]+),([0-9]+1)/)) { var op = m[1]; var x = parseInt(m[2]); var y = parseInt(m[3]); // TODO: Implement. } else if (m = cmds.match(/^(b|n)/)) { var op = m[1]; if (op == 'b') { draw_state.noplot = true; } else if (op == 'n') { draw_state.nomove = true; } } else { Throw('Bad drop op: ' + cmds); } cmds = cmds.substr(m[0].length); } } function Paint(x, y, paint, border) { paint = FixupColor(paint); if (border === undefined) { border = paint; } else { border = FixupColor(border) & 0xffffff; } var fpaint = paint & 0xffffff; var data = display_data; var pending = []; pending.push([Math.floor(x), Math.floor(y)]); while (pending.length) { var p = pending.pop(); if (p[0] < 0 || p[0] >= display.width || p[1] < 0 || p[1] >= display.height) { continue; } var pos = p[0] + p[1] * display.width; if ((data[pos] & 0xffffff) == border || (data[pos] & 0xffffff) == fpaint) { continue; } data[pos] = paint; pending.push([p[0] - 1, p[1]]); pending.push([p[0] + 1, p[1]]); pending.push([p[0], p[1] - 1]); pending.push([p[0], p[1] + 1]); } } function GetVar() { var name = tok; Next(); return IndexVariable(name, true); } var DEFAULT_TYPES = { 'defdbl': 'double', 'defsng': 'single', 'defint': 'short', 'deflng': 'long', 'defstr': 'string', }; function Statement() { if (EndOfStatement()) { // Ignore empty lines. } else if (tok == 'rem') { do { Next(); } while (tok != ''); } else if (tok == 'if') { Skip('if'); var e = Expression(); var kw=tok; if (kw=='goto') {Skip(kw);} else {Skip('then');} if (EndOfStatement()) { If(e); while (tok == ':') { Skip(':'); Statement(); } if (tok == 'else') { Skip('else'); Else(); while (tok == ':') { Skip(':'); Statement(); } } if (tok == 'end') { Skip('end'); Skip('if'); EndIf(); } } else { // Classic if then If(e); if ((kw=='goto')||(tok.match(/^[0-9]+$/))) { var name = tok; Next(); curop += 'if (LabelExists("'+name+'")==-1) {ip = labels[("' + name + '").replace(/^0+/, "")];} else {BadLabel("'+name+'");}\n'; NewOp(); } else { Statement(); while (tok==':') { Skip(':'); Statement(); } } var f = flow.pop(); if (f[0]!='if') { Throw('If in mixed style'); } flow.push(f); NewOp(); if (tok=='else') { Skip('else'); Else(); if ((kw=='goto')||tok.match(/^[0-9]+$/)) { var name = tok; Next(); curop += 'if (LabelExists("'+name+'")==-1) {ip = labels[("' + name + '").replace(/^0+/, "")];} else {BadLabel("'+name+'");}\n'; NewOp(); } else { Statement(); while (tok==':') { Skip(':'); Statement(); } } } EndIf(); } } else if (tok=='elseif') { Skip(tok); var e=Expression(); Skip('then'); ElseIf(e); } else if (tok=='else') { Skip(tok); Else(); if (!EndOfStatement()) {Statement();} } else if (tok=='do') { Skip(tok); if (tok=='while') { // Support DO WHILE Statement(); return; } NewOp(); flow.push(['do', ops.length]); } else if (tok=='loop') { Skip(tok); var is_while; if (tok=='while') { Skip(tok); is_while=true; } else if (tok=='until') { Skip(tok); is_while=false; } else if (EndOfStatement()) { var f=flow.pop(); if (f[0] != 'while' && f[0] != 'do') {Throw('LOOP does not match DO / WHILE');} curop+='ip = '+f[1]+';\n'; NewOp(); if (f[0]=='while') {ops[f[1]] += ops.length + '; }\n';} return; } else {Throw('Expected while/until');} var e = Expression(); var f = flow.pop(); if (f[0]!='do') {Throw('LOOP does not match DO');} if (is_while) {curop += 'if ('+e+') {ip='+f[1]+'; }\n';} else {curop += 'if (!('+e+')) {ip='+f[1]+';}\n';} NewOp(); } else if (tok=='while') { Skip(tok); var e=Expression(); NewOp(); curop+='if (!('+e+')) {ip='; NewOp(); flow.push(['while',ops.length-1]); } else if (tok=='wend') { Skip(tok); var f = flow.pop(); if (f[0]!='while') {Throw('Wend does not match while');} curop += 'ip = ' + f[1] + ';\n'; NewOp(); ops[f[1]] += ops.length + '; }\n'; } else if (tok == 'exit') { Skip('exit'); if (tok == 'sub') { Skip('sub'); FunctionExit(); } else if (tok == 'function') { Skip('function'); FunctionExit(); } else { Throw('Exit only works with SUB and FUNCTION'); } } else if (tok == 'end') { Skip('end'); if (tok == 'if') { Skip('if'); EndIf(); } else if (tok == 'select') { Skip('select'); NewOp(); var f = flow.pop(); if (f[0] != 'select') { Throw('end select outside select'); } var disp = 'var t = (' + f[1] + ');\n'; disp += 'if (false) {}\n'; for (var i = 0; i < f[3].length; i++) { var ii = f[3][i]; if (ii[0] == ii[1]) { disp += 'else if (t == (' + ii[0] + ')) { ip = ' + ii[2] + '; }\n'; } else { disp += 'else if (t >= (' + ii[0] + ') && t <= (' + ii[1] + ')) { ip = ' + ii[2] + '; }\n'; } } if (f[5] !== null) { disp += 'else { ip = ' + f[5] + '; }\n'; } else { disp += 'else { ip = ' + ops.length + '; }\n'; } ops[f[2]] += disp; for (var i = 0; i < f[4].length; i++) { ops[f[4][i]] += 'ip = ' + ops.length + ';\n'; } } else if (tok == 'sub') { Skip('sub'); FunctionEnd(); sub_check = sub_check - 1; } else if (tok == 'function') { Skip('function'); FunctionEnd(); fn_check = fn_check - 1; } else { curop += 'End();\n'; } } else if (tok=='stop') { Skip(tok); Throw('BREAK'); } else if (tok=='goto') { Skip(tok); var name = tok; if (tok=='eval') {Skip(tok);Skip('(');name=Expression();Skip(')');curop += 'if (LabelExists(('+name+').toString())==-1) {ip = labels[((' + name + '.toString()).toLowerCase()).replace(/^0+/, "")];} else {BadLabel('+name+');}\n';} else {Next();curop += 'if (LabelExists("'+name+'")==-1) {ip = labels[("' + name + '").replace(/^0+/, "")];} else {BadLabel("'+name+'");}\n';} NewOp(); } else if (tok=='gosub') { Skip(tok); var name = tok; curop += 'i[sp>>2] = ip; sp += 8;\n'; if (tok=='eval') {Skip(tok);Skip('(');name=Expression();Skip(')');curop += 'if (LabelExists(('+name+').toString())==-1) {ip = labels[((' + name + '.toString()).toLowerCase()).replace(/^0+/, "")];} else {BadLabel('+name+');}\n';} else {Next();curop += 'if (LabelExists("'+name+'")==-1) {ip = labels[("' + name + '").replace(/^0+/, "")];} else {BadLabel("'+name+'");}\n';} NewOp(); } else if (tok=='return') { Skip(tok); curop += 'sp-=8;ip=i[sp>>2];\n'; NewOp(); } else if (tok=='declare') { Skip(tok); if (tok=='sub') {Skip(tok);FunctionDefine({is_subroutine: true, is_declaration: true});} else if (tok=='function') {Skip(tok);FunctionDefine({is_subroutine: false, is_declaration: true});} else {Throw('Unexpected declaration');} } else if (tok=='type'||tok=='struct'||tok=='structure'||tok=='record') { var lbl=tok; var old_allocated = allocated; vars = {}; Skip(tok); var type_name = tok; Next(); if (types[type_name]!==undefined) {Throw('Duplicate type definition');} types[type_name]={vars: vars,size: 0,}; inside_type=true; var_decls+='// TYPE '+type_name+'\n'; SkipEndOfStatement(); while (tok!='end') { while (!EndOfStatement()) { DimVariable(null); while (tok == ',') {Skip(',');DimVariable(null);} } SkipEndOfStatement(); } Skip('end'); Skip(lbl); inside_type=false; types[type_name].size=allocated; vars=global_vars; allocated=old_allocated; } else if (tok=='defdv') { Skip(tok); for (;;) { var fname = tok; var pos = FunctionDefine({is_subroutine: false}); Skip('='); var e = Expression(); curop += IndexVariable(fname, true) + ' = ' + e + ';\n'; FunctionEnd(pos); if (tok == ',') { Skip(','); continue; } break; } } else if (tok=='let') { var kw=tok; Skip(tok); while (!EndOfStatement()) { if (tok==',') {Skip(',');} var name = tok; Next(); var vname = IndexVariable(name, true); if (constants_list.indexOf('C'+name+'C')>-1) { Throw('A constant named "'+name+'" already exists.');} if (tok=='=' || tok=='+=' || tok=='-=' || tok=='*=' || tok=='/=' || tok=='\\=' || tok=='^=' || tok=='&=') { var op = tok; Skip(tok); var e = Expression(); if (op == '&=') { op = '+='; } else if (op == '\\=') { op = '//='; } if (op == '^=') {curop += vname + ' = Math.pow(' + vname + ', ' + e + ');\n';} else {curop += vname + ' ' + op + ' (' + e + ');\n';} } else { Throw('Expected "=" or "x=" found "' + tok + '"\n\n(assuming assignment to a variable; could be a case of a misspelled keyword)'); }} } else if (tok=='dim'||tok=='redim'||tok=='var'||tok=='const') { var op=tok; Next(); if (tok=='shared') { Skip('shared'); } var tname = null; if (tok=='as') { Skip('as'); tname=TypeName(); } IsKeyword(tok); if (op=='const' && constants_list.indexOf('C'+tok+'C')==-1) {constants_list=constants_list+'C'+tok+'C';} else if (constants_list.indexOf('C'+tok+'C')>-1) {Throw('A constant named "'+tok+'" already exists.');} DimVariable(tname,op=='redim'); while (tok==',') { Skip(','); DimVariable(tname,op=='redim'); } } else if (tok == 'on') { Skip(tok); var name=Expression(); var kw=tok; var i=0; if (kw=='error') {Skip('error');} else { Next();if (EndOfStatement()) {Throw('Expected labels.');} var rlbls = ' '; while (!(EndOfStatement())) { rlbls+=String(tok); i+=1; Next(); if (EndOfStatement()) {} else {rlbls+=',';Skip(',');} } rlbls=rlbls.replace(/\s+/g, ''); curop += 'if ('+name+'>'+i+') {Throw("Not enough labels for [on ' + kw + '].",false);}'; if (kw=='restore') { curop += 'if (LabelExists("'+rlbls+'".split(",")['+name+'-1])==-1) {data_pos = data_labels["'+rlbls+'".split(",")['+name+'-1].replace(/^0+/, "")];} else {BadLabel("'+rlbls+'".split(",")['+name+'-1]);}\n'; } else if (kw=='goto' || kw=='gosub') { if (kw=='gosub') {curop += 'i[sp>>2] = ip; sp += 8;\n';} curop += 'if (LabelExists("'+rlbls+'".split(",")['+name+'-1])==-1) {ip = labels["'+rlbls+'".split(",")['+name+'-1].replace(/^0+/, "")];} else {BadLabel("'+rlbls+'".split(",")['+name+'-1]);}\n'; } else {Throw('Expected GOTO,GOSUB,RESTORE, or ERROR. Found ' + kw);} NewOp(); } } else if (tok == 'resume') { Skip('resume'); if (tok == 'next') { Skip('next'); // TODO: Implement. } else if (tok == '0') { Skip('0'); // TODO: Implement. } else if (!EndOfStatement()) { var name = tok; Next(); // TODO: Implement. } else { // TODO: Implement. } } else if (tok == 'sub') { Skip('sub'); FunctionDefine({is_subroutine: true}); sub_check = sub_check + 1; } else if (tok == 'function') { Skip('function'); FunctionDefine({is_subroutine: false}); fn_check = fn_check + 1; } else if (tok == 'def') { Skip('def'); if (tok == 'seg') { Skip('seg'); if (tok == '=') { Skip('='); var e = Expression(); // TODO: Do something useful with it? } } else if (tok.substr(0, 2) == 'fn') { if (tok=='fn') {Skip(tok);} var fname = tok; var pos = FunctionDefine({is_subroutine: false}); Skip('='); var e = Expression(); curop += IndexVariable(fname, true) + ' = ' + e + ';\n'; FunctionEnd(pos); } else { Throw('Expected SEG/FNxxx'); } } else if (tok == 'open') { Skip(tok); var fname = Expression(); Skip('for'); var fmode = '"' + tok + '"'; if (tok == 'input') { Skip(tok); } else if (tok == 'output') { Skip(tok); } else { Throw('Expected input/output'); } Skip('as'); tok=tok.replace('#',''); var fnum = Expression(); curop += 'fOpen(' + fname + ',' + fmode + ',' + fnum + ');\n'; NewOp(); } else if (tok == 'close') { Skip(tok); tok=tok.replace('#',''); var fnum = Expression(); // Skip(tok); curop += 'fClose(' + fnum + ');\n'; NewOp(); } else if (tok == 'system') { Skip(tok); // TODO: Implement. } else if (tok == 'donothing') { Skip(tok); } else if (/^([_]?keyclear)$/.test(tok)) { // tok=='keyclear'||tok=='_keyclear') { Skip(tok); curop += 'KeyClear();\n'; NewOp(); } else if (tok == 'beep') { Skip(tok); curop += 'Sound(800,1);\n'; NewOp(); } else if (tok == 'view') { Skip('view'); if (tok == 'print') { Skip('print'); if (!EndOfStatement()) { var top = Expression(); Skip('to'); var bottom = Expression(); } // TODO: Implement. } else if (tok == 'screen') { Skip('screen'); // TODO: Implement. } else { Throw('Expected PRINT/SCREEN'); } } else if (tok == 'randomize') { Skip('randomize'); if (!EndOfStatement()) {var seed = Expression();} // Ignore } else if (tok == 'poke') { Skip('poke'); var addr = Expression(); Skip(','); var value = Expression(); // TODO: Do something useful with it? } else if (tok == 'key') { Skip('key'); if (tok == 'on') { Skip('on'); } else if (tok == 'off') { Skip('off'); } else { Throw('Expected on/off'); } // Implement this? } else if (tok == 'setclipboardtext') { Skip(tok); Skip('('); var t = Expression(); Skip(')'); curop += 'SetClipboardText(' + t + ');\n'; NewOp(); } else if (tok=='lprint') { Skip(tok); var t=Expression(); curop += 'Lprint(' + t + ');\n'; NewOp(); } else if (tok=='_dumpvars') { Skip(tok); curop+='SetClipboardText(JSON.stringify(vars,null,2));\n'; NewOp(); } else if (tok=='mapset') { Skip(tok); Skip('('); var k=Expression(); Skip(','); var v=Expression(); Skip(')'); curop+='MapSet('+k+','+v+');\n'; NewOp(); } else if (tok=='clearlocalstorage') { Skip(tok); curop+='ClearLocalStorage();\n'; NewOp(); } else if (tok == 'removelocalstorageitem') { Skip(tok); Skip('('); var k = Expression(); Skip(')'); curop += 'RemoveLocalStorageItem(' + k + ');\n'; NewOp(); } else if (tok == 'setlocalstorageitem') { Skip(tok); Skip('('); var k = Expression(); Skip(','); var v = Expression(); Skip(')'); curop += 'SetLocalStorageItem(' + k + ',' + v + ');\n'; NewOp(); } else if (tok == 'removelocalstorageitem') { Skip(tok); Skip('('); var k = Expression(); Skip(')'); curop += 'localStorage.removeitem("' + k + '");\n'; NewOp(); } else if (tok == 'setsessionstorageitem') { Skip(tok); Skip('('); var k = Expression(); Skip(','); var v = Expression(); Skip(')'); curop += 'SetSessionStorageItem(' + k + ',' + v + ');\n'; NewOp(); } else if (tok == 'letchr$') { Skip(tok); Skip('('); var k = Expression(); Skip(','); var v = Expression(); Skip(')'); curop += 'LetChr(' + k + ',' + v + ');\n'; NewOp(); } else if (tok == 'sound') { Skip(tok); var freq = Expression(); Skip(','); var duration = Expression(); curop += 'Sound(' + freq + ',' + duration + ');\n'; NewOp(); } else if (tok == '_initaudio') { Skip(tok); curop += 'InitAudio();\n'; NewOp(); curop += 'Sound(10,18.2);\n'; NewOp(); curop += 'Sleep(1.25);\n'; NewOp(); } else if (tok == '_finishaudio') { Skip(tok); curop += 'Sleep(audio_queue_timer-GetTimer());\n'; NewOp(); } else if (tok=='_endaudio') { Skip(tok); curop+='EndAudio();\n'; NewOp(); } else if (tok=='_sndwave') { Skip(tok); var w=Expression(); curop+='sndwave=('+w+').toLowerCase();\n'; NewOp(); } else if (tok=='_sndfade') { Skip(tok); var w=Expression(); curop+='sndfade=('+w+').toLowerCase();\n'; NewOp(); } else if (tok=='play') { Skip('play'); var notes=Expression(); // TODO } else if (tok=='_startspool') { Skip(tok); var w=Expression(); curop+='spool_name='+w+';\n'; NewOp(); } else if (tok=='_endspool') { Skip(tok); curop+='EndSpool();\n'; NewOp(); } else if (tok=='_cancelspool') { Skip(tok); curop += 'CancelSpool();\n'; NewOp(); } else if (tok=='draw') { Skip(tok); var cmds=Expression(); curop+='Draw(' + cmds + ');\n'; } else if (tok == 'chain') { Skip(tok); var filename = Expression(); if (tok == ',') { Skip(','); var name = tok; Next(); } // TODO: Implement this. } else if (tok == 'option') { Skip(tok); if (tok == 'explicit' || tok =='_explicit' ) { Skip(tok); option_explicit = true; } else if (tok == 'base') { Skip(tok); if (tok == '0') { option_base = 0; } else if (tok == '1') { option_base = 1; } else { Throw('Unexpected option base "' + tok + '"'); } Next(); } else { Throw('Unexpected option "' + tok + '"'); } } else if (tok=='defdbl'||tok=='defsng'||tok=='deflng'||tok=='defint'||tok=='defstr') { var def_type = tok; Next(); for (;;) { var start = tok; var end; Next(); if (EndOfStatement()||tok==',') {end=start;} else {Skip('-');end=tok;Next();} if (!start.match(/^[a-z]$/) || !end.match(/^[a-z]$/) || start.charCodeAt(0) > end.charCodeAt(0)) { Throw('Invalid variable range'); } var i = start; do { letter_default[i] = DEFAULT_TYPES[def_type]; i = NextChar(i); } while (i <= end); if (tok == ',') { Skip(','); continue; } break; } } else if (tok == 'for') { Skip('for'); var name = tok; var v = IndexVariable(name, true); Next(); Skip('='); var start = Expression(); Skip('to'); var end = Expression(); var step = 1; if (tok == 'step') { Skip('step'); step = Expression(); curop += 'if ('+step+'==0) {Throw("STEP value of zero not allowed",false);}\n'; NewOp(); } curop += v + ' = (' + start + ');'; NewOp(); curop += 'if (((' + step + ' > 0) && ' + v + ' > (' + end + ')) || ' + '((' + step + ' < 0) && ' + v + ' < (' + end + '))) { ip = '; NewOp(); flow.push(['for', v, ops.length - 1, step]); } else if (tok == 'next') { function AddOp() { curop += f[1] + ' += (' + f[3] + ');\n'; curop += 'ip = ' + f[2] + ';\n'; NewOp(); ops[f[2]] += ops.length + '; }\n'; } Skip(tok); var yup=1; while (yup==1) { var f = flow.pop(); if (f[0] != 'for') { Throw('Expected NEXT'); } if (!EndOfStatement()) { var name = tok; // TODO: Shouldn't this fail? /* if (name != f[1]) { Throw('Expected ' + f[1]); } */ Next(); } AddOp(); if (!EndOfStatement()) { Skip(','); } else {yup=0;} } } else if (tok == 'paint') { Skip(tok); var do_step=0; if (tok=='step') {do_step=1;Skip(tok);} Skip('('); var x = Expression(); Skip(','); var y = Expression(); Skip(')'); if (do_step==1) {x='pen_x+'+x; y='pen_y+'+y;} var paint; if (tok == ',') { Skip(','); paint = Expression(); } var border; if (tok == ',') { Skip(','); border = Expression(); } curop += 'Paint((' + x + '), (' + y + '), (' + paint + '), (' + border + '));\n'; } else if (tok == 'circle') { var s = tok; Skip(tok); var do_step=0; if (tok=='step') {do_step=1; Skip(tok);} Skip('('); var x = Expression(); Skip(','); var y = Expression(); Skip(')'); if (do_step==1) {x='pen_x+'+x; y='pen_y+'+y;} Skip(','); var r = Expression(); var c = 'undefined'; if (tok == ',') { Skip(','); if (tok != ',' && !EndOfStatement()) { c = Expression(); } } var start = 0; var end = Math.PI * 3; var aspect = 'null'; var fill = 0; if (tok == ',') { Skip(','); if (tok != ',' && !EndOfStatement()) { start = Expression(); } } if (tok == ',') { Skip(','); if (tok != ',' && !EndOfStatement()) { end = Expression(); } } if (tok == ',') { Skip(','); if (tok != ',' && !EndOfStatement()) { aspect = Expression(); } } if (tok == ',') { Skip(','); if (tok=='f'||tok=='t') { if (tok=='f') {fill=1} else {fill=2}; Next(); } else { Throw('Expected F (fill) or T (thick), got ' + tok); } } curop += 'Circle((' + [x,y,r,c,start,end,aspect,fill].join('),(') + '));\n'; } else if (tok=='pset'||tok=='plot'||tok=='preset'||tok=='unplot') { var c=-1; if (tok=='pset'||tok=='plot') {c=-2;} Next(); var do_step=0; if (tok=='step') {do_step=1; Skip(tok);} Skip('('); var x=Expression(); Skip(','); var y=Expression(); Skip(')'); if (do_step==1) {x = 'pen_x+'+x; y = 'pen_y+'+y;} if (tok==',') {Skip(',');c=Expression();} curop+='Pset('+x+','+y+','+c+');\n'; } else if (tok=='line') { Skip(tok); if (tok=='input') { var crlf=1; Skip(tok); if (tok==';') {crlf=0;Skip(';');} var prompt='""'; if (tok.substr(0, 1)=='"') { var prompt=tok; Next(); Skip(';'); // if (tok == ';' || tok == ',') { // Next(); // } } curop += 'Print([' + prompt + ', ";"],"p", null);\n'; curop += 'PutCh(String.fromCharCode(219));\n'; curop += 'input_string = ""\n'; NewOp(); curop += 'LineInput('+crlf+');\n'; NewOp(); var a = GetVar(); curop += a + ' = input_string;\n'; return; } var x1='pen_x'; var y1='pen_y'; var do_step=0; if (tok=='step') {do_step=1;Skip(tok);} if (tok=='(') { Skip('('); x1 = Expression(); Skip(','); y1 = Expression(); Skip(')'); if (do_step==1) {x1='pen_x+'+x1;y1='pen_y+'+y1;} } if (tok=='to') {Skip(tok)} else {Skip('-')} do_step=0; if (tok=='step') {do_step=1;Skip(tok);} Skip('('); var x2=Expression(); Skip(','); var y2=Expression(); Skip(')'); if (do_step==1) {x2 = x1+'+'+x2;y2 = y1+'+'+y2;} var c='undefined'; var fill=0; if (tok==',') { Skip(','); if (tok!=',') {c=Expression();} if (tok==',') { Skip(','); if (tok=='b') {fill=1;} else if (tok=='bf') {fill=2;} else {Throw('Unexpected '+tok);} Next(); } } curop += 'Line2((' + [x1, y1, x2, y2, c, fill].join('), (') + '));\n'; } else if (tok == 'get') { Skip('get'); Skip('('); var x1 = Expression(); Skip(','); var y1 = Expression(); Skip(')'); Skip('-'); Skip('('); var x2 = Expression(); Skip(','); var y2 = Expression(); Skip(')'); Skip(','); var name = tok; Next(); var v = ArrayPart(ReserveArrayCell(name).offset, 0); curop += 'GetImage(' + x1 + ', ' + y1 + ', ' + x2 + ', ' + y2 + ', buffer, ' + v + ');\n'; } else if (tok == 'put') { Skip('put'); var do_step=0; if (tok=='step') {do_step=1; Skip(tok);} Skip('('); var x = Expression(); Skip(','); var y = Expression(); Skip(')'); if (do_step==1) {x='pen_x+'+x; y='pen_y+'+y;} Skip(','); var name = tok; Next(); var v = ArrayPart(ReserveArrayCell(name).offset, 0); var mode = 'xor'; if (tok == ',') { Skip(','); if (tok == 'pset' || tok == 'preset' || tok == 'and' || tok == 'or' || tok == 'xor') { mode = tok; Next(); } else { Throw('Invalid put mode'); } } curop += 'PutImage(' + x + ', ' + y + ', buffer, ' + v + ', "' + mode + '");\n'; } else if (tok == 'screen') { Skip(tok); var ret = 'Screen('; if (tok == '_newimage') { Skip(tok); Skip('('); var m0 = Expression(); Skip(','); var m1 = Expression(); Skip(','); var e = Expression(); Skip(')'); } else { var e = Expression(); var m0; var m1; } ret += '' + e + ', ' + m0 + ', ' + m1 + ''; while (tok == ',') { Skip(','); if (tok != ',' && !EndOfStatement()) { var e = Expression(); ret += ', (' + e + ')'; } else { ret += ', null'; } } ret += ');\n' curop += ret; } else if (tok=='cls') { Skip('cls'); var mode='0'; if (tok=='0'||tok=='1'||tok=='2') { mode = tok; Next(); } curop+='Cls('+mode+');\n'; } else if (tok=='sleep'||tok=='_delay') { Skip(tok); if (EndOfStatement()) {curop+='SleepUntilKey=1;\n';} else {var e=Expression();curop+='if ('+e+'==0) {SleepUntilKey=1;} else {Sleep('+e+');}\n';} NewOp(); } else if (tok == 'pcopy') { Skip(tok); var x1; var y1; var x2; var y2; if (tok=='(') { Skip('(');x1=Expression();Skip(',');y1=Expression();Skip(')'); Skip('-'); Skip('(');x2=Expression();Skip(',');y2=Expression();Skip(')'); Skip(','); } var a = Expression(); Skip(','); var b = Expression(); curop += 'Pcopy('+x1+','+y1+','+x2+','+y2+','+a+','+b+');\n'; NewOp(); } else if (tok=='scroll') { Skip(tok); var x1; var y1; var x2; var y2; if (tok=='(') { Skip('(');x1=Expression();Skip(',');y1=Expression();Skip(')'); if (tok=='to') {Skip(tok);} else {Skip('-');} Skip('(');x2=Expression();Skip(',');y2=Expression();Skip(')'); Skip(','); } var h=Expression();Skip(',');var v=Expression(); var w = 0; if (tok==',') {Skip(',');w=Expression();} curop+='Scroll('+x1+','+y1+','+x2+','+y2+','+h+','+v+','+w+');\n'; NewOp(); } else if (tok=='_limit') { Skip(tok); var e=Expression(); } else if (tok=='_title') { Skip(tok); var e=Expression(); curop+='document.title='+e+';\n'; NewOp(); } else if (tok=='_autodisplay') { Skip(tok); curop+='autodisplay = 1;\n'; NewOp(); curop+='display_now = 0;\n'; NewOp(); } else if (tok=='_display') { Skip(tok); curop+='autodisplay = 0;\n'; NewOp(); curop+='display_now = 1;\n'; NewOp(); } else if (tok=='alert') { Skip(tok); Skip('('); var e=Expression(); Skip(')'); curop+='Sleep(0.005);\n'; NewOp(); curop+='alert('+e+');\n'; NewOp(); curop+='mouse_buttons=0;\n'; NewOp(); curop+='KeyClear();\n'; NewOp(); curop+='canvas.focus();\n'; NewOp(); } else if (tok=='consolelog') { Skip(tok); Skip('('); if (tok=='ops') {var e = 'ops.toString()';Skip(tok);} else {var e=Expression();} Skip(')'); curop+='Sleep(0.005);\n'; NewOp(); curop+='console.log('+e+');\n'; NewOp(); } else if (tok=='_openwindow') { Skip(tok); Skip('('); var e=Expression(); Skip(')'); curop+='Sleep(0.005);\n'; NewOp(); curop+='OpenWindow('+e+');\n'; NewOp(); } else if (tok=='locate') { Skip(tok);var y='0';var x='0'; if (tok!=',' && !EndOfStatement()) {y=Expression();} if (tok == ',') {Skip(',');x=Expression();} if (tok == ',') { Skip(','); var cursor = Expression(); // TODO: Support cursor + start + stop } curop+='Locate(Math.trunc('+x+'),Math.trunc('+y+'));\n'; } else if (tok=='width') { Skip(tok); var w=Expression(); if (tok==',') { Skip(','); var n = Expression(); // TODO } curop+='Width('+w+');curr_width='+w+'*8;window.dispatchEvent(new Event("resize"));\n'; } else if (tok=='height') { Skip(tok); var w = Expression(); if (tok == ',') { Skip(','); var n = Expression(); // TODO } curop += 'Height(' + w + ');curr_height='+w+'*font_height;window.dispatchEvent(new Event("resize"));\n'; } else if (tok == 'color') { Skip('color'); var fg; var bg; if (tok != ',') fg = Expression(); if (tok == ',') { Skip(','); bg = Expression(); } if (tok == ',') { Skip(','); var cursor = Expression(); // TODO: Support cursor } curop += 'Color(' + fg + ',' + bg + ');\n'; } else if (tok == 'palette') { Skip(tok); if (!EndOfStatement()) { var c = Expression(); Skip(','); var p = Expression(); curop += 'Palette(' + c + ',' + p + ');\n'; } else { curop += 'InitPalette();\n'; } } else if (tok == 'swap') { Skip(tok); if (constants_list.indexOf('C'+tok+'C')>-1) {Throw('SWAP with constant "'+tok+'" not allowed.');} var a = GetVar(); Skip(','); if (constants_list.indexOf('C'+tok+'C')>-1) {Throw('SWAP with constant "'+tok+'" not allowed.');} var b = GetVar(); curop += 'var t = ' + a + ';\n'; curop += a + ' = ' + b + ';\n'; curop += b + ' = t' + ';\n'; } else if (tok == 'mid$') { Skip(tok); Skip('('); var a = GetVar(); Skip(','); var b = Expression(); var c = "0"; if (tok == ",") { Skip(','); c = Expression(); } Skip(')'); Skip('='); var d = Expression(); var thisop = '(' + a + ').substr( 0, (' + b + ') -1)'; var tempop = '(' + thisop +').concat( "", (' + d + ')'; if (c !== "0") { tempop = tempop + '.substr(0,' + c + ')'; } thisop = tempop + ')'; thisop = '(' + thisop + ').substr(0, (' + a + ').length)'; thisop = '(' + thisop + ').concat( "", (' + a + ').slice((' + thisop + ').length))'; curop += a + ' = ' + thisop + ';\n'; } else if (tok == 'data') { ConsumeData(); } else if (tok == 'read') { Skip('read'); curop += 'if(data[data_pos]==undefined) {Throw("OUT OF DATA");}\n'; curop += GetVar() + ' = data[data_pos++];\n'; while (tok == ',') { Skip(','); curop += GetVar() + ' = data[data_pos++];\n'; } } else if (tok == 'restore') { Skip('restore'); if (!EndOfStatement()) { var name = tok; if (tok=='eval') {Skip(tok);Skip('(');name=Expression();Skip(')');curop += 'if (LabelExists(('+name+').toString())==-1) {data_pos = data_labels[((' + name + '.toString()).toLowerCase()).replace(/^0+/, "")];} else {BadLabel('+name+');}\n';} else {curop += 'if (LabelExists("'+name+'")==-1) {data_pos = data_labels[("' + name + '").replace(/^0+/, "")];} else {BadLabel("'+name+'");}\n';Next();} } else { curop += 'data_pos = 0;\n'; } } else if (tok == 'input') { var crlf=1; Skip(tok); if (tok[0] == '#') { // TODO: Implement. Next(); if (tok == ';' || tok == ',') { Next(); } } if (tok == ';') { crlf=0; Skip(';'); } var prompt = '"? "'; if (tok.substr(0, 1) == '"') { var prompt = tok; Next(); if (tok==';' || tok==',') { if (tok==';') {prompt=prompt.substring(0,prompt.length-1)+'? "'} Next(); } } curop += 'Print([' + prompt + ', ";"],"p",null);\n'; curop += 'PutCh(String.fromCharCode(219));\n'; curop += 'input_string = ""\n'; NewOp(); curop += 'LineInput('+crlf+');\n'; NewOp(); var n = 0; while (!EndOfStatement()) { curop += GetVar() + ' = input_string.split(",")[' + n++ + '];\n'; if (tok != ',') { break; } Skip(','); } } else if (tok=='print'||tok=='?'||tok=='write'||tok.substring(0,6)=='print#'||tok.substring(0,6)=='write#') { var sttmnt=tok.substring(0,1); var fnum = null; if (tok.substring(0,6)=='print#') { tok = tok.replace('print#',''); fnum = Expression(); } else if (tok.substring(0,6)=='write#') { tok = tok.replace('write#',''); fnum = Expression(); } else { Skip(tok); if (tok.charAt(0)=='#') { tok = tok.replace('#',''); fnum = Expression(); } } if (EndOfStatement()) { if (fnum !== null) { curop += 'fPrint('+ fnum + ',"","p");\n'; } else { curop += 'Print([],"p",null);\n'; } return; } if (fnum !== null) { Skip(','); } var fmt = null; if (tok == 'using') { Skip('using'); fmt = Expression(); Skip(';'); } var items = []; var e = Expression(); items.push(e); while (tok == ';' || tok == ',') { if (fnum==null) { items.push('"' + tok + '"'); } else { if (sttmnt=='w') {items.push('","');} else {items.push('"' + tok + '"');} } Next(); if (tok == 'else') { break; } // cjv if (EndOfStatement()) { break; } var e = Expression(); items.push(e); } if (fnum !== null) { // curop += 'fPrint(' + fnum + ',' + items.join(' + ') + ',"' + sttmnt + '");\n'; curop += 'Print([' + items.join(', ') + '],"' + sttmnt + '",' + fnum + ');\n'; } else if (fmt !== null) { curop += 'PrintUsing(' + fmt + ', [' + items.join(', ') + ']);\n'; } else { curop += 'Print([' + items.join(', ') + '],"' + sttmnt + '",' + fnum + ');\n'; } } else if (tok == 'select') { Skip('select'); Skip('case'); if (tok == 'as') { Skip('as'); Skip('const'); } var e = Expression(); NewOp(); flow.push(['select', e, ops.length - 1, [], [], null]); } else if (tok == 'case') { Skip('case'); var f = flow.pop(); if (f[0] != 'select') { Throw('Case outside select'); } NewOp(); f[4].push(ops.length - 1); if (tok == 'else') { Skip('else'); f[5] = ops.length; flow.push(f); return; } function Accept(t) { if (tok == t) { Next(); return true; } return false; } do { var e = Expression(); if (tok == 'to') { Skip('to'); var e1 = Expression(); f[3].push([e, e1, ops.length]); } else { f[3].push([e, e, ops.length]); } } while (Accept(',')); flow.push(f); } else if (tok == 'getmouse') { Skip('getmouse'); curop += 'Yield();'; NewOp(); curop += GetVar() + ' = mouse_x;\n'; Skip(','); curop += GetVar() + ' = mouse_y;\n'; if (tok == ',') { Skip(','); if (tok != ',') { curop += GetVar() + ' = mouse_wheel;\n'; } } if (tok == ',') { Skip(','); if (tok != ',') { curop += GetVar() + ' = mouse_buttons;\n'; } } if (tok == ',') { Skip(','); curop += GetVar() + ' = mouse_clip;\n'; } } else if (tok=='') { return; } else if (tok=='call' || (functions[tok] !== undefined && functions[tok].is_subroutine)) { var name=tok; Next(); if (name=='call') { name=tok; Next(); FunctionCall(name,{is_subroutine:true,is_call:true}); } else { NoEekOnSkip=1; FunctionCall(name,{is_subroutine: true}); NoEekOnSkip=0; } } else { var name = tok; Next(); if (tok == ':') { Skip(':'); AddLabel(name); Statement(); return; } if (constants_list.indexOf('C'+name+'C')>-1) { Throw('The constant "'+name+'" already exists.');} var vname = IndexVariable(name,true); if (tok=='='||tok=='+='||tok=='-='||tok=='*='||tok=='/='||tok=='\\='||tok=='^='||tok=='&=') { var op = tok; Next(); var e=Expression(); if (op=='&=') { op='+='; } else if (op=='\\=') { op='//='; } else if (op=='^=') { curop+=vname+'=Math.pow('+vname+', '+e+');\n'; return; } curop+=vname+' '+op+' ('+e+');\n'; } else { Throw('Expected "=" or "x=" found "' + tok + '"\n\n(assuming assignment to a variable; could be a case of a misspelled keyword)'); } } } function Compile() { NewOp(); while (tok != '') { for (;;) { // Implement line numbers. if (tok.match(/^[0-9]+$/)) { AddLabel(tok); Next(); } Statement(); while (tok == ':') { Next(); Statement(); } if (tok == '') { break; } SkipEndOfStatement(); } if (sub_check!=0) {throw(sub_check + ' missing "END SUB".')}; if (fn_check!=0) {throw(fn_check + ' missing "END FUNCTION".')}; } // Check for matching flow control. if (flow.length != 0) { var f = flow.pop(); Throw('Unmatched ' + f[0]); } // Implicit End. NewOp(); curop += 'End();'; NewOp(); // Align to 8. Align(8); // Allocate stack. stack = Allocate(STACK_SIZE); sp = stack; bp = sp; var total = ''; total += 'var buffer = new ArrayBuffer(' + allocated + ' + ' + DYNAMIC_HEAP_SIZE + ');\n'; for (var i in SIMPLE_TYPE_INFO) { var info = SIMPLE_TYPE_INFO[i]; if (i == 'string') { total += 'var str = [];\n'; } else { total += 'var ' + info.view + ' = new ' + info.array + '(buffer);\n'; } } total += var_decls; total += 'for (var j = 0; j < ops.length; ++j) {\n'; if (debugging_mode) { total += ' console.info("L" + j + ":\\n" + ops[j]);\n'; } total += ' ops[j] = eval("(function() {\\n" + ops[j] + "})\\n");\n'; total += '}\n'; if (debugging_mode) { console.info(total); } eval(total); } var viewport_x, viewport_y; var viewport_w, viewport_h; function Resize() { if (from_tag) {canvas.width=window.innerWidth;canvas.height=window.innerHeight;} canvas.width-=2;canvas.height-=2; var raspect = canvas.width / canvas.height; var aspect = display.width / (display.height * screen_aspect); if (raspect > aspect) { viewport_w = Math.floor( display.width * canvas.height / (display.height * screen_aspect)); viewport_h = canvas.height; viewport_x = Math.floor((canvas.width - viewport_w) / 2); viewport_y = 0; } else { viewport_w = canvas.width; viewport_h = Math.floor( (display.height * screen_aspect) * canvas.width / display.width); viewport_x = 0; viewport_y = Math.floor((canvas.height - viewport_h) / 2); } } function Render() { if (!canvas) { return; } var scale_ctx = scale_canvas.getContext('2d'); scale_ctx.fillStyle = '#000'; scale_ctx.fillRect(0, 0, scale_canvas.width, scale_canvas.height); scale_ctx.putImageData(display, 0, 0); var ctx = canvas.getContext('2d'); ctx.fillStyle = '#aaaaaa'; // cjv ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.imageSmoothingQuality = 'high'; ctx.imageSmoothingEnabled = false; ctx.drawImage(scale_canvas, viewport_x, viewport_y, viewport_w, viewport_h); requestAnimationFrame(Render); } function RegularKey(n) { keys.push(String.fromCharCode(n)); } function ExtendedKey(n) { keys.push(String.fromCharCode(0) + String.fromCharCode(n)); } function InitEvents() { const SIMPLE_KEYMAP = { // BACKSPACE, ENTER, ESCAPE 8: { regular: 8 }, 13: { regular: 13 }, 27: { regular: 27 }, // INS, DEL 45: { regular: 82 }, 46: { regular: 83 }, // LEFT, RIGHT 37: { extended: 75 }, 39: { extended: 77 }, // UP, DOWN 38: { extended: 72 }, 40: { extended: 80 }, // PGUP, PGDN 33: { extended: 73 }, 34: { extended: 81 }, // HOME, END 36: { extended: 71 }, 35: { extended: 79 }, }; if (!canvas) { return; } Resize(); if (from_tag) { window.addEventListener('resize', Resize, false); } window.addEventListener('keyup',function(e){ keyState[e.keyCode || e.which] = false; keyPressed = 0; },false); window.addEventListener('keydown', function(e) { keyPressed = e.keyCode || e.which; keyState[keyPressed] = true; if (SleepUntilKey==1) {SleepUntilKey=0;} if (e.keyCode >= 112 && e.keyCode <= 123) { // F1 - F10 if (e.altKey) { ExtendedKey(e.keyCode - 112 + 104); } else if (e.ctrlKey) { ExtendedKey(e.keyCode - 112 + 94); } else if (e.shiftKey) { ExtendedKey(e.keyCode - 112 + 84); } else { ExtendedKey(e.keyCode - 112 + 59); } } else if (e.keyCode == 9) { // TAB, Shift-TAB if (e.shiftKey) { RegularKey(15); } else { RegularKey(9); } } else if (SIMPLE_KEYMAP[e.keyCode]) { if (SIMPLE_KEYMAP[e.keyCode].regular) { RegularKey(SIMPLE_KEYMAP[e.keyCode].regular); } else { ExtendedKey(SIMPLE_KEYMAP[e.keyCode].extended); } } else if (e.ctrlKey && e.keyCode >= 65 && e.keyCode <= 90) { // Ctrl-A to Ctrl-Z RegularKey(e.keyCode - 65 + 1); } else if (e.altKey) { const ch = String.fromCharCode(e.keyCode); const row1 = '1234567890-='.indexOf(ch); const row2 = 'QWERTYUIOP'.indexOf(ch); const row3 = 'ASDFGHJKL'.indexOf(ch); const row4 = 'ZXCVBNM'.indexOf(ch); if (row1 >= 0) { ExtendedKey(row1 + 120); } else if (row2 >= 0) { ExtendedKey(row2 + 16); } else if (row3 >= 0) { ExtendedKey(row3 + 30); } else if (row4 >= 0) { ExtendedKey(row4 + 44); } } else { const code = e.key.charCodeAt(0); if (e.key.length == 1 && code >= 32 && code <= 126) { RegularKey(code); } } }, false); canvas.addEventListener('mousemove', function(e) { // TODO: Generalize for non-fullscreen. //var rect = canvas.getBoundingClientRect(); var rect = {left: 0, top: 0}; mouse_x = Math.floor( (e.clientX - rect.left - viewport_x) * display.width / viewport_w); mouse_y = Math.floor( (e.clientY - rect.top - viewport_y) * display.height / viewport_h); }, false); canvas.addEventListener('touchmove', function(e) { var touch = e.touches.item(0); const evt = new Event('mousemove'); evt.clientX = touch.clientX; evt.clientY = touch.clientY; canvas.dispatchEvent(evt); }, false); canvas.addEventListener('mousedown', function(e) { if (SleepUntilKey==1) {SleepUntilKey=0;} var e = e || window.event; if (e.button == 0) {mouse_buttons = -1;} }, false); canvas.addEventListener('touchstart', function(e) { canvas.dispatchEvent(new Event('mousedown')); }, false); canvas.addEventListener('mouseup', function(e) { mouse_buttons = 0; }, false); canvas.addEventListener('mouseleave', function(e) { mouse_buttons = 0; }, false); canvas.addEventListener('mouseover', function(e) { mouse_buttons = e.buttons; }, false); canvas.addEventListener('touchend', function(e) { canvas.dispatchEvent(new Event('mouseup')); }, false) // TODO: Implement Mouse Wheel! canvas.addEventListener('wheel', function(e) { if (event.deltaY < 0) {mouse_wheel = mouse_wheel - 1;} else if (event.deltaY > 0) {mouse_wheel = mouse_wheel + 1;} }); // TODO: Implement Mouse Clip! } function Run() { var speed=10000001-0; for (;;) { for (var i=0;i

Sunday, October 20, 2024

Undead Handmines

 Undead Handmines

A seasonal trap/encounter for yuor RPG campaigns.


 Undead Handmines

A fields of earth, sand, or gravel concealing bound undead that will reach up from under the ground to hold victims in place. 

A casting of "Find Traps" will provide allies with a +2 hit bonus when attacking exposed handmines.

A 10 ' area will have 2-5 handmines placed within it. When one activates the entire field of handfields activiate and flail about grasping at any nearby.  Up to 2 attacks per victim are possible. When all handmines in a 10' area have grasped someone it is safe to pass.

The chance of activation is per character per 10' area of handmine stepped upon. Prodding with a 10' pole will only have a 1 in 6 chance of triggering Ghastly, Wight, or Mummified handmines due to those undead being more clever than lesser undead. 

A normal Turn or a T result will deactivate the undead handmines for 1-3 hours. A D result will permanently deactivate the land mines freeing them from their binds and allowing them to dig themselves free in 3-6 turns.

Handmines attack as normal members of the undead but ay -2 to hit. Each handmime gets 2 attacks against an individual but may only hold one victim. Any successful attack by a handmine will require the victim to make a saving throw or be held in place until they break free or expire.

Skeletal Handmines, activation 1-2 in 6, Grasp STR 9, initial damage: 1, Hand destroyed on 1 pt of damage

Zombie Handmines, activation 1-2 in 6, Grasp STR 12, initial damage: 1-2, , Hand destroyed on 2 pts of damage

Ghoulish Handmines, activation 1-3 in 6, Grasp STR 12, initial damage: 1-4, Hand destroyed on 2 pts of damage

Ghastly Handmines, activation 1-3 in 6, Grasp STR 13, initial damage: 1-4, Hand destroyed on 3 pts of damage

Wight Handmines, activation 1-4 in 6, Grasp STR 13, initial damage: energy drain!, Hand destroyed on 4 pts of damage if attacked with silver or magical weapons.

Mummified Handmines,activation 1-5 in 6, Grasp STR 14, initial damage: 1d12+disease, Hand destroyed on 4 pts of damage if attacked with magical weapon.

 Grasp STR is the strength the undead hand mine applies to hold a victim. This will impact the saving throw to pull free.  If victims strength is 2 points or lower than the handmines Grasp STR save at -2, if the victims strength is 2 point or more higher than the handmines Grasp STR the save is at +2.  Up to 3 attempts to pull free are allowed but the victim will be subject to attack each time after the first when struggling to break free. 

Attacking the hand grasping someone. Any blow that misses the hit roll has a 75% to harm the victim being held. On a successful hit any damage over the amount needed to destroy the hand is suffered by the vitim being grasped.

An undead handmine will hold a victim until the victim expires. Usually 3 days will result in death by dehydration if the victim has no access to water.

Success of attacks against un-exposed undead handmines are ill-advised as the ground will protect them in effect providing them +4 to AC and saving throws along with halving damage  of area attacks (before saving throws) with some still failing to cause harm if the ground may not be penetrated.

Saturday, October 5, 2024

Mutant Scavenger Equipment Generation Idea

 Possible Mutant Scavenger Equipment Generation.  Armors and weapons for the last post at least.


Mutant Scavenger Weaponry. (Roll 3d6+level)

4 Junk Kosh (Sock full of rocks), 1d4 damage.

5 Junk Shiv, 1d4 damage.

6 Junk Bludgeon (big hunk of old pip), 2d4 damage

7 Junk Spear (Pointy Stick), 1d6 damage.

8 Flimsy Junk Bow 40ft range, and 6 junky glass headed arrows 1d6 damage.

9 Junk Club (Old Broomstick), 1d6 damage.

10 Primitive Stone Dagger 20ft effective ragne is thrown, 2d4 damage.

11 Primitive Stone Club, 2d6 damage

12 Primitive Self Bow 60ft range, 2d6 damage.

13 Pipe Grenade , as grenade 3d6 damage.

14 Steel Bayonet, 2d6 damage

15 Primitive Scrap Axe, throw 20ft range, 2d8 damage

16 Primitive Bronze Sword 2d8 damage.

17 Primitive Crossbow 80ft range 2d8 damage (1 round to load), 8 bolts.

18 Junk Zip Gun 20ft range, 1d10 damage. 6 shots of ammo.

19 Primitive Pipe Musket 40ftrange, 2d10 damage. 6 shots of ammo.

20 Advanced Compound Bow 100ft range, 3d6 damage, 12 arrows.

21 Advanced Ceramax Bayonet, 3d6 damage

22 Advanced Poly-alloy Claymore Sword, 3d8

23 Electro-rapier 3d6, charge holds for 20 minutes.

24 Bolt Action Rifle, 200ft range, 3d10 damage, 10 rounds of ammo.

25 Advanced Gyorjet Pistol 80ft range, 3d8 damage. 10 rounds of ammo.

26 Laser Pistol, 100ft range, 5d6. 10 shot energy cell.

27 Chainsaw 4d6

28 Plasma Grenade, as grenade 5d8 damage

29 Advanced Combat Shotgun, 90ft range,10ft spread, 4d10 dmg., 2 shots per round, 12 shot magazine.

30 Advanced Battle Rifle, 300ft range 4d10 dmg, 4 shots per round, 40 shot magazine.

31 Implosion Grenade, as grenade 6d10 damage

32 Cryonic-Hammer 4d6 damage

33 Sonic-nunchaku 4d6 damage

34 Vibronic Katana 4d8 damage

35 Mono-filament Blade 4d10 damage

36 Void Ray Pistol 60' range, 6d8 damage. charge holds 8 shots.


*Ranges listed are effective range. If a grenade is rolled that's just 1 grenade so roll again for another weapon.


Armor

1st level has 65% chance of having armor, 2-5th level is 80% chance of armor, 6th level plus is 90% chance of armor.

1st level roll 1d8, 2-4th level roll 1d12, 5-10th level roll 3d6, 11th level+ roll 2d8+6


1  Furs and Skins, +1 Armor

2  Carboard and Duct Tape Cuirass, +1 Armor

3  Boiled Leather Cuirass +2 Armor

4  Reinforced Padded Armor +2 Armor, 1/2 Damage from Junk Weapons

5  Reflective Poncho +2 Armor, 1/2 damage from beam weapons

6  Turnout Coat, +2 Armor, +2 to save and 1/2 damage from thermal attacks

7  Chainmail Brynie +3 Armor

8  Ballistic Vest, +3 armor, 1/2 damage from ballistic weapons

9  Brigandine Armor +4 Armor

10 Void Shroud +4 armor, 90% chance to negate Void Rays weapons

11 Full Suit of Chainmail, +5 Armor

12 Grid Suit +5 Armor, 1/2 damage from electro and stun weapons

13 Medieval Plate Armor, +6 Armor

14 Riot Armor +6 Armor, 1/2 Damage from Primitive weapons and Ballistic Weapons

15 Kinetic Suit, +7 Armor , 1/2 damage from Blunt Weapons, Ballistic, and explosives

16 Plasteel Combat armor +8 Armor

17 Heavy NBC Armor +8 Armor, +4 bonus to saves vs Thermal, Radiation, Bacteria, or Chemical Weapons

18 Nano-fiber Ninja-stocking, +8 Armor, 1/2 Damage from Advanced melee weapons (or weaker)

19 Elite Forces Body Armor, +9 Armor

20 Mirrored Power Armor +9 Armor, blocks 50% of Beam Weapon Damage.

21 Ceramax Combat Armor +10 Armor

22 Poly-Alloy Symbiotic Armor +10 Armor, 50 HP Forcefield, field regenerates every 10 minutes.


Wednesday, October 2, 2024

Twelve Mutant Scavengers

 12 Mutant scavengers for the heck of it. The rules are skeletal but not hard to translate to many old schools set of rules.  Tasks are 3d6+Bonus vs difficulty unless the task is done with a mutant's prime ability score and it's 4d6+Bonus in that case. All bonuses are 1/3 the related ability score (no fractions). Target Class is how hard a target is to hit in combat, the score on the left is the normal score the one in parens is for attacks from behind or when the mutant is dazed or surprised; higher numbers are better.

These mutant scavengers are the result of a random generation and the mutations come from here for now: https://elfmaidsandoctopi.blogspot.com/2024/09/chagrinspire-mutations.html

Mutant Scavenger # 1

=========================

Cunning  : 16/+5    Target Class :  10 (6)

Interface: 5/+1     Hit Points    :  35

Psi      : 7/+2

Fitness  : 7/+2     Level  :  4

Prowess  : 7/+2     Notice : +5

Influence: 13/+4    Sneak  : +5

Prime Ability is Cunning

=== Mutations ===

Nose is a beak usable for d4 unarmed peck attack.

Cyclopean Deformity, has a single eye.

Terrible breath like rotten meat.

----------



 Mutant Scavenger # 2

=========================

Cunning  : 15/+5    Target Class :  10 (6)

Interface: 12/+4    Hit Points    :  49

Psi      : 10/+3

Fitness  : 14/+4    Level  :  4

Prowess  : 7/+2     Notice : +5

Influence: 10/+3    Sneak  : +5

Prime Ability is Cunning

=== Mutations ===

Huge dripping nose, sensitive to pollen and spores which trigger sneezing.

----------



 Mutant Scavenger # 3

=========================

Cunning  : 20/+6    Target Class :  11 (6)

Interface: 11/+3    Hit Points    :  69

Psi      : 11/+3

Fitness  : 17/+5    Level  :  6

Prowess  : 10/+3    Notice : +6

Influence: 9/+3     Sneak  : +6

Prime Ability is Cunning

=== Mutations ===

Terrible breath like rotten meat.

----------



 Mutant Scavenger # 4

=========================

Cunning  : 6/+2     Target Class :  13 (8)

Interface: 8/+2     Hit Points    :  29

Psi      : 15/+5

Fitness  : 9/+3     Level  :  2

Prowess  : 15/+5    Notice : +2

Influence: 16/+5    Sneak  : +2

Prime Ability is Influence

=== Mutations ===

Second jaws fold from the mouth and can bite foes within 10ft range for a d6 bonus unarmed attack.

Exhale acid fog cloud forming a 30'wide circle up to 60' range, one use per level per day. 1 HP damage per level per round. Will damage cured leather and fibres.

----------


 Mutant Scavenger # 5

=========================

Cunning  : 8/+2     Target Class :  12 (6)

Interface: 15/+5    Hit Points    :  30

Psi      : 9/+3

Fitness  : 11/+3    Level  :  2

Prowess  : 12/+4    Notice : +2

Influence: 12/+4    Sneak  : +2

Prime Ability is Interface

=== Mutations ===

Prehensile tongue a yard long.

Detachable head, mind linked to the body and know each other's location. If either is destroyed will grow a replacement over a year.

----------



 Mutant Scavenger # 6

=========================

Cunning  : 7/+2     Target Class :  13 (6)

Interface: 12/+4    Hit Points    :  26

Psi      : 11/+3

Fitness  : 13/+4    Level  :  1

Prowess  : 15/+5    Notice : +2

Influence: 8/+2     Sneak  : +2

Prime Ability is Prowess

=== Mutations ===

Improved Fitness +2 to score and recover extra d4HP per day.

Detect synthetic beings within 60ft.

----------



 Mutant Scavenger # 7

=========================

Cunning  : 12/+4    Target Class :  13 (6)

Interface: 13/+4    Hit Points    :  21

Psi      : 7/+2

Fitness  : 6/+2     Level  :  1

Prowess  : 15/+5    Notice : +6

Influence: 14/+4    Sneak  : +4

Prime Ability is Prowess

=== Mutations ===

Can reproduce with plants. Produce a level one clone at end of growing season.

Head can rotate 360 degrees or detach to fly up to 240 feet away from body. But may otherwise act as normal.

----------



 Mutant Scavenger # 8

=========================

Cunning  : 11/+3    Target Class :  10 (7)

Interface: 5/+1     Hit Points    :  47

Psi      : 12/+4

Fitness  : 10/+3    Level  :  6

Prowess  : 7/+2     Notice : +3

Influence: 10/+3    Sneak  : +3

Prime Ability is Influence

=== Mutations ===

Extra toes.

Skeletal emaciated face. Foes under level are uneasy and -2 to hit in melee combat if they aren't rallied.

----------



 Mutant Scavenger # 9

=========================

Cunning  : 9/+3     Target Class :  10 (6)

Interface: 14/+4    Hit Points    :  36

Psi      : 15/+5

Fitness  : 12/+4    Level  :  4

Prowess  : 7/+2     Notice : +3

Influence: 6/+2     Sneak  : +3

Prime Ability is Fitness

=== Mutations ===

Stone feet, half damage from hazards or traps that damage feet.

Breathe fire 3d6 damage 30ft range 30 degree cone, Fitness bonus times pear day, targets saves halve damage.

----------



 Mutant Scavenger # 10

=========================

Cunning  : 9/+3     Target Class :  13 (6)

Interface: 6/+2     Hit Points    :  24

Psi      : 7/+2

Fitness  : 8/+2     Level  :  1

Prowess  : 16/+5    Notice : +3

Influence: 10/+3    Sneak  : +3

Prime Ability is Prowess

=== Mutations ===

Extra Face on chest yells abuse and gibbers crazily in fights.

----------



 Mutant Scavenger # 11

=========================

Cunning  : 13/+4    Target Class :  11 (6)

Interface: 14/+4    Hit Points    :  23

Psi      : 9/+3

Fitness  : 9/+3     Level  :  1

Prowess  : 11/+3    Notice : +4

Influence: 11/+3    Sneak  : +4

Prime Ability is Interface

=== Mutations ===

Magnetic sense. Can detect metals within 20' when concentrating.

Extra Crab claw, d6 unarmed attack.

----------



 Mutant Scavenger # 12

=========================

Cunning  : 10/+3    Target Class :  11 (6)

Interface: 16/+5    Hit Points    :  34

Psi      : 9/+3

Fitness  : 8/+2     Level  :  4

Prowess  : 9/+3     Notice : +3

Influence: 14/+4    Sneak  : +3

Prime Ability is Interface

=== Mutations ===

Head is a biological gasmask protecting eyes and filtering out smoke, poison gas, stench or miasmas.