@8bitscript/web 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +63 -0
- package/src/index.8bs +42 -0
- package/src/input.8bs +100 -0
- package/src/pointer.8bs +42 -0
- package/src/screen.8bs +107 -0
- package/src/text.8bs +105 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 8BitScript contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@8bitscript/web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Browser target support for 8BitScript: the hardware underneath the portable APIs, where the hardware is a canvas.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"8bitscript": {
|
|
7
|
+
"entry": "./src/index.8bs",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./screen": "./src/screen.8bs",
|
|
10
|
+
"./text": "./src/text.8bs",
|
|
11
|
+
"./input": "./src/input.8bs",
|
|
12
|
+
"./pointer": "./src/pointer.8bs"
|
|
13
|
+
},
|
|
14
|
+
"hardware": {
|
|
15
|
+
"facts": {
|
|
16
|
+
"video.columns": 40,
|
|
17
|
+
"video.rows": 25,
|
|
18
|
+
"video.cellWidth": 8,
|
|
19
|
+
"video.cellHeight": 8,
|
|
20
|
+
"video.palette": 16,
|
|
21
|
+
"video.cellColors": 2,
|
|
22
|
+
"video.colorPerCell": true,
|
|
23
|
+
"video.glyphs": 0,
|
|
24
|
+
"video.blockWidth": 0,
|
|
25
|
+
"video.blockHeight": 0,
|
|
26
|
+
"video.bitmap": false,
|
|
27
|
+
"video.layers": 1,
|
|
28
|
+
"video.scroll": false,
|
|
29
|
+
"video.sprites": 0,
|
|
30
|
+
"video.spritesPerLine": 0,
|
|
31
|
+
"video.spriteWidth": 0,
|
|
32
|
+
"video.spriteHeight": 0,
|
|
33
|
+
"video.spriteColors": 0,
|
|
34
|
+
"video.frameRate": 60,
|
|
35
|
+
"audio.voices": 0,
|
|
36
|
+
"audio.noise": false,
|
|
37
|
+
"audio.envelope": false,
|
|
38
|
+
"audio.filter": false,
|
|
39
|
+
"audio.pcm": false,
|
|
40
|
+
"audio.volume": false,
|
|
41
|
+
"audio.entropy": false,
|
|
42
|
+
"input.keyboard": true,
|
|
43
|
+
"input.joysticks": 0,
|
|
44
|
+
"input.pads": 0,
|
|
45
|
+
"input.mouse": false,
|
|
46
|
+
"input.paddles": false,
|
|
47
|
+
"storage.save": false,
|
|
48
|
+
"memory.ram": 57344,
|
|
49
|
+
"memory.banked": false,
|
|
50
|
+
"memory.bankedKib": 0,
|
|
51
|
+
"storage.kib": 0
|
|
52
|
+
},
|
|
53
|
+
"options": {},
|
|
54
|
+
"presets": {}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"files": [
|
|
58
|
+
"src"
|
|
59
|
+
],
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/index.8bs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @8bitscript/web — browser target support for 8BitScript: the "hardware"
|
|
2
|
+
// underneath.
|
|
3
|
+
//
|
|
4
|
+
// The module entry point named by the "8bitscript".entry field in this
|
|
5
|
+
// package's package.json, resolved and linked for:
|
|
6
|
+
//
|
|
7
|
+
// import { WebRegisters } from "@8bitscript/web";
|
|
8
|
+
//
|
|
9
|
+
// What a program usually wants sits one layer up, in this package's own
|
|
10
|
+
// implementations of the portable capability packages — `./src/screen.8bs`
|
|
11
|
+
// behind @8bitscript/screen and `./src/text.8bs` behind @8bitscript/text,
|
|
12
|
+
// named by the "8bitscript".exports map in package.json — which are built
|
|
13
|
+
// on what this file exports and on nothing else.
|
|
14
|
+
//
|
|
15
|
+
// There is no $900F or $D020 here: the web target has no fixed hardware, so
|
|
16
|
+
// "the screen" is a canvas the host paints from wasm's own linear memory
|
|
17
|
+
// (the `--initialMemory 1` page every web build reserves, per
|
|
18
|
+
// @8bitscript/backend-web). This package's hardware surface is therefore an
|
|
19
|
+
// AGREEMENT rather than a register map: a handful of fixed byte offsets in
|
|
20
|
+
// that memory — two for the border and background colours, a virtual
|
|
21
|
+
// character screen for everything `text` pokes, and one byte of input the
|
|
22
|
+
// page writes and `@8bitscript/web/input` reads — which the host
|
|
23
|
+
// (packages/cli's browser runtime, packages/cli/src/web-runtime.mjs) paints
|
|
24
|
+
// from once per display refresh. It is the same deferred-apply contract a
|
|
25
|
+
// VIC-II register has, aimed at a buffer instead.
|
|
26
|
+
//
|
|
27
|
+
// The character screen is 1000 cells, 40 wide — the C64's own screen shape
|
|
28
|
+
// (see @8bitscript/c64) — picked as a safe superset of the VIC-20's smaller
|
|
29
|
+
// 506-cell one so the same program's cell numbers never run off the end of
|
|
30
|
+
// this buffer either. Two side-by-side regions, not interleaved, so a
|
|
31
|
+
// caller that only ever touches a handful of cells leaves most of both
|
|
32
|
+
// untouched.
|
|
33
|
+
export namespace WebRegisters {
|
|
34
|
+
const BORDER_OFFSET: usmallint = 0;
|
|
35
|
+
const BACKGROUND_OFFSET: usmallint = 1;
|
|
36
|
+
const CHAR_BASE: usmallint = 2;
|
|
37
|
+
const COLOR_BASE: usmallint = 1002;
|
|
38
|
+
// Directions, confirm and cancel as the same Edge bits every input
|
|
39
|
+
// layer uses. The page writes this; poll() in ./input.8bs reads it.
|
|
40
|
+
// First byte after the 1000 colour cells at COLOR_BASE.
|
|
41
|
+
const INPUT_OFFSET: usmallint = 2002;
|
|
42
|
+
}
|
package/src/input.8bs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// @8bitscript/web/input — the browser runtime behind @8bitscript/input.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./input"] in this package's package.json,
|
|
4
|
+
// and by "8bitscript".entry.web in @8bitscript/input's, so a portable
|
|
5
|
+
// program writes
|
|
6
|
+
//
|
|
7
|
+
// import { input } from "@8bitscript/input";
|
|
8
|
+
//
|
|
9
|
+
// and gets this file in a browser.
|
|
10
|
+
//
|
|
11
|
+
// ---- the page writes, the program reads ----------------------------------
|
|
12
|
+
//
|
|
13
|
+
// The program runs in a worker and cannot see DOM events. The page listens
|
|
14
|
+
// for keys and stores a snapshot at WebRegisters.INPUT_OFFSET in the
|
|
15
|
+
// shared wasm memory; poll() reads that byte once a frame, the same shape
|
|
16
|
+
// every other machine's layer uses — a held mask, last frame's mask, and
|
|
17
|
+
// the bits that began this frame. No new wasm import: the agreement page
|
|
18
|
+
// is the port.
|
|
19
|
+
//
|
|
20
|
+
// - **left / right / up / down** — ArrowLeft / ArrowRight / ArrowUp /
|
|
21
|
+
// ArrowDown.
|
|
22
|
+
// - **confirm** — Enter.
|
|
23
|
+
// - **cancel** — Escape.
|
|
24
|
+
//
|
|
25
|
+
// There is still no pointer. `pointer()` is a constant false; a mouse
|
|
26
|
+
// belongs in the same snapshot when someone writes it, and
|
|
27
|
+
// `input.mouse` stays false until then.
|
|
28
|
+
//
|
|
29
|
+
// ---- edges, not levels ---------------------------------------------------
|
|
30
|
+
//
|
|
31
|
+
// Every answer is **edge-triggered**: true on the one frame the press
|
|
32
|
+
// begins, false while it is held. `poll()` must be called exactly once a
|
|
33
|
+
// frame, right after waitFrame().
|
|
34
|
+
import { WebRegisters } from "./index.8bs";
|
|
35
|
+
|
|
36
|
+
namespace Edge {
|
|
37
|
+
const LEFT: utinyint = 1;
|
|
38
|
+
const RIGHT: utinyint = 2;
|
|
39
|
+
const UP: utinyint = 4;
|
|
40
|
+
const DOWN: utinyint = 8;
|
|
41
|
+
const CONFIRM: utinyint = 16;
|
|
42
|
+
const CANCEL: utinyint = 32;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let held: utinyint = 0;
|
|
46
|
+
let before: utinyint = 0;
|
|
47
|
+
let began: utinyint = 0;
|
|
48
|
+
|
|
49
|
+
export namespace input {
|
|
50
|
+
|
|
51
|
+
// Nothing to set up: the page owns the snapshot byte. Here so a
|
|
52
|
+
// program written for nine machines calls the same thing on all of
|
|
53
|
+
// them.
|
|
54
|
+
function begin(): void {
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Read the page's snapshot and work out what began this frame. Once
|
|
58
|
+
// a frame, right after waitFrame().
|
|
59
|
+
function poll(): void {
|
|
60
|
+
before = held;
|
|
61
|
+
held = memory.read(WebRegisters.INPUT_OFFSET);
|
|
62
|
+
began = held & (before ^ 0xFF);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function left(): bool {
|
|
66
|
+
return (began & Edge.LEFT) != 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function right(): bool {
|
|
70
|
+
return (began & Edge.RIGHT) != 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function up(): bool {
|
|
74
|
+
return (began & Edge.UP) != 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function down(): bool {
|
|
78
|
+
return (began & Edge.DOWN) != 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function confirm(): bool {
|
|
82
|
+
return (began & Edge.CONFIRM) != 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function cancel(): bool {
|
|
86
|
+
return (began & Edge.CANCEL) != 0;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pointer(): bool {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function pointerCell(): usmallint {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function pointerButton(): bool {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/pointer.8bs
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// @8bitscript/web/pointer — the web runtime behind @8bitscript/pointer,
|
|
2
|
+
// and the one target where the pointer the user already has is the
|
|
3
|
+
// problem rather than the solution.
|
|
4
|
+
//
|
|
5
|
+
// Every call here does nothing and `DRAWS` is false: the runtime now
|
|
6
|
+
// delivers arrow keys through @8bitscript/web/input, but still no pointer.
|
|
7
|
+
// A mouse belongs in the same shared snapshot as those keys, and until
|
|
8
|
+
// that lands there is nothing to draw.
|
|
9
|
+
//
|
|
10
|
+
// This target has a decision the other eight do not, and it is worth
|
|
11
|
+
// writing down before anyone implements it: **the user is already looking
|
|
12
|
+
// at a real mouse cursor** — the browser's — sitting over the canvas. So
|
|
13
|
+
// "draw a pointer" here means one of two quite different things. Either
|
|
14
|
+
// the runtime hides the browser's cursor over the canvas and draws the
|
|
15
|
+
// machine's own arrow, which is what makes a web build look like the
|
|
16
|
+
// machine it is emulating and what every other target does; or it leaves
|
|
17
|
+
// the browser's cursor alone and `DRAWS` stays false forever, with
|
|
18
|
+
// `input.pointerCell()` reporting where that cursor is. The second is less
|
|
19
|
+
// work and the first is more honest to what this target is for, and every
|
|
20
|
+
// "hardware" fact of this machine is a decision recorded in the runtime's
|
|
21
|
+
// source (see packages/web/AGENTS.md), so this one belongs written down
|
|
22
|
+
// there when it is made.
|
|
23
|
+
//
|
|
24
|
+
// It costs the target nothing today: `DRAWS` is a const and every function
|
|
25
|
+
// is empty, so a program's `if (pointer.DRAWS)` folds away and the backend
|
|
26
|
+
// drops the calls.
|
|
27
|
+
export namespace pointer {
|
|
28
|
+
|
|
29
|
+
const DRAWS: bool = false;
|
|
30
|
+
|
|
31
|
+
function begin(): void {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function setColor(color: utinyint): void {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function update(): void {
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hide(): void {
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/screen.8bs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// @8bitscript/web/screen — the browser target's implementation of
|
|
2
|
+
// @8bitscript/screen.
|
|
3
|
+
//
|
|
4
|
+
// Named by "8bitscript".exports["./screen"] in this package's package.json,
|
|
5
|
+
// and what
|
|
6
|
+
//
|
|
7
|
+
// import { screen, BorderColor, BackgroundColor } from "@8bitscript/screen";
|
|
8
|
+
//
|
|
9
|
+
// resolves to when the build is for the web: @8bitscript/screen's entry is
|
|
10
|
+
// keyed by machine and delegates here. Built on the memory-layout agreement
|
|
11
|
+
// @8bitscript/web exports, one layer up from it. Every machine's screen.8bs
|
|
12
|
+
// exports this same surface, which is what lets a program import it from
|
|
13
|
+
// @8bitscript/screen and never name the hardware — or, here, the host.
|
|
14
|
+
import { WebRegisters } from "./index.8bs";
|
|
15
|
+
|
|
16
|
+
// ---- the screen: border and background --------------------------------------
|
|
17
|
+
//
|
|
18
|
+
// Sixteen colours, masked the way the C64's registers are: the web target
|
|
19
|
+
// has no palette size of its own to be faithful to, so it borrows the C64's
|
|
20
|
+
// (see COLORS in packages/cli/src/web-runtime.mjs), which keeps colour
|
|
21
|
+
// numbers meaning the same thing across every 8BitScript example. The host
|
|
22
|
+
// reads the two bytes once per painted frame, so the picture changes at
|
|
23
|
+
// the next paint.
|
|
24
|
+
export namespace screen {
|
|
25
|
+
function setColors(border: u8, background: u8): void {
|
|
26
|
+
memory.write(WebRegisters.BORDER_OFFSET, border & 15);
|
|
27
|
+
memory.write(WebRegisters.BACKGROUND_OFFSET, background & 15);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// `blank(border, background)`: every cell blank and both colours set —
|
|
31
|
+
// black when left off, or `BorderColor.KEEP` / `BackgroundColor.KEEP`
|
|
32
|
+
// to leave one as it is. `setBorder` and `setBackground` are one
|
|
33
|
+
// colour each, `setColors` the pair; `text.setColor` owns what the
|
|
34
|
+
// next print looks like.
|
|
35
|
+
function blank(border: utinyint = BorderColor.BLACK, background: utinyint = BackgroundColor.BLACK): void {
|
|
36
|
+
if (border != BorderColor.KEEP) {
|
|
37
|
+
screen.setBorder(border);
|
|
38
|
+
}
|
|
39
|
+
if (background != BackgroundColor.KEEP) {
|
|
40
|
+
screen.setBackground(background);
|
|
41
|
+
}
|
|
42
|
+
for (let cell: usmallint = 0; cell < 1000; cell++) {
|
|
43
|
+
memory.write(WebRegisters.CHAR_BASE + cell, 32);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setBackground(background: u8): void {
|
|
48
|
+
memory.write(WebRegisters.BACKGROUND_OFFSET, background & 15);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function setBorder(border: u8): void {
|
|
52
|
+
memory.write(WebRegisters.BORDER_OFFSET, border & 15);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---- colour names ----------------------------------------------------------
|
|
57
|
+
//
|
|
58
|
+
// The same eight names every machine's screen.8bs exports — Black, White,
|
|
59
|
+
// Red, Cyan, Purple, Green, Blue, Yellow — so a program can write
|
|
60
|
+
// `screen.setColors(BorderColor.BLUE, BackgroundColor.BLACK)` through
|
|
61
|
+
// @8bitscript/screen and get blue on every target. The values are the C64's
|
|
62
|
+
// sixteen colours in the C64's numbering, the palette this target borrows
|
|
63
|
+
// (see setColors above).
|
|
64
|
+
|
|
65
|
+
export namespace BorderColor {
|
|
66
|
+
const BLACK: utinyint = 0;
|
|
67
|
+
const WHITE: utinyint = 1;
|
|
68
|
+
const RED: utinyint = 2;
|
|
69
|
+
const CYAN: utinyint = 3;
|
|
70
|
+
const PURPLE: utinyint = 4;
|
|
71
|
+
const GREEN: utinyint = 5;
|
|
72
|
+
const BLUE: utinyint = 6;
|
|
73
|
+
const YELLOW: utinyint = 7;
|
|
74
|
+
const ORANGE: utinyint = 8;
|
|
75
|
+
const BROWN: utinyint = 9;
|
|
76
|
+
const LIGHT_RED: utinyint = 10;
|
|
77
|
+
const DARK_GREY: utinyint = 11;
|
|
78
|
+
const GREY: utinyint = 12;
|
|
79
|
+
const LIGHT_GREEN: utinyint = 13;
|
|
80
|
+
const LIGHT_BLUE: utinyint = 14;
|
|
81
|
+
const LIGHT_GREY: utinyint = 15;
|
|
82
|
+
// Not a colour: `blank(KEEP, ...)` leaves this one as it is. 255 is
|
|
83
|
+
// a value no register here takes.
|
|
84
|
+
const KEEP: utinyint = 255;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export namespace BackgroundColor {
|
|
88
|
+
const BLACK: utinyint = 0;
|
|
89
|
+
const WHITE: utinyint = 1;
|
|
90
|
+
const RED: utinyint = 2;
|
|
91
|
+
const CYAN: utinyint = 3;
|
|
92
|
+
const PURPLE: utinyint = 4;
|
|
93
|
+
const GREEN: utinyint = 5;
|
|
94
|
+
const BLUE: utinyint = 6;
|
|
95
|
+
const YELLOW: utinyint = 7;
|
|
96
|
+
const ORANGE: utinyint = 8;
|
|
97
|
+
const BROWN: utinyint = 9;
|
|
98
|
+
const LIGHT_RED: utinyint = 10;
|
|
99
|
+
const DARK_GREY: utinyint = 11;
|
|
100
|
+
const GREY: utinyint = 12;
|
|
101
|
+
const LIGHT_GREEN: utinyint = 13;
|
|
102
|
+
const LIGHT_BLUE: utinyint = 14;
|
|
103
|
+
const LIGHT_GREY: utinyint = 15;
|
|
104
|
+
// Not a colour: `blank(KEEP, ...)` leaves this one as it is. 255 is
|
|
105
|
+
// a value no register here takes.
|
|
106
|
+
const KEEP: utinyint = 255;
|
|
107
|
+
}
|
package/src/text.8bs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @8bitscript/web/text — the browser target's implementation of
|
|
2
|
+
// @8bitscript/text.
|
|
3
|
+
//
|
|
4
|
+
// Named by "8bitscript".exports["./text"] in this package's package.json,
|
|
5
|
+
// and what
|
|
6
|
+
//
|
|
7
|
+
// import { text } from "@8bitscript/text";
|
|
8
|
+
//
|
|
9
|
+
// resolves to when the build is for the web. Built on the memory-layout
|
|
10
|
+
// agreement @8bitscript/web exports. Every machine's text.8bs exports this
|
|
11
|
+
// same namespace — the same names, the same shapes, ASCII codes, and a cell
|
|
12
|
+
// 0 at the top-left corner inside the border — so a program written against
|
|
13
|
+
// it doesn't know or care that "screen memory" is a real 6502 address on
|
|
14
|
+
// most targets and a wasm-memory offset on this one.
|
|
15
|
+
import { WebRegisters } from "./index.8bs";
|
|
16
|
+
|
|
17
|
+
// Codes are ASCII, as they are everywhere (space, '0'-'9', 'A'-'Z' and a
|
|
18
|
+
// little punctuation, upper case only); this buffer holds them as they
|
|
19
|
+
// come, and the host (packages/cli/src/web-runtime.mjs) draws whatever
|
|
20
|
+
// isn't blank as the text it is, once per painted frame — there is no
|
|
21
|
+
// character ROM here to need screen codes for.
|
|
22
|
+
// ---- print: strings and number fields ------------------------------------
|
|
23
|
+
//
|
|
24
|
+
// `text.print(cell, s)` writes a string's characters into consecutive
|
|
25
|
+
// cells from `cell`, and `text.printNumber(cell, value, width)` writes
|
|
26
|
+
// `value` as exactly `width` decimal digits, zero-padded and right-aligned,
|
|
27
|
+
// so a field on a HUD never shifts columns. Both draw in the current colour —
|
|
28
|
+
// white until `text.setColor(TextColor.CYAN)` changes it, and that one
|
|
29
|
+
// call then colours everything printed after it, on the machines that have
|
|
30
|
+
// per-cell colour. They are also the two
|
|
31
|
+
// functions the compiler's template layout targets: `text.print(0,
|
|
32
|
+
// \`TICK ${ticks:1}\`)` is laid out at compile time into these same calls
|
|
33
|
+
// (see packages/compiler/src/ir).
|
|
34
|
+
//
|
|
35
|
+
// Under them: `place()` puts one character at one cell in the current
|
|
36
|
+
// colour, and is all a run of text costs per character.
|
|
37
|
+
// `text.putChar` and `text.putColor` stay the one-cell pokes a caller can
|
|
38
|
+
// build anything from.
|
|
39
|
+
let currentColor: utinyint = 1; // white, until text.setColor() says otherwise
|
|
40
|
+
let currentReverse: bool = false; // until text.setReverse() says otherwise
|
|
41
|
+
|
|
42
|
+
// Reverse rides in bit 7 of the colour byte: the host fills the cell with
|
|
43
|
+
// the foreground colour and punches the glyph out in the screen background.
|
|
44
|
+
// A reverse space is a solid block. The low four bits stay the colour.
|
|
45
|
+
function colorByte(): utinyint {
|
|
46
|
+
if (currentReverse) {
|
|
47
|
+
return currentColor + 128;
|
|
48
|
+
}
|
|
49
|
+
return currentColor;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function place(cell: usmallint, code: utinyint): void {
|
|
53
|
+
memory.write(WebRegisters.CHAR_BASE + cell, code);
|
|
54
|
+
memory.write(WebRegisters.COLOR_BASE + cell, colorByte());
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export namespace text {
|
|
58
|
+
const CELL_COUNT: usmallint = 1000; // 40 columns x 25 rows
|
|
59
|
+
const COLUMNS: utinyint = 40; // cells per row, so cell = y * text.COLUMNS + x
|
|
60
|
+
|
|
61
|
+
function putChar(cell: usmallint, code: utinyint): void {
|
|
62
|
+
memory.write(WebRegisters.CHAR_BASE + cell, code);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function putColor(cell: usmallint, color: utinyint): void {
|
|
66
|
+
memory.write(WebRegisters.COLOR_BASE + cell, color);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function setColor(color: utinyint): void {
|
|
70
|
+
currentColor = color;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function setReverse(on: bool): void {
|
|
74
|
+
currentReverse = on;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function print(cell: usmallint, s: string): void {
|
|
78
|
+
for (let i: utinyint = 0; i < s.length; i++) {
|
|
79
|
+
place(cell + i, s[i]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Least significant digit first, from the right-hand end of the field.
|
|
84
|
+
// Division, unlike on the 6502 machines: wasm has a divide instruction,
|
|
85
|
+
// and the subtraction they use to avoid one is more code here.
|
|
86
|
+
function printNumber(cell: usmallint, value: usmallint, width: utinyint): void {
|
|
87
|
+
for (let i: utinyint = width; i > 0; i--) {
|
|
88
|
+
place(cell + i - 1, 48 + value % 10); // 48 = '0'
|
|
89
|
+
value = value / 10;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// The colours `text.setColor()` takes: the eight names every machine
|
|
95
|
+
// shares, with this machine's colour-RAM values.
|
|
96
|
+
export namespace TextColor {
|
|
97
|
+
const BLACK: utinyint = 0;
|
|
98
|
+
const WHITE: utinyint = 1;
|
|
99
|
+
const RED: utinyint = 2;
|
|
100
|
+
const CYAN: utinyint = 3;
|
|
101
|
+
const PURPLE: utinyint = 4;
|
|
102
|
+
const GREEN: utinyint = 5;
|
|
103
|
+
const BLUE: utinyint = 6;
|
|
104
|
+
const YELLOW: utinyint = 7;
|
|
105
|
+
}
|