Friday, August 29, 2025

Holmes1to3 [general guidelines]

 Notes on playing a D&D campaign using only the Holmes Basic set of rules with level 1 to 3 being the only levels achievable by Player Characters.    1st level characters are tough bravos. 2nd level characters are champions. 3rd level characters are legendary. This is for doing a "whole campaign" that will see it's end sometime after all the PCS are 3rd level. Will they become lords of a new order, will they break the seals and release the old magics, or will it never matter because levels 1 to 3 are enough for a dozen or so sessions.

Holmes1to3

Hit Points.

As the whole campaign is from levels 1 to 3 PCs are given a HP boost at 1st level to improve their odds a bit (and makes the PCS more heroic compared to many NPCs and monsters). This boost is equal to the prime requisite score of the player character.  So Fighters have an HP bonus equal to their STR score(for example). Split classed Characters will have the lower primer requisite score as the HP bonus. So an elf with an STR of 9 and an INT of 15 playing the standard split Fighter/MU has a bonus of 9 HP as the STR of 9 is the lower of the prime requisites.

At 1st level a split classed character adds the HitDie roll of the large HD to their HP total. As they advance per level after that they only get ½ the roll for each class as the individual levels are gained. (The new roll with the HP bonus for CON is halved if the character has such)

Note: this puts the HP range from 4 to 51 for PCS over the life of a campaign based on class, level and ability scores.  

Class options:

Human: any 1 class

Dwarf: Fighter or Split Class Fighter/Thief or Split Class Fighter/Cleric. Dwarven Figher/Thieves are commonly known as Sappers and may be of any alignment. Dwarven Figher/Clerics are known as RuneBrands and MUST be Lawful Good or Chaotic Evil.

Halfling: Fighter, Thief or Split Class Fighter/Thief

Halglfing Fighter/Thieves of Good Alignments are known as Scouts.

Elf: Fighter, Thief, or Magic-user, or Split Classed Fighter/Magic-User or Split Classed Magi-User/Thief. Single classed Thieves and Magic-users are rare among elves and the Magic-User/Thief is known as a Trickster and not considered trustworthy among Good Elves.

Split classed characters advancement and limitations:

Some characters may advance in 2 classes. When doing so those experience points are divided equally into each class whenever they are rewarded. Experience points are still divided even 3rd level has been reach in one of the 2 classes a character has.

Split Classed characters are free to use any weapon of their classes

Split classed Fighter/MU may cast spells in armor.

Spit classed thieves only have ½ their chance to use a thief skill if wearing armor heavier than Leather or bearing a shield, and lose their backstab, escape, and waylay abilities in those heavier armors.

Ability Modifiers:

Strength: All characters with a Strength score of 13 or more may a +1 damage to damage they cause by blows in melee combat.

Fighters may qualify for a bonus to hit and damage as follows.

STR Score

Bonus to Hit and Damage

13 to 15

+1 to hit AND +1 Damage  with two Handed weapons, Mounted Lances*

16 or 17

+2 to hit AND +2 Damage with two Handed weapons, Mounted Lances*

18

+3 to hit ANS +3 Damage with two Handed Weapons, Mounted Lances*

 

Humans and Elves may add this bonus if they are using a Two Handed Sword, A MorningStar, A Battle Axe(in 2 hands), or A Pole Arm.

*Dwarves may add this bonus when wielding ANY Axe or Hammer in Two Hands.

*Halflings are too small to make use of this bonus regardless of ability score.

Extra Abilities of Each Character Race and Class.

Fighters:

MIGHTY BLOWS:  Any time the d20 die roll to hit is 19 or 20 (and that attack doesn’t require a score of 19 or more to hit) the character has struck a mighty blow and add 1d6 per Fighter Level to the damage roll. (These additional damage dice are always 1d6 even if variant weapon damage rules are used). The mighty blow applies to melee weapons and thrown weapons. (This rule should NOT be used in concert with critical strikes). Monsters do NOT have this ability normally.

