@8bitscript/atari8 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 +658 -0
- package/src/banks.8bs +124 -0
- package/src/console.8bs +85 -0
- package/src/index.8bs +150 -0
- package/src/input.8bs +178 -0
- package/src/joystick.8bs +147 -0
- package/src/keyboard.8bs +157 -0
- package/src/keys.8bs +116 -0
- package/src/pointer.8bs +39 -0
- package/src/pokey.8bs +225 -0
- package/src/random.8bs +59 -0
- package/src/screen.8bs +129 -0
- package/src/text.8bs +159 -0
package/src/joystick.8bs
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// @8bitscript/atari8/joystick — the control ports, one snapshot a frame.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./joystick"] in this package's package.json:
|
|
4
|
+
//
|
|
5
|
+
// import { joystick, Joystick } from "@8bitscript/atari8/joystick";
|
|
6
|
+
//
|
|
7
|
+
// joystick.scan();
|
|
8
|
+
// if (joystick.left(Joystick.PORT_1)) { ... }
|
|
9
|
+
//
|
|
10
|
+
// Hardware-level, Atari-only surface, the layer a portable input capability
|
|
11
|
+
// will sit on — the same shape as @8bitscript/c64/joystick, deliberately:
|
|
12
|
+
// `scan()` once a frame into a snapshot, then `up`/`down`/`left`/`right`/
|
|
13
|
+
// `fire` answering from it, and the same `Joystick.UP`/`DOWN`/`LEFT`/
|
|
14
|
+
// `RIGHT`/`FIRE` bit values. That is not a coincidence to be tidied away
|
|
15
|
+
// later: the Atari's own masks in the SDK's `atari.h` (`JOY_UP_MASK` $01,
|
|
16
|
+
// `JOY_DOWN_MASK` $02, `JOY_LEFT_MASK` $04, `JOY_RIGHT_MASK` $08,
|
|
17
|
+
// `JOY_BTN_1_MASK` $10) are bit for bit the C64's, so the two machines'
|
|
18
|
+
// layers agree without either one pretending.
|
|
19
|
+
//
|
|
20
|
+
// ---- why this reads the OS shadows, not the hardware ----------------------
|
|
21
|
+
//
|
|
22
|
+
// A digital joystick is five switches to ground. The hardware is PIA's
|
|
23
|
+
// PORTA ($D300) — four direction bits per port, 0 where a switch is
|
|
24
|
+
// closed — and GTIA's TRIG0-3 ($D010-$D013), 0 while the button is down.
|
|
25
|
+
// A program could read those directly, and one that has cleared NMIEN's
|
|
26
|
+
// VBI bit and taken the machine over must. This layer does not, because on
|
|
27
|
+
// an 8BitScript build it does not have to: an Atari program built by this
|
|
28
|
+
// toolchain contains no `sei` and never disables the OS vertical blank
|
|
29
|
+
// (checked in the linked ELF; see ../AGENTS.md), so the OS's own VBI
|
|
30
|
+
// refreshes STICK0-3 ($0278-$027B) and STRIG0-3 ($0284-$0287) every single
|
|
31
|
+
// frame, already debounced and already split per port. Reading the shadow
|
|
32
|
+
// is one `lda abs,x` against PORTA's mask-and-shift, and it cannot race the
|
|
33
|
+
// OS the way the C64's matrix scan races the KERNAL.
|
|
34
|
+
//
|
|
35
|
+
// The one thing it costs: the shadows are only as fresh as the last VBI, so
|
|
36
|
+
// `scan()` belongs right after `waitFrame()`, which is where a frame's
|
|
37
|
+
// input reads belong anyway.
|
|
38
|
+
//
|
|
39
|
+
// ---- a mouse in a port IS the joystick ------------------------------------
|
|
40
|
+
//
|
|
41
|
+
// An ST or Amiga mouse and a Trak-Ball are not separate devices to the
|
|
42
|
+
// machine: they sit on a joystick port and signal movement as quadrature on
|
|
43
|
+
// the very direction bits this file reads. So with `--hardware mouse=st` in
|
|
44
|
+
// the port a program is scanning, `bits()` returns mouse motion and `up()`
|
|
45
|
+
// and friends go true while the mouse moves — that is the hardware being
|
|
46
|
+
// honest, not this layer being wrong. Seen here: test/layers-probe.8bs paints
|
|
47
|
+
// its border green when every input reads idle, and under `mouse=st` it goes
|
|
48
|
+
// red while `mouse=paddles` and `mouse=koala` stay green, because paddles and
|
|
49
|
+
// a Koala Pad are on POKEY's POT lines instead and leave the stick bits
|
|
50
|
+
// alone. A program that wants both puts them in different ports
|
|
51
|
+
// (`--hardware mouseport=2`), or reads the pointer and does not scan that
|
|
52
|
+
// port as a stick.
|
|
53
|
+
//
|
|
54
|
+
// ---- how many ports there are is a fact, not a guess -----------------------
|
|
55
|
+
//
|
|
56
|
+
// The 400 and 800 have four ports; every XL and XE has two, and on those
|
|
57
|
+
// machines PORTB is not a joystick port at all but the memory-control
|
|
58
|
+
// register (see ./banks.8bs). So the port count is a property of the
|
|
59
|
+
// *model* the build was made for — `input.joysticks` on the fact sheet,
|
|
60
|
+
// which the catalog's `model` option sets — and `scan()` reads exactly that
|
|
61
|
+
// many. `#fact(input.joysticks)` is a compile-time constant, so on an
|
|
62
|
+
// 800XL build the loop is two iterations and ports 3 and 4 cost nothing;
|
|
63
|
+
// on an 800 build it is four. Asking for a port the machine does not have
|
|
64
|
+
// answers "nothing pushed" rather than reading PORTB and calling the
|
|
65
|
+
// memory-control bits a joystick.
|
|
66
|
+
import { attract } from "./index.8bs";
|
|
67
|
+
|
|
68
|
+
// The OS's per-port shadows, refreshed by its VBI: four directions at
|
|
69
|
+
// STICK0 and one button at STRIG0, one byte each, consecutive.
|
|
70
|
+
const STICK0: usmallint = 0x0278;
|
|
71
|
+
const STRIG0: usmallint = 0x0284;
|
|
72
|
+
|
|
73
|
+
// The last scan, one byte per port, `Joystick.*` bits, 1 = pushed — the
|
|
74
|
+
// hardware's active-low sense inverted once, here, so nothing above has to
|
|
75
|
+
// think about it. All zero until the first scan.
|
|
76
|
+
let state: array<utinyint, 4>;
|
|
77
|
+
|
|
78
|
+
export namespace Joystick {
|
|
79
|
+
const PORT_1: utinyint = 0;
|
|
80
|
+
const PORT_2: utinyint = 1;
|
|
81
|
+
// Only on a 400 or an 800; `Joystick.PORTS` says whether this build has
|
|
82
|
+
// them, and `scan()` leaves them at zero when it does not.
|
|
83
|
+
const PORT_3: utinyint = 2;
|
|
84
|
+
const PORT_4: utinyint = 3;
|
|
85
|
+
|
|
86
|
+
const UP: utinyint = 1;
|
|
87
|
+
const DOWN: utinyint = 2;
|
|
88
|
+
const LEFT: utinyint = 4;
|
|
89
|
+
const RIGHT: utinyint = 8;
|
|
90
|
+
const FIRE: utinyint = 16;
|
|
91
|
+
|
|
92
|
+
// What this build's model has: 4 on a 400 or 800, 2 on every XL/XE.
|
|
93
|
+
// The same number as `Input.JOYSTICKS` from @8bitscript/system — the
|
|
94
|
+
// fact sheet is the one source, this is it spelled without the import.
|
|
95
|
+
const PORTS: utinyint = #fact(input.joysticks);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export namespace joystick {
|
|
99
|
+
// Read every port this machine has into the snapshot. Once a frame,
|
|
100
|
+
// right after waitFrame().
|
|
101
|
+
//
|
|
102
|
+
// It also zeroes ATRACT ($4D), and that is not a side errand. The OS
|
|
103
|
+
// counts that byte up in its VBI and, after roughly nine minutes with
|
|
104
|
+
// no key pressed, starts cycling the playfield colours to save the
|
|
105
|
+
// phosphor — which a keyboard game never sees, because the OS clears
|
|
106
|
+
// ATRACT on a keypress, and a joystick-only game always does, because
|
|
107
|
+
// a joystick is not a key. Left alone it turns a working game's screen
|
|
108
|
+
// into slowly rolling colours partway through a long session. Three
|
|
109
|
+
// bytes here is the whole fix, and this is the one function a
|
|
110
|
+
// joystick-only game is certain to call every frame.
|
|
111
|
+
function scan(): void {
|
|
112
|
+
for (let port: utinyint = 0; port < Joystick.PORTS; port++) {
|
|
113
|
+
let pushed: utinyint = (memory.read(STICK0 + port) ^ 0x0F) & 0x0F;
|
|
114
|
+
// TRIG is 0 while the button is down.
|
|
115
|
+
if (memory.read(STRIG0 + port) == 0) {
|
|
116
|
+
pushed = pushed + Joystick.FIRE;
|
|
117
|
+
}
|
|
118
|
+
state[port] = pushed;
|
|
119
|
+
}
|
|
120
|
+
attract = 0;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// The five switches of one port as `Joystick.*` bits, 1 = pushed.
|
|
124
|
+
function bits(port: utinyint): utinyint {
|
|
125
|
+
return state[port];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function up(port: utinyint): bool {
|
|
129
|
+
return (state[port] & Joystick.UP) != 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function down(port: utinyint): bool {
|
|
133
|
+
return (state[port] & Joystick.DOWN) != 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function left(port: utinyint): bool {
|
|
137
|
+
return (state[port] & Joystick.LEFT) != 0;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function right(port: utinyint): bool {
|
|
141
|
+
return (state[port] & Joystick.RIGHT) != 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function fire(port: utinyint): bool {
|
|
145
|
+
return (state[port] & Joystick.FIRE) != 0;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/src/keyboard.8bs
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// @8bitscript/atari8/keyboard — the keyboard, one snapshot a frame.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./keyboard"] in this package's package.json:
|
|
4
|
+
//
|
|
5
|
+
// import { keyboard } from "@8bitscript/atari8/keyboard";
|
|
6
|
+
// import { Key } from "@8bitscript/atari8/keys";
|
|
7
|
+
//
|
|
8
|
+
// keyboard.scan();
|
|
9
|
+
// if (keyboard.pressed(Key.SPACE)) { ... }
|
|
10
|
+
//
|
|
11
|
+
// Hardware-level, Atari-only surface, the same shape as
|
|
12
|
+
// @8bitscript/c64/keyboard and @8bitscript/pet/keyboard — `scan()` once a
|
|
13
|
+
// frame, then questions answered from the snapshot. What is behind it is
|
|
14
|
+
// not the same shape at all, and the difference is the whole point of this
|
|
15
|
+
// file, so read the next paragraph before writing a game around it.
|
|
16
|
+
//
|
|
17
|
+
// ---- this machine cannot tell you which keys are down ----------------------
|
|
18
|
+
//
|
|
19
|
+
// The C64 and PET have a matrix a program drives itself: select a column,
|
|
20
|
+
// read eight rows, and after eight passes you know the state of every key at
|
|
21
|
+
// once. The Atari has no matrix a program can reach. POKEY scans the
|
|
22
|
+
// keyboard in hardware and reports *one* thing — the code of the last key
|
|
23
|
+
// it saw, in KBCODE ($D209), with an IRQ to say it changed — plus one bit in
|
|
24
|
+
// SKSTAT saying whether a key, that key, is still held. So:
|
|
25
|
+
//
|
|
26
|
+
// - "is SPACE down" is answerable: the last key was SPACE and a key is
|
|
27
|
+
// still held. `pressed()` is exactly that, and it is honest.
|
|
28
|
+
// - "are UP and FIRE both down" is *not* answerable here. Two keys at once
|
|
29
|
+
// is what the joystick is for on this machine (./joystick.8bs), and it
|
|
30
|
+
// is why Atari games put movement on a stick and reserve the keyboard
|
|
31
|
+
// for one-at-a-time choices. A portable input capability must not
|
|
32
|
+
// promise chords on this target.
|
|
33
|
+
// - a key held down repeats: the OS's VBI auto-repeats it into CH after a
|
|
34
|
+
// delay, so `key()` fires again. `pressed()` does not care, but a menu
|
|
35
|
+
// that steps on every `key()` will step twice.
|
|
36
|
+
//
|
|
37
|
+
// ---- why this reads the OS shadow, not KBCODE ------------------------------
|
|
38
|
+
//
|
|
39
|
+
// The same reasoning as ./joystick.8bs. POKEY's keyboard IRQ is live under an
|
|
40
|
+
// 8BitScript build (no `sei` is emitted; see ../AGENTS.md), and the OS's
|
|
41
|
+
// handler is what debounces the key, applies SHIFT and CTRL, handles the
|
|
42
|
+
// auto-repeat and the CAPS lock, and leaves the result in CH ($02FC), $FF
|
|
43
|
+
// for "nothing new". Reading KBCODE instead means racing that handler for a
|
|
44
|
+
// register it is already consuming, and re-implementing debounce. So
|
|
45
|
+
// `scan()` takes CH and hands it back to the OS as $FF — the documented way
|
|
46
|
+
// a program consumes a key — and only the "still held" bit comes from the
|
|
47
|
+
// hardware, because the OS keeps no shadow of it.
|
|
48
|
+
//
|
|
49
|
+
// A program that has taken the machine over (NMIEN's VBI bit cleared) has no
|
|
50
|
+
// CH being filled and must read KBCODE itself; `keyboard.raw()` is that
|
|
51
|
+
// register, for exactly that case.
|
|
52
|
+
import { chShadow, skstat, kbcode } from "./index.8bs";
|
|
53
|
+
|
|
54
|
+
// The last code `scan()` took from CH, modifiers included, or `Key.NONE`.
|
|
55
|
+
let last: utinyint = 0xFF;
|
|
56
|
+
// Whether a key was still held at the last scan.
|
|
57
|
+
let held: bool = false;
|
|
58
|
+
// Whether SHIFT was down at the last scan, from SKSTAT rather than from the
|
|
59
|
+
// code's bit 6 — so it is true while SHIFT is held on its own.
|
|
60
|
+
let shifted: bool = false;
|
|
61
|
+
|
|
62
|
+
// SKSTAT's keyboard bits are active low: the bit is 0 while the thing is
|
|
63
|
+
// true. (Atari Hardware Manual; the SDK's _pokey.h names them
|
|
64
|
+
// SKSTAT_LASTKEY_PRESSED and SKSTAT_SHIFTKEY_PRESSED without the polarity.
|
|
65
|
+
// Checked here under atari800 with nothing pressed — both bits read 1 — by
|
|
66
|
+
// test/layers-probe.8bs.)
|
|
67
|
+
const SK_KEY_HELD: utinyint = 0x04;
|
|
68
|
+
const SK_SHIFT_HELD: utinyint = 0x08;
|
|
69
|
+
|
|
70
|
+
export namespace keyboard {
|
|
71
|
+
// Take whatever the OS has, once a frame, right after waitFrame().
|
|
72
|
+
//
|
|
73
|
+
// A key is consumed here: CH goes back to $FF so the next press is seen
|
|
74
|
+
// as new. That means exactly one `scan()` per frame — a second call in
|
|
75
|
+
// the same frame finds nothing and clears the answer.
|
|
76
|
+
function scan(): void {
|
|
77
|
+
let code: utinyint = chShadow;
|
|
78
|
+
if (code != 0xFF) {
|
|
79
|
+
last = code;
|
|
80
|
+
chShadow = 0xFF;
|
|
81
|
+
} else {
|
|
82
|
+
// No new key. Keep the old code only while it is still held, so
|
|
83
|
+
// `pressed()` follows the key down and up rather than latching
|
|
84
|
+
// the last key pressed forever.
|
|
85
|
+
let status: utinyint = skstat;
|
|
86
|
+
if ((status & SK_KEY_HELD) != 0) {
|
|
87
|
+
last = 0xFF;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
let status2: utinyint = skstat;
|
|
91
|
+
held = (status2 & SK_KEY_HELD) == 0;
|
|
92
|
+
shifted = (status2 & SK_SHIFT_HELD) == 0;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Is this key the one being pressed? `key` is a `Key.*` code; SHIFT and
|
|
96
|
+
// CTRL are masked off both sides, so `pressed(Key.A)` is true for a
|
|
97
|
+
// shifted A as well. To require a modifier, compare `code()` against
|
|
98
|
+
// `Key.A + Key.SHIFT` instead.
|
|
99
|
+
//
|
|
100
|
+
// The `Key.NONE` guard is not belt-and-braces: `Key.NONE` is $FF and the
|
|
101
|
+
// mask is $3F, so masking an idle keyboard gives $3F — which is exactly
|
|
102
|
+
// `Key.A`. Without this line an untouched machine reports A held down
|
|
103
|
+
// forever, and it looks perfectly correct in source. test/layers-probe.8bs
|
|
104
|
+
// checks this one case in its idle reading for that reason.
|
|
105
|
+
function pressed(key: utinyint): bool {
|
|
106
|
+
if (last == 0xFF) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return (last & 0x3F) == (key & 0x3F);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// The last key's code with its modifiers masked off, or `Key.NONE`.
|
|
113
|
+
function key(): utinyint {
|
|
114
|
+
if (last == 0xFF) {
|
|
115
|
+
return 0xFF;
|
|
116
|
+
}
|
|
117
|
+
return last & 0x3F;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The last key's code exactly as it arrived, `Key.SHIFT` and `Key.CTRL`
|
|
121
|
+
// still on it, or `Key.NONE`.
|
|
122
|
+
function code(): utinyint {
|
|
123
|
+
return last;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Is a key — the one `key()` names — still being held down?
|
|
127
|
+
function down(): bool {
|
|
128
|
+
return held;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Is SHIFT held, whether or not any other key is? This is SKSTAT's own
|
|
132
|
+
// bit, so it is true for SHIFT alone, which the key code cannot say.
|
|
133
|
+
function shift(): bool {
|
|
134
|
+
return shifted;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Was CTRL held when the last key arrived? CTRL alone cannot be read on
|
|
138
|
+
// this machine at all — there is no bit for it — so this is the code's
|
|
139
|
+
// modifier and nothing more.
|
|
140
|
+
function control(): bool {
|
|
141
|
+
return (last & 0x80) != 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Forget the last key, without waiting for a scan: a menu that has acted
|
|
145
|
+
// on a choice and does not want it acted on twice.
|
|
146
|
+
function clear(): void {
|
|
147
|
+
last = 0xFF;
|
|
148
|
+
chShadow = 0xFF;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// POKEY's own KBCODE, unfiltered and undebounced — for a program that
|
|
152
|
+
// has cleared NMIEN's VBI bit and so has no OS filling CH. Nothing else
|
|
153
|
+
// in this file reads it.
|
|
154
|
+
function raw(): utinyint {
|
|
155
|
+
return kbcode;
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/keys.8bs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// @8bitscript/atari8/keys — the keyboard's codes, key by key.
|
|
2
|
+
//
|
|
3
|
+
// Named by "8bitscript".exports["./keys"] in this package's package.json, for
|
|
4
|
+
//
|
|
5
|
+
// import { keyboard } from "@8bitscript/atari8/keyboard";
|
|
6
|
+
// import { Key } from "@8bitscript/atari8/keys";
|
|
7
|
+
//
|
|
8
|
+
// if (keyboard.pressed(Key.SPACE)) { ... }
|
|
9
|
+
//
|
|
10
|
+
// Each value is the code POKEY leaves in KBCODE ($D209) and the OS leaves in
|
|
11
|
+
// CH ($02FC) — not a row and column the way the C64's and PET's tables are,
|
|
12
|
+
// because on this machine a program never scans a matrix. POKEY scans it in
|
|
13
|
+
// hardware and hands over one code (see ./keyboard.8bs for what that costs).
|
|
14
|
+
//
|
|
15
|
+
// The numbering looks arbitrary because it is: it is the order POKEY's own
|
|
16
|
+
// scan counter reaches each key, so `L` is 0 and `A` is $3F with nothing in
|
|
17
|
+
// between to reason about. Bits 6 and 7 are the SHIFT and CTRL modifiers
|
|
18
|
+
// OR-ed onto the key's own six bits, which is why every code here is under
|
|
19
|
+
// $40 and why `Key.SHIFT` and `Key.CTRL` are masks rather than keys.
|
|
20
|
+
//
|
|
21
|
+
// A key that also exists on the C64 and PET has the same name here —
|
|
22
|
+
// letters, `DIGIT_n`, SPACE, RETURN, DELETE, ESC, the punctuation — so a
|
|
23
|
+
// program that sticks to those names reads the same on all three machines'
|
|
24
|
+
// keyboard layers.
|
|
25
|
+
//
|
|
26
|
+
// Source: `KEY_*` in the LLVM-MOS SDK's own
|
|
27
|
+
// `mos-platform/atari8-common/include/atari.h`, which is also mirrored as
|
|
28
|
+
// assembler equates in `asminc/atari.inc`; this file is generated from that
|
|
29
|
+
// header, not transcribed, and packages/atari8/test/keys.test.mjs checks
|
|
30
|
+
// every value against it whenever the SDK is present. (../AGENTS.md listed
|
|
31
|
+
// this table as "to verify" before the header was found; it is now sourced.)
|
|
32
|
+
|
|
33
|
+
export namespace Key {
|
|
34
|
+
// Nothing pressed: what CH holds when the OS has no key for the program,
|
|
35
|
+
// and what `keyboard.key()` answers.
|
|
36
|
+
const NONE: utinyint = 0xFF;
|
|
37
|
+
|
|
38
|
+
// Letters.
|
|
39
|
+
const A: utinyint = 0x3F;
|
|
40
|
+
const B: utinyint = 0x15;
|
|
41
|
+
const C: utinyint = 0x12;
|
|
42
|
+
const D: utinyint = 0x3A;
|
|
43
|
+
const E: utinyint = 0x2A;
|
|
44
|
+
const F: utinyint = 0x38;
|
|
45
|
+
const G: utinyint = 0x3D;
|
|
46
|
+
const H: utinyint = 0x39;
|
|
47
|
+
const I: utinyint = 0x0D;
|
|
48
|
+
const J: utinyint = 0x01;
|
|
49
|
+
const K: utinyint = 0x05;
|
|
50
|
+
const L: utinyint = 0x00;
|
|
51
|
+
const M: utinyint = 0x25;
|
|
52
|
+
const N: utinyint = 0x23;
|
|
53
|
+
const O: utinyint = 0x08;
|
|
54
|
+
const P: utinyint = 0x0A;
|
|
55
|
+
const Q: utinyint = 0x2F;
|
|
56
|
+
const R: utinyint = 0x28;
|
|
57
|
+
const S: utinyint = 0x3E;
|
|
58
|
+
const T: utinyint = 0x2D;
|
|
59
|
+
const U: utinyint = 0x0B;
|
|
60
|
+
const V: utinyint = 0x10;
|
|
61
|
+
const W: utinyint = 0x2E;
|
|
62
|
+
const X: utinyint = 0x16;
|
|
63
|
+
const Y: utinyint = 0x2B;
|
|
64
|
+
const Z: utinyint = 0x17;
|
|
65
|
+
|
|
66
|
+
// Digits, named DIGIT_n so a name never starts with a number.
|
|
67
|
+
const DIGIT_0: utinyint = 0x32;
|
|
68
|
+
const DIGIT_1: utinyint = 0x1F;
|
|
69
|
+
const DIGIT_2: utinyint = 0x1E;
|
|
70
|
+
const DIGIT_3: utinyint = 0x1A;
|
|
71
|
+
const DIGIT_4: utinyint = 0x18;
|
|
72
|
+
const DIGIT_5: utinyint = 0x1D;
|
|
73
|
+
const DIGIT_6: utinyint = 0x1B;
|
|
74
|
+
const DIGIT_7: utinyint = 0x33;
|
|
75
|
+
const DIGIT_8: utinyint = 0x35;
|
|
76
|
+
const DIGIT_9: utinyint = 0x30;
|
|
77
|
+
|
|
78
|
+
// Punctuation, unshifted. The shifted character on the same key is
|
|
79
|
+
// that key OR Key.SHIFT.
|
|
80
|
+
const COMMA: utinyint = 0x20;
|
|
81
|
+
const PERIOD: utinyint = 0x22;
|
|
82
|
+
const SLASH: utinyint = 0x26;
|
|
83
|
+
const SEMICOLON: utinyint = 0x02;
|
|
84
|
+
const PLUS: utinyint = 0x06; // '+', shifted '\\'
|
|
85
|
+
const ASTERISK: utinyint = 0x07; // '*'
|
|
86
|
+
const DASH: utinyint = 0x0E; // '-'
|
|
87
|
+
const EQUALS: utinyint = 0x0F; // '='
|
|
88
|
+
const LESSTHAN: utinyint = 0x36; // '<', on its own key
|
|
89
|
+
const GREATERTHAN: utinyint = 0x37; // '>', on its own key
|
|
90
|
+
|
|
91
|
+
// The keys that are not characters.
|
|
92
|
+
const ESC: utinyint = 0x1C;
|
|
93
|
+
const TAB: utinyint = 0x2C;
|
|
94
|
+
const SPACE: utinyint = 0x21;
|
|
95
|
+
const RETURN: utinyint = 0x0C;
|
|
96
|
+
const DELETE: utinyint = 0x34; // DELETE/BACK S
|
|
97
|
+
const CAPS: utinyint = 0x3C; // CAPS/LOWR
|
|
98
|
+
const INVERSE: utinyint = 0x27; // the Atari-logo key: inverse video
|
|
99
|
+
const HELP: utinyint = 0x11; // the 1200XL and later only
|
|
100
|
+
|
|
101
|
+
// Function keys: the 1200XL had four; no other model has any.
|
|
102
|
+
const F1: utinyint = 0x03;
|
|
103
|
+
const F2: utinyint = 0x04;
|
|
104
|
+
const F3: utinyint = 0x13;
|
|
105
|
+
const F4: utinyint = 0x14;
|
|
106
|
+
|
|
107
|
+
// Modifiers, as masks OR-ed onto a key code rather than keys of their
|
|
108
|
+
// own. Neither can be read alone through KBCODE: SHIFT on its own is
|
|
109
|
+
// SKSTAT's bit 3 (`keyboard.shift()`), and CTRL on its own cannot be
|
|
110
|
+
// read at all. There is no way to tell left SHIFT from right SHIFT.
|
|
111
|
+
const SHIFT: utinyint = 0x40;
|
|
112
|
+
const CTRL: utinyint = 0x80;
|
|
113
|
+
// The key itself, with both modifiers masked off — what
|
|
114
|
+
// `keyboard.key()` is compared against.
|
|
115
|
+
const CODE: utinyint = 0x3F;
|
|
116
|
+
}
|
package/src/pointer.8bs
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// @8bitscript/atari8/pointer — the Atari 8-bit behind @8bitscript/pointer.
|
|
2
|
+
//
|
|
3
|
+
// Every call here does nothing and `DRAWS` is false, and both halves are
|
|
4
|
+
// missing for the same reason: nobody has written the drivers.
|
|
5
|
+
//
|
|
6
|
+
// **The hardware could do it.** GTIA has four players and four missiles —
|
|
7
|
+
// movable objects a cursor fits in, though a player is 8 pixels wide and
|
|
8
|
+
// as tall as the display, so an arrow is a few bytes of a player's stripe
|
|
9
|
+
// rather than a 24x21 block like the VIC's sprite. And the catalog already
|
|
10
|
+
// offers a pointing device: `mouse` with `st`, `amiga` and `trak` values,
|
|
11
|
+
// each of which atari800 emulates on `-mouseport`.
|
|
12
|
+
//
|
|
13
|
+
// **Neither end is built.** This package has no players/missiles layer for
|
|
14
|
+
// the drawing, and @8bitscript/atari8/input reports no pointer because an
|
|
15
|
+
// ST or Amiga mouse on this machine is quadrature on a joystick port's
|
|
16
|
+
// direction bits, decoded in software at a high poll rate — a driver in
|
|
17
|
+
// its own right, listed as a gap in packages/input/AGENTS.md. An arrow
|
|
18
|
+
// wants the input half first: there is no point drawing a cursor with
|
|
19
|
+
// nothing to move it.
|
|
20
|
+
//
|
|
21
|
+
// It costs the machine nothing meanwhile: `DRAWS` is a const and every
|
|
22
|
+
// function is empty, so a program's `if (pointer.DRAWS)` folds away and
|
|
23
|
+
// LLVM deletes the calls.
|
|
24
|
+
export namespace pointer {
|
|
25
|
+
|
|
26
|
+
const DRAWS: bool = false;
|
|
27
|
+
|
|
28
|
+
function begin(): void {
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function setColor(color: utinyint): void {
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function update(): void {
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hide(): void {
|
|
38
|
+
}
|
|
39
|
+
}
|