COMBAT DOMINANCE: When fighting a group of foes all of 1HD or less a fighter of level 2 or 3  is permitted 1 extra attack per level over 1 they have. A higher HD foe within 20’ cancels this ability as the fighter dare not divert attention from them. The fighter may lay all these blows on one hapless victim or divide them each against a different foe within reach. (creatures of 1+1HD do not qualify as having 1HD or less but 1st level character types do even if they have a CON bonus).

FEND OFF FOES: when armed with a long weapon or two handed weapon a fighter may choose to FEND OFF FOES instead of making a regular attack. A Fighter may move when fending off foes but may not move within 10’ of a foe in this round. They sacrifice their next attack to be able to attack all foes advancing within 10 feet of them. If the blow hits the foes  is held back 10’ away and damaged as normal (sorry no Mighty Blows when fending off foes but bonuses for high STR still apply). When fending off foes the fighter is allowed 1 counter attack and 1extra counter attack per level (but only one per opponent). If any counter attack misses no further counter attacks may be made in a round. Counter attacks interrupt the closing enemies attacks. (There, now two handed weapons are very useful even if normally only allowed an attack every other round)

If no foes try to close within10’ of the fighter fending off foes no attacks may be made this round.

You may not fend off foes if you start the round engaged in melee.

Dwarves:

 dwarves add 1d6 damage per Fighter level when striking Ogres, Trolls, and Giants in melee combat even when it is not a mighty blow. When a dwarf scores a mighty blow against such a foe they get roll all the bonus dice but only keep the best rolls equal to their level . So a 2nd level dwarf striking a mightyl blow against a giant would roll 2 dice for hitting a giant and 2 dice for the mighty blow and if the rolls were 1,3,2,5 the 3 and 5 would be picked to determine damage.

Halflings:

Halfling Fighters may add their level to damage caused by thrown weapons in all situations.

Elves:

Elven Fighters that start a round armed with a short bow or longbow and are not in melee combat may choose to fire a volley of shots if they DO NOT MOVE during the round. This volley of shots permits the elf to loose 1 more arrow per Fighter Level (making a standard attack roll with each). Attacks from a volley may be all made against 1 foe or against multiple foes that are within the same 20 of the 1st target of a volley. Targets must be picked for each shot before the hit rolls are made.

Clerics:

SMITE EVIL. As with mighty blows any time a cleric hits an evil supernatural creature with a hit roll of 19 or 20 the cleric adds 1 die per cleric level to the damage. This applies against all Undead, Demons, and Spirts that may be harmed in melee combat. Different pantheons of gods may have a different group of creatures that may be vicitms of the “SMITE EVIL” ability.

SCRIBE SCROLLS: clerics may fashion scrolls in a similar cost an fashion to a magic-user. A cleric may also copy A scroll if they have one on hand even if 1st level. The copying costs as much as it noramlly does to scribe a scroll. This allows 1st level clerics to make scrolls if they have the money and time.

Magicusers:

BREW POTIONS: As they may scribe scrolls for 100 GP per spell level Magic users may brew potions for 200GP per spell level. All character classes may use a potion regardless of the spell so placed in it. A spell potion may take effect immediately or the drinker may target a victim for the spell in the following round. Spells not used by the end of the following round are lost. This potion spell may not be used on the same round a spell caster casts one of their normal spells or reads a scroll. A DM may require special ingredients (up to 2 per spell level) and each such special ingredient does reduce the cost to brew by 50GP. If special ingredients are required for a potion no amount of cash will replace them in and of itself.

Thieves:

WAYLAY: instead of making a backstab a thief that strikes an unaware foe from behind may instead try to knock them out adding 1d6 temporary damage per level if armed with a blunt weapon on their waylay attempt. If this would bring a foe to 0 HP or less they are knocked out but have only suffered 1 actual HP of damage and will recover all but that one point when revived by the actions of others or 2 full turns have passed.

If the blow wasn’t enough to waylay the foe (bring hp to 0 or less) they only take 1 hp damage from the attack.

Waylay Attacks may not be made against Elementals, Dragons, Undead, Giant Insects, Plants, Fungus, Slimes ,Oozes, Constructs, Animated Objects, and large sized animals.

ESCAPE COMBAT: A Thief with is allowed to make a saving throw to retreat from combat without risking a parting shot from any foes. Use the roll vs Turn to Stone. One saving throw is all that is needed to escape all foes. a failed saving throw and the thief is subject to partying blows as normal.

Add a bonus to the roll as follows if the Thief has a DEX of 13 or higher.

DEX Score

Bonus to Escape Combat

13 to 15

+1 to saving throw to escape combat

16 or 17

+2 to saving throw to escape combat

18

+3 to saving throw to escape combat

 Note of thief abilities: some of the chances of thieves skills seem really meager capped as they are in levels 1 to 3 but do consider these are in addition to normal capabilities beyond those of normal folks. They are hiding in shadows, not simply hiding. They are moving silently not simply quietly. And anyone can figure out how to bypass a trap the theif skills are ABOVE that in the odds of success.

HOLMES1to3 Player Character and NPC Score to hit

LEVEL

Armor Class

F

C/T

MU

9

8

7

6

5

4

3

2

1

0

0-Level

11

12

13

14

15

16

17

18

19

20

1

1-2

1-3

10

11

12

13

14

15

16

17

18

19

2

3

 

9

10

11

12

13

14

15

16

17

18

3

 

 

8

9

10

11

12

13

14

15

16

17

 NOTE: to hit chances are adjusted to make fighters more meaningful and not keep all characters at the same fighting capability at levels 1 to 3. The slightly improved odds for fighters along with the additional special abilities make fighters pretty fearsome and capable even at 2nd level.

 

Holmes1to3 Spells Class and Level

Magic-User

(Elf)

Cleric

CL

Spell Level

CL

Spell Level

CL

Spell Level

1

2

3

1

2

3

1

2

1

2

-

-

1

1

-

-

1

-

-

2

3

2

-

2

2

1

-

2

2

-

3

3

3

2

3

2

2

1

3

2

2

 

(ELF) column is to limit the split/class options a little bit. The level is either their combination level or their MU level depending on how the DM tracks Elf levels. A single classed Elf MU would use the Magic-User portion of the table.

Notes: Magical wands and other devices are very powerful compared to the spell casting abilities of player characters in Holmes1to3.

Cure Light Wounds is the ONLY PC-sourced healing spell. More powerful healing will have to come from ancient magics, special rituals or mysterious places.

Magic-Users get a lot of spell capability but are fairly vulnerable. (With 18 CON and 18 Int the highest HP a MU will have is 39, the highest a Fighter/MU will have is 47 and only if they have an 18 is STR,INT, and CON ). Remember those 3rd level fireballs and lightning bolts they cast will be 3d6 damage (not 5d6) before a saving throw for hald of that.


Sunday, August 24, 2025

Old DM Trick #5: Careful Door Description

Never forget the power of description.

There are two doors on the northern wall of the chamber. The one to the right is a simple oaken door with brass fittings and ring pull. The door to the left is half again as wide and half again as tall as the other door. It is made of ossified dragonwood and bound by bands of Urru-Steel forged by the dwarven forge-masters on Kazak-Urrinnor; the bands are bent out slightly and still hold the door firm from whatever impact or pressure thrown upon the other side. Closer inspection to the door on the left reveals small cracks in the ossified dragonwood boards, whatever did that is powerful indeed and surely only held back by the mighyt Urru-steel bands.  

This depends on how well you know your group of players. Some groups will never be able to avoid the fancier door to the left, they will even forget there is another door. Other groups will choose the simpler door smelling a trap on the more detailed door..."but wait, maybe that's the trick on hand to force us through the boring door".

And therein lays the trick. A boring choice now has drama. An arbitrary choice is no longer arbitrary.

Saturday, August 23, 2025

Push Die!

Sometimes you want your PC heroes (and knaves) to push on, to try harder. Here's a game mechanic for any system where you roll high. The Push Die.

The Push Die is a die added to your normal resolution check to improve your odds of success. It is voluntary on part of the player if they will or will not add the push die to the roll and must be declared before making the normal roll to resolve a task. The push die should be rolled simultaneously with the resolution die, it's score being added to roll.

If the roll of a push die is a 1 the character suffers a point of fatigue. Tally that up and if fatigue is ever higher than 1/2 of the ability score related to an action the PC is now -2 to attempt that action (or at disadvantage if you use that mechanic in your game).

A HitDie of Fatigue is removed each full turn of rest a character takes. Magical healing will remove 1/2 of fatigue if the character is fully healed by healing magic. A DM may wish to add additional spells, salves, or potions t0 the game that remove (or inflict fatigue). A normal amount of full rest (for the night or a full rest break) this fatigue is fully removed.

If the resolution roll fails the full score of the push die is suffered as fatigue.

If the resolution roll fails with a fumble (a roll of 1 if you are using 1d20) the full score of the push die is suffered as fatigue and the related ability score suffers a point of strain. If the highest number was rolled on a push die than 1/2 that score is suffered as strain. Strain can be added up against each ability score. If strain is greater than 1/2 the ability score the character is now -2 to attempt actions with that ability. if strain exceeds a character's ability score they are overcome and unable to purposefully take actions that would use that ability and are -10 if forced to make a saving throw or reaction using that ability score.  Yeah -10, being virtually helpless should be a big deal.

An hour of rest will remove 1 point of strain in 1 ability score. 

I recommend a 1d4 be the base Push Die for all characters. If the ability score is a Prime Ability shift up a die. If the character has a score of 15 or more in an ability shift up a die.  A player may choose to roll less than their full push die if desired. So a Fighter with a score of 17 in Strength (a prime attribute in many systems) could roll 1d8 when pushing it. (or choose to roll 1d4, or 1d6 instead).

Why I like this mechanic.   There's no direct penalty for trying harder. But there is a consequence for trying harder and failing. The more players push it the more they will win...until they don't. I can also use a fatigue mechanic without having to always apply a fatigue score to a host of actions. Trying harder can cause fatigue but there isn't a constant penalty for action itself. Players that have their characters push on and on without resting will suffer more. those that do not risk will not reap rewards as easily however.

Tuesday, August 5, 2025

"And this d8 has an A instead of a 1, do you want to know why?"

A  friendly joking facebook response made to a humorous post on gamers dice fetish. But seriously do you want to know more? 

The first eight sided die I owned came from a set of polyhedral dice my dad went into the city and bought for me when I was a kid and had begun playing D&D with the boxed set that came with chits instead of actual dice. I pillage dice from the Yahtzee set but used paper chits for the other dice sizes. So probably sometime after my 2nd or 3rd D&D game my dad just shows up late after work one day with a set of polyhedral dice. There was a d4, d8, d12 and one or two d20's numbered 0-9 twice. The d8 has this curious little A on it instead of a 1 where the 1 would normally be.

Not a huge mystery really. Turns out it was from a mold meant for a set of poker dice to get results of 2 through 8 and Aces. I immediately pondered "what about the other numbers? Wouldn't a d12 make more sense for poker dice? That way you could come closer to a full set of cards, you'd just be missing one face card?" . "They probably didn't know about 12 sided dice when they made these eight-sided ones" was my father's sensible response. I was actually a little surprised the first time I saw d8 with a "1" instead of an "A".

EDIT:   I believe the set of dice was sold by The Armory and my dad bought it as The Compleat Strategist.

Monday, August 4, 2025

Quick Humanoid Roster

Wanting to have a different roster of humaoids in my next megadungoen and here's
my roster so far.
HD Move AC Atk, Special
Feral Hobars         1/2     30 12 weapon 1d4, +4 save vs poison,hide 90%
Thorn Gnomes 1/2     20 13 weapon 1d4 +sleep poison, automatic counter
attack vs grapple attacks +4 to save against herbals and gas
Cyanoids 1-1     40 11 weapon 1d6, immune to poison & paralysis, -2 vs mental                                                                             attacks Healing magic "Hastes" then for 2 rounds per point
                                                                of healing
Boggarts         1     30 13 weapon 1d6, 33% capture spell
Poison Dwarves 1+1     20 14 weapon 1d6 +poison, immune to poison & paralysis 
  Rotwood Elves     1+1     30 14 weapons 1d6 +spores, immune to sleep & paralysis,
                                                                +4 vs poison
Hammer Goats 2 50 14 hammers 2d4, +2 to save vs mental attacks cause confusion in                                                                     those 2HD or less 
Amazons 2 40 14 weapons 1d8,+1 initiative, immune to charm by males
Broken Men 2+1 20 14 weapons 1d8, 33% to drive a telepath insane vs mental                                                                                 intrusion/attacks,climb walls 95%,hide 66%
                                                                can't be knocked prone
Headless         3 30 13 weapons 1d8+1,Bite 1d4+1, +4 to save vs mental attacks 
Lanks 3 40 13 weapons 1d10, 90% silent, always has reach advantage over                                                                     normal men
Geminoids 3+1 30 13     2 weapons 1d8+1 or 4 weapons 1d6 each,
                                                                +4 save vs.mental & illusions,twice as hard to surprise
Cyclops         4 40 14 weapon 1d10+2, blows shatter normal weapons or shields 1in6,                                                                 +2 vs illusions 

HD are 1d8 Move is feet per round if unencumbered or in stabdard default armor (as
per AC listed) 
AC listing is for most common armor. Boggarts,Poison Dwarves,
Rotwood Elves, Amazons,Headless,Lanks,Geminoids, and Cyclops may be found with
any sort of shields and armor. Cyanoids physically could but they find armor too
confining and itchy.

Sunday, August 3, 2025

Is old school play ready to move beyond 1d20?

A plain and simple question. Is old school play ready to move beyond 1d20? I'm seeing plenty of new games embracing alternate dice mechanics again while the games are otherwise very D&D adjacent. All actually old old-timers should be familiar with a few now classic competitors to the throne where we rolled 3d6 under a difficulty rating or 1d100 to get within a % chance. But they were only "slightly" D&D adjacent. Like The Fantasy Trip and Rune Quest. (please not very subtle sarcasm there) Has the corporate beast actually left the gate open to "allow" gamers to enjoy a wider range of dice mechanics in games that sell and will be played widely?

Friday, May 9, 2025

The Rule of 2

In my games I've been using the rule of 2. The rule of 2 is easy. If you are better at 2 things over your oppoent or 2 steps better you get +2 to your rolls. If you are worese at 2 things compared to your opponent you are -2 to yuor rolls. I keep track of quality of manufacture and materials used in equipment. So if your weapon is 2 steps better than an opponent's armor you are +2 to hit. Two or more friends helping you out in a task? +2 to yuor roll! Ability contest between two characters? I use a 1d20 roll vs opponents ability score. As we alreayd track differences in ability scores in the score itself and ability modifiers to rolls that diffwerence isn't factore into the rule of 2 but levels are. So if you are 2 levels or higher than an challenger yuo are +2 to your rolls and they are -2 to their roll. Oh is this saving throw 10th level but you are only 5th level. That's -2 to your saving throw roll! (oh no) Oh and only ever apply the rule of 2 in two factors at most keeping all modifiers the rule of 2 could apply to form -4 to +4. I like fiddly details but I also recognize not all players can track a lot of details and apply the math quickly. So I keep the math out of the descriptions a lot of the time. why 2 instead of one? Becasue a shift of only 1 isn't really noticable on a turn per turn (or round by round) basis beyond what the rules are already keeping track of in most situations using a d20 roll. the rule Rule of 2 baby!

Monday, May 5, 2025

It was safe trust me.

That last post was an experiment. A fun one and despite it beign a little frightenign completely safe. It's a javasctipt scrput that embeds a basic transpiler. I've been a nohhyst and work-tools promammer for years and always expermineting to bringign more of that to my gaming. I have a simple dungeon geenrator I've been fiddling with on and off for sometime now that I'm explorign ways to share.

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