@moonbase-machines/vector 0.0.0-stage → 0.6.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/README.md +224 -2
- package/dist/moonbase-vector.js +4 -0
- package/dist/moonbase-vector.js.map +7 -0
- package/draw.js +117 -0
- package/font.js +61 -0
- package/index.js +9 -0
- package/inline.js +41 -0
- package/objects.js +109 -0
- package/package.json +16 -4
- package/pictures.js +67 -0
- package/svg.js +35 -0
- package/test/vector.test.js +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Wharton
|
|
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/README.md
CHANGED
|
@@ -1,3 +1,225 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @moonbase-machines/vector
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Mark Wharton's vector graphics engine for the Apple IIe, as it is in PART0 of Animal Rescue
|
|
4
|
+
and Nature Park Adventure, both of 1989. Mark designed it and wrote all of it in 6502. He also
|
|
5
|
+
wrote the vector editor the pictures were drawn in, which is not on the disks. This package
|
|
6
|
+
describes the engine from its code and has JavaScript that reads its pictures and objects and
|
|
7
|
+
draws as it does, dot for dot.
|
|
8
|
+
|
|
9
|
+
The addresses here are Animal Rescue's. Nature Park's engine is the same code, later and moved:
|
|
10
|
+
its zero page is $10 to $21 where Animal Rescue's is $00 to $11, and every routine is at a new
|
|
11
|
+
address. The table at the end gives the main ones in both.
|
|
12
|
+
|
|
13
|
+
## The modules
|
|
14
|
+
|
|
15
|
+
| Module | What it has |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `pictures.js` | `pictures` reads PICS1 to PICS3; `shapes` reads shapes from memory, as the engine finds them at $E000; `windows` finds each picture's background color in the scenes' code |
|
|
18
|
+
| `draw.js` | `line`, `crossings`, `spans`, `fill`, `outline`, `draw`, `inside` and `under`, on a screen of 192 rows of 140 colors |
|
|
19
|
+
| `objects.js` | `decode` reads an object, `put` puts one on a screen, `tables` finds every object table in the code, `carry` |
|
|
20
|
+
| `svg.js` | `elements` and `document` draw shapes as SVG at any size, crisp as the screen was or smooth |
|
|
21
|
+
| `font.js` | the engine's proportional font: each character's width and dots, and where a line of them starts |
|
|
22
|
+
| `inline.js` | the text, picture names and sounds the engine reads from after a call |
|
|
23
|
+
|
|
24
|
+
The sixteen colors, their names and `rgb` are the Apple's own, `@moonbase-machines/apple2e/colors.js`.
|
|
25
|
+
ES modules with nothing else to install, in Node and in the browser. The tests here are made from
|
|
26
|
+
bytes they build; the test that runs PART0 itself against `draw.js`, on every shape of both games,
|
|
27
|
+
lives where the games' disks are, since the disks are not this package's to hold.
|
|
28
|
+
|
|
29
|
+
## The screen
|
|
30
|
+
|
|
31
|
+
The screen is double hi-res: 140 dots across and 192 down, each dot one of sixteen colors. Each
|
|
32
|
+
row is 80 bytes, alternately from the auxiliary and the main halves of the page. Each byte holds
|
|
33
|
+
seven bits, lowest first, and four bits make one dot, its color's bits high bit first. So a dot
|
|
34
|
+
is four bits wide on the glass and a row is 560 bits.
|
|
35
|
+
|
|
36
|
+
A color is 0 to 15, and its four bits are the pattern the engine writes for it. The table at
|
|
37
|
+
$5FAD holds four bytes for each color: auxiliary, main, auxiliary, main, for seven dots of it.
|
|
38
|
+
Color 12 is 33 66 4C 19, which is 1100 repeated, and Apple calls that medium blue.
|
|
39
|
+
|
|
40
|
+
| | | | |
|
|
41
|
+
|---|---|---|---|
|
|
42
|
+
| 0 black | 4 dark green | 8 dark blue | 12 medium blue |
|
|
43
|
+
| 1 magenta | 5 gray | 9 purple | 13 light blue |
|
|
44
|
+
| 2 brown | 6 green | 10 gray | 14 aqua |
|
|
45
|
+
| 3 orange | 7 yellow | 11 pink | 15 white |
|
|
46
|
+
|
|
47
|
+
**One dot,** $5AF4, with the color in A and the dot's column and row in X and Y. Tables built
|
|
48
|
+
at $5A17 give each row's address in the page, each dot's byte and its place s, from 0 to 6, in a
|
|
49
|
+
group of seven. Dots in places 1, 3 and 5 straddle two bytes. The routine keeps the bits of the
|
|
50
|
+
byte that are not the dot's, with the masks at $18B8, and writes the dot's bits from a table of
|
|
51
|
+
each color in each place. For a dot that straddles, it does the same to the next byte with the
|
|
52
|
+
masks at $18BF. The places that start in the auxiliary half are 0, 1, 4 and 5. Animal Rescue's
|
|
53
|
+
dot routine draws anywhere it is told to. Nature Park's, at $500C, does nothing for a column
|
|
54
|
+
past 139 or a row past 191.
|
|
55
|
+
|
|
56
|
+
**A span,** $7947: the dots from X to Y on the row in $02, in the color in A. It writes the
|
|
57
|
+
first and last bytes with masks at $713F and $7146 and the bytes between whole.
|
|
58
|
+
|
|
59
|
+
**Reading a dot back,** $5B4C, gives the color at X and Y. $77BA keeps a span's bytes and $7958
|
|
60
|
+
puts them back, which is how an object is moved without spoiling the picture under it.
|
|
61
|
+
|
|
62
|
+
## Pictures
|
|
63
|
+
|
|
64
|
+
A picture is a list of shapes. A shape is two bytes, its fill color and its outline color,
|
|
65
|
+
then its points as column and row pairs. $FE ends a shape and $FF after it ends the picture. A
|
|
66
|
+
shape of two points is a line in its outline color.
|
|
67
|
+
|
|
68
|
+
PICS1 to PICS3 are lists of pictures. Each picture is its name in 14 bytes, padded with spaces,
|
|
69
|
+
then two bytes for its length from the start of the name, then its shapes. A scene asks for a
|
|
70
|
+
picture by name: the picture routine at $7AFE takes the name inline after the call, as a length
|
|
71
|
+
byte with its high bit set and the name in small letters with underscores for spaces. The
|
|
72
|
+
routine has the loader copy the shapes to $E000, then draws them.
|
|
73
|
+
|
|
74
|
+
The color behind a picture is not in the picture. Each scene sets it first, with the box routine
|
|
75
|
+
at $65D2, which takes three colors: the picture window's in A and the two text boxes' below it
|
|
76
|
+
in X and Y.
|
|
77
|
+
|
|
78
|
+
## How a shape is drawn
|
|
79
|
+
|
|
80
|
+
The loop at $7B8C draws each shape at $E000, its fill and then its outline, until it reads $FF.
|
|
81
|
+
Every rule below was found in the code and then checked: PART0's own fill, outline and point test,
|
|
82
|
+
run on the 6502 (`@moonbase-machines/mos6502`) on every shape of every picture of both games,
|
|
83
|
+
compared with `draw.js` after every step (the test where the disks are):
|
|
84
|
+
|
|
85
|
+
- **Animal Rescue:** 787 shapes and 4,722 point tests, all the same.
|
|
86
|
+
- **Nature Park:** 1,881 shapes and 11,286 point tests, all the same.
|
|
87
|
+
|
|
88
|
+
**The line,** $7A54, from ($00, $02) to ($01, $03) in the color in $11. It draws both ends and
|
|
89
|
+
one dot for every step along the longer axis. It keeps an error that starts at twice the shorter
|
|
90
|
+
distance less the longer one. While the error is below zero, only the longer axis steps, and
|
|
91
|
+
the error grows by twice the shorter distance. Otherwise both axes step, and it grows by twice
|
|
92
|
+
the shorter less twice the longer. The steps are written into the routine itself, as INX or DEX,
|
|
93
|
+
INY or DEY, or NOP.
|
|
94
|
+
|
|
95
|
+
**The outline,** $7BAA, draws every edge in the outline color and closes back to the first
|
|
96
|
+
point. A shape of two points is one line, and its fill color is not used.
|
|
97
|
+
|
|
98
|
+
**The fill,** $7C57, works in two passes over the edges, with a list of crossings for each row
|
|
99
|
+
kept in the language card.
|
|
100
|
+
|
|
101
|
+
1. The first pass counts the crossings on each row, so that each row's list can be laid out.
|
|
102
|
+
2. The second pass records them. It walks each edge that is not level with the line's own
|
|
103
|
+
steps. Each time the walk leaves a row, it records the column of its last dot on that row.
|
|
104
|
+
So an edge records nothing on the row it ends on, and the fill meets the outline's dots
|
|
105
|
+
exactly.
|
|
106
|
+
3. Where an edge turns up after going down, or down after going up, its first point is recorded
|
|
107
|
+
as well. The first edge that is not level always counts as a turn. Level edges are passed
|
|
108
|
+
over.
|
|
109
|
+
4. The shape closes back to its first point. When that point is strictly between its two
|
|
110
|
+
neighbours in height, and not on the top or bottom row, it is recorded as well.
|
|
111
|
+
5. Each row's list is kept in order, and a column equal to one already there goes after it.
|
|
112
|
+
|
|
113
|
+
The rows filled run from the first row with a crossing to the row before the next row without
|
|
114
|
+
one. On each row the columns are paired from the end of the list. So a row with an odd count
|
|
115
|
+
leaves out its largest column: three shapes in Animal Rescue do that, and thirteen in Nature
|
|
116
|
+
Park. Each pair is filled as a span, both ends included.
|
|
117
|
+
|
|
118
|
+
A span of one dot goes to the dot routine with the wrong value in A, and is drawn black. The
|
|
119
|
+
outline is drawn next and always covers it, so no finished picture shows it. `draw.js` draws
|
|
120
|
+
it anyway, so that the screen is the same after every step.
|
|
121
|
+
|
|
122
|
+
**The point test,** $7C54, is the fill with nothing drawn. The only difference between the two
|
|
123
|
+
entries is one byte, the mode, since $7C54 is `LDA #$80` followed by a BIT that hides the fill's
|
|
124
|
+
own `LDA #$00`. The point is in the shape when a span on its row has it between its ends, both
|
|
125
|
+
ends counted. It is not the usual count of crossings left of the point. It returns $80 for in and
|
|
126
|
+
0 for out. For a shape of two points it returns $FE, which a caller takes as in.
|
|
127
|
+
|
|
128
|
+
Only Nature Park uses the test. Its colouring-in problem, PROB1, asks it of every shape at the
|
|
129
|
+
point under the pointer, keeps the last shape that answers in, since later shapes cover earlier
|
|
130
|
+
ones, and fills that shape again in the new color. `under` in `draw.js` does the same.
|
|
131
|
+
|
|
132
|
+
## Objects
|
|
133
|
+
|
|
134
|
+
What moves over a picture is not in the picture. A scene points the engine at a table of objects,
|
|
135
|
+
a list of two-byte addresses, through $685A with the address in X and Y. It then draws an
|
|
136
|
+
object by its number, counted from 2, at a column and row: through $686D, or through $686F to
|
|
137
|
+
keep the ground under it so that it can be put back. Objects 0 and 1 put the kept ground back.
|
|
138
|
+
|
|
139
|
+
An object is four bytes, its flags, the bytes to a row, its width in dots and its height in
|
|
140
|
+
rows, then its rows. It is packed one of two ways:
|
|
141
|
+
- **Bytes:** with bit 6 of the flags clear, the rows are double hi-res bytes of seven bits. A
|
|
142
|
+
byte with its high bit set is a run: the byte less that bit, as many times as the next byte
|
|
143
|
+
says.
|
|
144
|
+
- **Nibbles:** with bit 6 set, the rows are a nibble a dot, each nibble a color. The first byte
|
|
145
|
+
of all is an escape: after it come a count and a pair of nibbles to repeat.
|
|
146
|
+
|
|
147
|
+
A run may carry over the end of a row.
|
|
148
|
+
|
|
149
|
+
With bit 7 clear, the color in the flags' low four bits is the object's clear color: where one of
|
|
150
|
+
its own dots is that color, the ground under it shows through ($6A7D, which takes the dot from
|
|
151
|
+
the ground kept at $6B4E wherever the object's buffer at $6B50 holds that color). The elephants
|
|
152
|
+
of PROB2 are drawn in black boxes and flagged black, so only the elephants show, and the towers
|
|
153
|
+
are flagged white. With bit 7 set the object is put down whole.
|
|
154
|
+
|
|
155
|
+
The clear color works only when the routine has the ground to take the dot from. Through $686F
|
|
156
|
+
it keeps the ground at $6B4E, for object 0 to put back. Through $6863 it keeps each row in a
|
|
157
|
+
buffer of its own at $0240 ($6C12), so the clear color shows the ground and nothing is kept for
|
|
158
|
+
object 0. Through $686D the routine goes from $69C8 straight to $6B0F and puts the object down
|
|
159
|
+
whole, its clear color and all. PROB6's car is flagged gray, the road's color, and is drawn
|
|
160
|
+
through $686D as it moves, so its gray wipes out the car a dot behind; only its white edge, a
|
|
161
|
+
column the next car does not cover, is left where the dot drawn behind it misses.
|
|
162
|
+
|
|
163
|
+
## Text
|
|
164
|
+
|
|
165
|
+
The text routine at $6CE2 prints the text that follows the call, in the engine's own font on
|
|
166
|
+
the graphics screen. The text is ASCII with the high bit set, $8D starts a new line, and $80
|
|
167
|
+
ends it. $1B starts a code for the column, the row, the colors, the line spacing or a repeated
|
|
168
|
+
character. After $1B, codes $0F and $0C take two more bytes, $06 and $0B take four, and $07 to
|
|
169
|
+
$0A, $0D, $0E, $10 and $11 take none. Any other code takes one byte.
|
|
170
|
+
|
|
171
|
+
## The routines the scenes call
|
|
172
|
+
|
|
173
|
+
Animal Rescue's parts and problems are 6502 code, but nearly all of it is calls into the engine.
|
|
174
|
+
They call 42 of its routines, 974 times. Only three routines take data after the call: text, the
|
|
175
|
+
picture and the sound. The counts are for the parts and the problems together.
|
|
176
|
+
|
|
177
|
+
| Address | What it does | Given | Calls |
|
|
178
|
+
|---|---|---|---|
|
|
179
|
+
| $6CE2 | print the text after the call | the text | 191 |
|
|
180
|
+
| $6299, $629F, $6313, $6319, $6395, $6387 | clear one of the text panels and put the cursor in it | nothing | 168 |
|
|
181
|
+
| $5C23 | wait for the space bar, showing its button | nothing | 82 |
|
|
182
|
+
| $5C08 | wait for the space bar, then take its button away | nothing | 67 |
|
|
183
|
+
| $686D | draw an object | A the number, X and Y the place | 55 |
|
|
184
|
+
| $686F | draw an object, keeping the ground under it | A the number, X and Y the place | 49 |
|
|
185
|
+
| $65D2 | set up the picture window and the two text boxes | A, X, Y the three colors; $80 leaves one as it is | 48 |
|
|
186
|
+
| $7AFE | load a picture by name and draw it | the name | 48 |
|
|
187
|
+
| $7947 | fill a span of a row | A the color, X and Y the ends, $02 the row | 27 |
|
|
188
|
+
| $60C2 | a random number below A | A | 26 |
|
|
189
|
+
| $6831 | a beep of the pitch in A | A | 26 |
|
|
190
|
+
| $685A | set the table of objects | X and Y its address | 24 |
|
|
191
|
+
| $425B | a sound: end pitch, start pitch, clicks a step, the step, repeats | five bytes | 23 |
|
|
192
|
+
| $5BA9 | show the double hi-res screen | nothing | 16 |
|
|
193
|
+
| $76ED, $76F3 | draw a key with its letter | A the letter, X and Y the place | 25 |
|
|
194
|
+
| $5F1D | multiply A by X into X and A | A, X | 13 |
|
|
195
|
+
| $7A54 | a line | $00 to $03 the ends, $11 the color | 13 |
|
|
196
|
+
| $641B, $6415 | ask for a key, showing its letter | A the letter | 11 |
|
|
197
|
+
| $67B9 | a filled rectangle, columns X to X + $0E, both ends, and $0F rows | A the color, X and Y the corner, $0E and $0F the size | 10 |
|
|
198
|
+
| $5B4C | read the color of a dot | X, Y | 8 |
|
|
199
|
+
| $57CA | load a problem into the language card and run it at $F000 | A the problem | 6 |
|
|
200
|
+
| $5AF4 | one dot | A the color, X, Y | 6 |
|
|
201
|
+
| $5F4B | fill rows with a color | A the color, X the rows, Y the first | 5 |
|
|
202
|
+
| $5D66 | read the keyboard, the joystick or the Concept Keyboard | nothing | 4 |
|
|
203
|
+
| $5EEF | divide Y and X by A | A, X, Y | 4 |
|
|
204
|
+
| $4035 | load a part and start it | A the part | 3 |
|
|
205
|
+
| $677E | a rectangle's outline, four lines from X to X + $0E and Y to Y + $0F | as $67B9 | 3 |
|
|
206
|
+
| $67DB | flash the picture window, with a sound | nothing | 3 |
|
|
207
|
+
|
|
208
|
+
The rest are called once or twice: a key panel from a table ($66F8), keeping and putting back a
|
|
209
|
+
span ($77BA, $7958), the space bar's button alone ($5C45), two white lines across the screen
|
|
210
|
+
($5FFD), and inverting a span ($7870). What each text code does, the random routine's exact
|
|
211
|
+
range, and the third way of drawing an object, $6863, are read from the routines' first
|
|
212
|
+
instructions and not traced further.
|
|
213
|
+
|
|
214
|
+
| Routine | Animal Rescue | Nature Park |
|
|
215
|
+
|---|---|---|
|
|
216
|
+
| text | $6CE2 | $61C3 |
|
|
217
|
+
| picture | $7AFE | $6FDF |
|
|
218
|
+
| box | $65D2 | $5AB3 |
|
|
219
|
+
| sound | $425B | $4E12 |
|
|
220
|
+
| wait for the space bar | $5C23 | $513A |
|
|
221
|
+
| object, object keeping the ground | $686D, $686F | $5D4E, $5D50 |
|
|
222
|
+
| set the table of objects | $685A | $5D3B |
|
|
223
|
+
| fill, point test, outline | $7C57, $7C54, $7BAA | $7147, $7144, $709A |
|
|
224
|
+
| line, span, dot, read a dot | $7A54, $7947, $5AF4, $5B4C | $6F35, $6E28, $500C, $506C |
|
|
225
|
+
| the color table | $5FAD | $54FE |
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/*! @moonbase-machines/vector 0.5.1: MIT, (c) 2026 Mark Wharton (its LICENSE) */
|
|
2
|
+
var z=Object.defineProperty;var w=(t,n)=>{for(var o in n)z(t,o,{get:n[o],enumerable:!0})};var N={};w(N,{crossings:()=>E,draw:()=>J,fill:()=>j,inside:()=>B,line:()=>$,outline:()=>I,screen:()=>v,spans:()=>A,under:()=>K});var v=(t=0)=>Array.from({length:192},()=>new Array(140).fill(t));function $(t,n,o,r){let c=Math.abs(o-t),e=Math.abs(r-n),f=o>=t?1:-1,i=r>=n?1:-1,l=c>=e,s=l?c:e,g=l?e:c,u=2*g-s,h=t,x=n,a=[[h,x]];for(let p=0;p<s;p++)u<0?(l?h+=f:x+=i,u+=2*g):(h+=f,x+=i,u+=2*g-2*s),a.push([h,x]);return a}function E(t){let n=Array.from({length:192},()=>[]),o=(u,h)=>{if(u<0||u>=192)return;let x=n[u],a=0;for(;a<x.length&&h>=x[a];)a++;x.splice(a,0,h)},r=t.length,c=0;for(let u=0;u<r;u++){let[h,x]=t[u],[a,p]=t[(u+1)%r];if(x===p)continue;let d=$(h,x,a,p);for(let m=1;m<d.length;m++)d[m][1]!==d[m-1][1]&&o(d[m-1][1],d[m-1][0]);let S=p>x?1:-1;S!==c&&(c=S,o(x,h))}let e=n.findIndex(u=>u.length);if(e<0)return null;let f=e;for(;f+1<192&&n[f+1].length;)f++;let[i,l]=t[0],s=Math.min(t[1][1],t[r-1][1]),g=Math.max(t[1][1],t[r-1][1]);return l!==e&&l!==f&&l>s&&l<g&&o(l,i),{rows:n,top:e,bottom:f}}function A(t,n){let o=E(t);if(o)for(let r=o.top;r<=o.bottom;r++){let c=o.rows[r];for(let e=(c.length&-2)-2;e>=0;e-=2)n(c[e],c[e+1],r)}}var M=(t,n,o,r)=>{n>=0&&n<140&&o>=0&&o<192&&(t[o][n]=r&15)};function j(t,n){return n.points.length<3||A(n.points,(o,r,c)=>{if(o===r)M(t,o,c,0);else for(let e=o;e<=r;e++)M(t,e,c,n.fill)}),t}function I(t,n){let o=n.points,r=o.length;if(r<2)return t;for(let c=0;c<(r===2?1:r);c++)for(let[e,f]of $(...o[c],...o[(c+1)%r]))M(t,e,f,n.line);return t}function J(t,n=v()){for(let o of t)j(n,o),I(n,o);return n}function B(t,n,o){if(t.points.length===2)return 254;let r=0;return A(t.points,(c,e,f)=>{f===o&&n>=c&&n<=e&&(r=128)}),r}function K(t,n,o){let r=-1;return t.forEach((c,e)=>{B(c,n,o)&&(r=e)}),r}var R={};w(R,{GLYPHS:()=>y,WIDTHS:()=>b,dots:()=>X,start:()=>Q,useFont:()=>t0,width:()=>C});var b=[3,1,3,5,5,5,4,1,3,3,5,5,2,5,1,5,4,3,4,4,4,4,4,4,4,4,1,2,3,3,3,4,8,4,4,4,4,4,4,4,4,3,4,4,4,5,4,4,4,4,4,4,3,4,5,5,4,5,4,5,5,5,5,5,3,4,4,4,4,4,3,4,4,3,4,4,3,5,4,4,4,4,4,4,3,4,5,5,4,4,4,3,1,3,4,5],O=[[6,"78ccccdceccccccc78"],[4,"60e0606060606060f0"],[6,"78cc0c183060c0c0fc"],[6,"78cc0c0c380c0ccc78"],[6,"0c1c2c4cccfc0c0c0c"],[6,"fcc0c0f80c0c0ccc78"],[6,"78ccc0c0f8cccccc78"],[6,"fc0c0c181830303030"],[6,"78cccccc78cccccc78"],[6,"78cccccc7c0c0ccc78"]],C=t=>{let n=t.charCodeAt(0);return n<32?n:n>=128?O[n-128]?.[0]??6:b[n-32]??4};function Q(t,n,o,r){if(!r)return o;let c=0;for(let e of t)c+=e.charCodeAt(0)<32?C(e):C(e)+n;return(140-c&255)>>1}var y=["000000000000000000","808080808000800000","a0a0a0000000000000","0050f850f850000000","207080700870200000","808810204088080000","609060909090700000","808080000000000000","204080808040200000","804020202040800000","008850205088000000","002020f82020000000","000000000000408000","000000f80000000000","000000000080800000","000810204080000000","6090b0d09090600000","40c040404040e00000","609010608080f00000","609010201090600000","10305090f010100000","f080e0101090600000","609080e09090600000","f01010202040400000","609090609090600000","609090701090600000","000080800080800000","000040400040404080","002040804020000000","0000e000e000000000","008040204080000000","609010204000400000","ffffffffffffffffff","609090f09090900000","e09090e09090e00000","609080808090600000","e09090909090e00000","f08080c08080f00000","f08080c08080800000","609080b09090700000","909090f09090900000","e04040404040e00000","301010101090600000","90a0c0a0a090900000","808080808080f00000","88d8a8888888880000","90d0b0909090900000","609090909090600000","e09090e08080800000","60909090b0b0700000","e09090e09090900000","609080601090600000","e04040404040400000","909090909090600000","888888888850200000","88888888a8d8880000","909090609090900000","888850202020200000","f01020408080f00000","002060f86020000000","002030f83020000000","002020f87020000000","002070f82020000000","0000000000000000f8","804020000000000000","0000709090b0500000","8080e0909090e00000","000060908090600000","101070909090700000","00006090f080600000","2040e0404040400000","0000709090907010e0","8080e0909090900000","4000c0404040e00000","2000602020202020c0","808090a0c0a0900000","804040404040e00000","0000d0a8a8a8a80000","0000e0909090900000","000060909090600000","0000e0909090e08080","000070909090701010","0000e0908080800000","000070806010e00000","4040e0404040200000","000090909090700000","000088888850200000","00008888a8d8880000","000090906090900000","0000909090907010e0","0000f0106080f00000","204040804040200000","808080808080808080","804040204040800000","106080000000000000","ff0000000000000000"];function X(t){let n=t.charCodeAt(0),o=n>=128?O[n-128]?.[1]:y[n-32],r=[];if(!o)return r;for(let c=0;c<9;c++){let e=parseInt(o.slice(2*c,2*c+2),16);for(let f=0;f<8;f++)e>>7-f&1&&r.push([f,c])}return r}var Z={"nature-park-adventure":{0:[4,"609090909090600000"],_:[7,"000054aa5400000000"]}},L={widths:b.slice(),glyphs:y.slice()};function t0(t){b.splice(0,b.length,...L.widths),y.splice(0,y.length,...L.glyphs);for(let[n,[o,r]]of Object.entries(Z[t]??{}))b[n.charCodeAt(0)-32]=o,y[n.charCodeAt(0)-32]=r}var _={};w(_,{INLINE:()=>e0,name:()=>H,sound:()=>T,text:()=>G});function G(t,n){let o="",r=n;for(let c=0;c<4096;c++){let e=t(r++);if(e===void 0||e===128)break;if(e===27){let f=t(r++),i=f===15||f===12?2:f===6||f===11?4:[7,8,9,10,13,14,16,17].includes(f)?0:1,l=[];for(let s=0;s<i;s++)l.push(t(r+s).toString(16));o+="{"+f.toString(16)+(i?":"+l.join(","):"")+"}",r+=i;continue}o+=e===141?`
|
|
3
|
+
`:String.fromCharCode(e&127)}return{length:r-n,value:o}}function H(t,n){let o=t(n)&127,r="";for(let c=1;c<=o;c++)r+=String.fromCharCode(t(n+c)&127);return{length:o+1,value:r}}function T(t,n){let o=[];for(let r=0;r<5;r++)o.push(t(n+r).toString(16));return{length:5,value:o.join(" ")}}var e0={text:G,picture:H,sound:T};var D={};w(D,{carry:()=>r0,decode:()=>q,put:()=>n0,tables:()=>o0});function q(t,n){let o=t[n],r=t[n+1],c=t[n+2],e=t[n+3],f=n+4,i=0,l=0,s=[];if(o&64){let g=t[f++],u=()=>{if(i)return i--,l;let h=t[f++];return h===g?(i=t[f]-1,l=t[f+1],f+=2,l):h};for(let h=0;h<e;h++){let x=[];for(let a=0;a<c+1>>1;a++){let p=u();x.push(p>>4,p&15)}s.push(x.slice(0,c))}}else{let g=()=>{if(i)return i--,l;let u=t[f++];return u&128?(l=u&127,i=t[f++]-1,l):u};for(let u=0;u<e;u++){let h=[];for(let a=0;a<r;a++){let p=g();for(let d=0;d<7;d++)h.push(p>>d&1)}let x=[];for(let a=0;a<c;a++)x.push(h[a*4]<<3|h[a*4+1]<<2|h[a*4+2]<<1|h[a*4+3]);s.push(x)}}return{flags:o,stride:r,width:c,height:e,packed:o&64?"nibbles":"bytes",clear:o&128?void 0:o&15,rows:s,end:f}}function n0(t,n,o,r,c=!1){return n.rows.forEach((e,f)=>e.forEach((i,l)=>{let s=t[r+f];!s||o+l<0||o+l>=s.length||(c||i!==n.clear)&&(s[o+l]=i)})),t}function o0(t,n){let o=[],r=(e,f)=>e[f]|e[f+1]<<8,c=e=>t.find(f=>e>=f.origin&&e<f.origin+f.bytes.length);for(let e of t){let f=e.bytes;for(let i=0;i+7<=f.length;i++){if(!(f[i]===162&&f[i+2]===160&&f[i+4]===32&&r(f,i+5)===n))continue;let l=f[i+1]|f[i+3]<<8,s=l>=e.origin&&l<e.origin+f.length?e:c(l);if(!s||o.some(u=>u.file===s.name&&u.address===l))continue;let g=[];for(let u=2;;u++){let h=l-s.origin+(u-2)*2;if(h+2>s.bytes.length)break;let x=r(s.bytes,h);if(x<s.origin||x+4>s.origin+s.bytes.length)break;let a=x-s.origin,p=s.bytes[a+2],d=s.bytes[a+3];if(!s.bytes[a+1]||!p||!d||p>140||d>192)break;g.push({number:u,at:x,...q(s.bytes,a)})}g.length&&o.push({file:s.name,setBy:e.name,address:l,objects:g})}}return o}function r0(t,n){let o=Math.floor(2*n/7),r=n%7,c=-(2*o+(r>>1&1))&255,e=0;for(let f=0;f<t.height;f++){let i=c+t.stride+1;e=i>255?1:0,c=i&255}return e}var U={};w(U,{pictures:()=>c0,shapes:()=>P,windows:()=>f0});var F=t=>String.fromCharCode(...Array.from(t,n=>n&127));function P(t,n,o=8192){let r=[],c=null;for(let e=n;e<n+o;e++){let f=t(e);if(f===255&&!c)break;if(f===254){c&&r.push(c),c=null;continue}if(!c){c={fill:f,line:t(e+1),points:[]},e++;continue}c.points.push([f,t(e+1)]),e++}return c&&r.push(c),r}function c0(t){let n=[];for(let o=0;o+16<=t.length;){let r=F(t.subarray(o,o+14)).trimEnd(),c=t[o+14]|t[o+15]<<8;if(c<18||o+c>t.length)break;n.push({name:r,at:o,length:c,shapes:P(e=>t[e],o+16,c-16)}),o+=c}return n}function f0(t,{box:n,picture:o}){let r=new Map,c=(e,f)=>e[f]|e[f+1]<<8;for(let e of t){let f;for(let i=0;i+8<e.length;i++){if(e[i]===169&&e[i+2]===162&&e[i+4]===160&&e[i+6]===32&&c(e,i+7)===n){f=e[i+1],i+=8;continue}if(e[i]===32&&c(e,i+1)===o&&e[i+3]&128){let l=e[i+3]&127,s=F(e.subarray(i+4,i+4+l)).toUpperCase().replace(/_/g," ").replace(/[\x00-\x1f]/g,"").trimEnd();f!==void 0&&!r.has(s)&&r.set(s,f),i+=3+l}}}return e=>r.get(e)??r.get(e.replace(/ ?\d$/,""))}var Y={};w(Y,{bounds:()=>V,document:()=>s0,elements:()=>W});import{colors as i0}from"@moonbase-machines/apple2e";var{COLORS:a0,NAMES:h0,rgb:k}=i0;function V(t){let n=140,o=192,r=-1,c=-1;for(let e of t)for(let[f,i]of e.points)n=Math.min(n,f),o=Math.min(o,i),r=Math.max(r,f),c=Math.max(c,i);return r<0?null:{x0:Math.max(0,n-1),y0:Math.max(0,o-1),x1:Math.min(139,r+1),y1:Math.min(191,c+1)}}function W(t,{background:n,crisp:o=!0}={}){let r=V(t),c=o?' shape-rendering="crispEdges"':"",e=r&&n!==void 0?`<rect x="${Math.max(0,r.x0-1)}" y="${Math.max(0,r.y0-1)}" width="${r.x1-r.x0+3}" height="${r.y1-r.y0+3}" fill="${k(n)}"/>`:"";for(let f of t){let i=f.points.map(([l,s])=>l+.5+","+(s+.5)).join(" ");f.points.length===2?e+=`<polyline points="${i}" fill="none" stroke="${k(f.line)}" stroke-width="1" stroke-linecap="square"${c}/>`:f.points.length>2&&(e+=`<polygon points="${i}" fill="${k(f.fill)}" stroke="${k(f.line)}" stroke-width="1" stroke-linejoin="round"${c}/>`)}return e}function s0(t,{background:n=0,width:o=560,crisp:r=!1}={}){return`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 192" width="${o}" height="${o*384/560}" preserveAspectRatio="none"><rect width="140" height="192" fill="${k(n)}"/>`+W(t,{crisp:r})+"</svg>"}export{N as draw,R as font,_ as inline,D as objects,U as pictures,Y as svg};
|
|
4
|
+
//# sourceMappingURL=moonbase-vector.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../draw.js", "../font.js", "../inline.js", "../objects.js", "../pictures.js", "../svg.js", "moonbase-module:apple2e/colors.js"],
|
|
4
|
+
"sourcesContent": ["// The engine's drawing, dot for dot. Every rule here is the engine's own, read from PART0 and\n// checked by running PART0's code on every shape of every picture of both games (test/).\n// Addresses are Animal Rescue's; Nature Park's engine is the same code moved.\n//\n// A screen is 192 rows of 140 colors. Drawing a shape is its fill, then its outline.\n\n// a screen of 192 rows of 140 dots, cleared to `background`\nexport const screen = (background = 0) => Array.from({ length: 192 }, () => new Array(140).fill(background));\n\n// The line, $7A54: both ends drawn, and the major axis stepped every dot. The error starts at\n// 2\u00B7minor \u2212 major; while it is negative only the major axis steps and it gains 2\u00B7minor,\n// otherwise both axes step and it gains 2\u00B7minor \u2212 2\u00B7major. Returns the dots in drawing order.\nexport function line(x0, y0, x1, y1) {\n const dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0), sx = x1 >= x0 ? 1 : -1, sy = y1 >= y0 ? 1 : -1;\n const xMajor = dx >= dy, major = xMajor ? dx : dy, minor = xMajor ? dy : dx;\n let err = 2 * minor - major, x = x0, y = y0;\n const out = [[x, y]];\n for (let k = 0; k < major; k++) {\n if (err < 0) { if (xMajor) x += sx; else y += sy; err += 2 * minor; }\n else { x += sx; y += sy; err += 2 * minor - 2 * major; }\n out.push([x, y]);\n }\n return out;\n}\n\n// The crossings of a polygon's edges with each row, $7C57 to $7E24. Each row's list is kept\n// sorted, an equal x going after the ones already there ($7E87).\n//\n// - Each edge that is not level is walked with the line's own steps, and each time the walk\n// leaves a row, the x of its last dot on that row is kept for that row. So an edge gives\n// nothing to the row it ends on, and the fill meets the outline's dots exactly.\n// - Where an edge goes the other way up or down from the last edge that was not level, its\n// first point is kept too. The direction starts at neither, so the first such edge always\n// keeps its first point. Level edges are passed over and change nothing.\n// - The rows filled run from the first row with a crossing to the row before the next row with\n// none.\n// - The shape is closed back to its first point, and a crossing is added there when that point\n// is strictly between its neighbours in height and not on the first or last row filled.\n//\n// Returns { rows, top, bottom }, or null for a shape that crosses no row.\nexport function crossings(points) {\n const rows = Array.from({ length: 192 }, () => []);\n const keep = (y, x) => { if (y < 0 || y >= 192) return; const l = rows[y]; let i = 0; while (i < l.length && x >= l[i]) i++; l.splice(i, 0, x); };\n const n = points.length;\n let last = 0;\n for (let i = 0; i < n; i++) {\n const [xa, ya] = points[i], [xb, yb] = points[(i + 1) % n];\n if (ya === yb) continue;\n const dots = line(xa, ya, xb, yb);\n for (let k = 1; k < dots.length; k++) if (dots[k][1] !== dots[k - 1][1]) keep(dots[k - 1][1], dots[k - 1][0]);\n const way = yb > ya ? 1 : -1;\n if (way !== last) { last = way; keep(ya, xa); }\n }\n const top = rows.findIndex(l => l.length);\n if (top < 0) return null;\n let bottom = top;\n while (bottom + 1 < 192 && rows[bottom + 1].length) bottom++;\n const [x0, y0] = points[0], lo = Math.min(points[1][1], points[n - 1][1]), hi = Math.max(points[1][1], points[n - 1][1]);\n if (y0 !== top && y0 !== bottom && y0 > lo && y0 < hi) keep(y0, x0);\n return { rows, top, bottom };\n}\n\n// The spans the fill draws, $7E25: on each row the crossings are paired from the end of the\n// list, so a row with an odd number of crossings leaves out its largest x. Calls `each(left,\n// right, y)` for every span, in the engine's order.\nexport function spans(points, each) {\n const c = crossings(points);\n if (!c) return;\n for (let y = c.top; y <= c.bottom; y++) {\n const l = c.rows[y];\n for (let j = (l.length & ~1) - 2; j >= 0; j -= 2) each(l[j], l[j + 1], y);\n }\n}\n\nconst set = (scr, x, y, c) => { if (x >= 0 && x < 140 && y >= 0 && y < 192) scr[y][x] = c & 15; };\n\n// The fill. A span of one dot goes to the dot routine with the wrong color and is drawn black\n// ($7955); the outline, drawn next, always covers it.\nexport function fill(scr, shape) {\n if (shape.points.length < 3) return scr;\n spans(shape.points, (a, b, y) => { if (a === b) set(scr, a, y, 0); else for (let x = a; x <= b; x++) set(scr, x, y, shape.fill); });\n return scr;\n}\n\n// The outline, $7BAA: every edge in the outline color, closing back to the first point. A shape\n// of two points is one line in the outline color, and its fill color is not used.\nexport function outline(scr, shape) {\n const p = shape.points, n = p.length;\n if (n < 2) return scr;\n for (let i = 0; i < (n === 2 ? 1 : n); i++) for (const [x, y] of line(...p[i], ...p[(i + 1) % n])) set(scr, x, y, shape.line);\n return scr;\n}\n\n// a list of shapes, each filled and then outlined, as the loop at $7B8C draws a picture\nexport function draw(list, scr = screen()) {\n for (const shape of list) { fill(scr, shape); outline(scr, shape); }\n return scr;\n}\n\n// The point test, $7C54: the fill's own spans, with nothing drawn. A point is in a shape when\n// a span on its row has it between its ends, both ends counted. Returns what the engine returns\n// in A: $80 inside, 0 outside, and $FE for a shape of two points, which a caller takes as inside.\n// Nature Park's colouring-in problem uses it to find the shape under the pointer.\nexport function inside(shape, x, y) {\n if (shape.points.length === 2) return 0xfe;\n let r = 0;\n spans(shape.points, (a, b, row) => { if (row === y && x >= a && x <= b) r = 0x80; });\n return r;\n}\n\n// The shape under a point: the last of `list` that the point is inside, since later shapes are\n// drawn over earlier ones. Returns its index, or -1.\nexport function under(list, x, y) {\n let hit = -1;\n list.forEach((shape, i) => { if (inside(shape, x, y)) hit = i; });\n return hit;\n}\n", "// The engine's font, measured: the width in dots of each character from space to $7F, the first\n// byte of each glyph, found through the tables of pointers at $7551 and $75BB in Animal Rescue's\n// PART0. A line's width is each character's width and the spacing after it; a line is centred\n// at (140 \u2212 its width) / 2, worked in a byte as the engine did ($6F4D).\nexport const WIDTHS = [\n 3, 1, 3, 5, 5, 5, 4, 1, 3, 3, 5, 5, 2, 5, 1, 5, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 1, 2, 3, 3, 3, 4,\n 8, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 3, 4, 5, 5, 4, 5, 4, 5, 5, 5, 5, 5,\n 3, 4, 4, 4, 4, 4, 3, 4, 4, 3, 4, 4, 3, 5, 4, 4, 4, 4, 4, 4, 3, 4, 5, 5, 4, 4, 4, 3, 1, 3, 4, 5,\n];\n\n// The text's code $10 turns to a second set of digits, bigger, read through the same tables at\n// $80 to $89 (the digit plus $50, $63FD in Nature Park), and $11 back; each text starts with the\n// first. Both engines have the same: six dots wide, the one four, nine rows.\nconst BIG = [[6, '78ccccdceccccccc78'], [4, '60e0606060606060f0'], [6, '78cc0c183060c0c0fc'], [6, '78cc0c0c380c0ccc78'], [6, '0c1c2c4cccfc0c0c0c'],\n [6, 'fcc0c0f80c0c0ccc78'], [6, '78ccc0c0f8cccccc78'], [6, 'fc0c0c181830303030'], [6, '78cccccc78cccccc78'], [6, '78cccccc7c0c0ccc78']];\n\n// a character's width in dots; a byte below a space is a spacer that takes its own value\nexport const width = ch => { const c = ch.charCodeAt(0); return c < 0x20 ? c : c >= 0x80 ? BIG[c - 0x80]?.[0] ?? 6 : WIDTHS[c - 0x20] ?? 4; };\n\n// where a line of characters starts, in dots: its own x, or centred as the engine centres\nexport function start(chars, spacing, x, centre) {\n if (!centre) return x;\n let sum = 0;\n for (const ch of chars) sum += ch.charCodeAt(0) < 0x20 ? width(ch) : width(ch) + spacing;\n return ((0x8c - sum) & 0xff) >> 1;\n}\n\n// Each character's glyph, space to $7F: nine rows from the top, a byte a row, its high bit the\n// leftmost dot, as the glyph follows its width in PART0. Written in hexadecimal, two digits a row.\nexport const GLYPHS = [\n '000000000000000000', '808080808000800000', 'a0a0a0000000000000', '0050f850f850000000', '207080700870200000', '808810204088080000', '609060909090700000', '808080000000000000',\n '204080808040200000', '804020202040800000', '008850205088000000', '002020f82020000000', '000000000000408000', '000000f80000000000', '000000000080800000', '000810204080000000',\n '6090b0d09090600000', '40c040404040e00000', '609010608080f00000', '609010201090600000', '10305090f010100000', 'f080e0101090600000', '609080e09090600000', 'f01010202040400000',\n '609090609090600000', '609090701090600000', '000080800080800000', '000040400040404080', '002040804020000000', '0000e000e000000000', '008040204080000000', '609010204000400000',\n 'ffffffffffffffffff', '609090f09090900000', 'e09090e09090e00000', '609080808090600000', 'e09090909090e00000', 'f08080c08080f00000', 'f08080c08080800000', '609080b09090700000',\n '909090f09090900000', 'e04040404040e00000', '301010101090600000', '90a0c0a0a090900000', '808080808080f00000', '88d8a8888888880000', '90d0b0909090900000', '609090909090600000',\n 'e09090e08080800000', '60909090b0b0700000', 'e09090e09090900000', '609080601090600000', 'e04040404040400000', '909090909090600000', '888888888850200000', '88888888a8d8880000',\n '909090609090900000', '888850202020200000', 'f01020408080f00000', '002060f86020000000', '002030f83020000000', '002020f87020000000', '002070f82020000000', '0000000000000000f8',\n '804020000000000000', '0000709090b0500000', '8080e0909090e00000', '000060908090600000', '101070909090700000', '00006090f080600000', '2040e0404040400000', '0000709090907010e0',\n '8080e0909090900000', '4000c0404040e00000', '2000602020202020c0', '808090a0c0a0900000', '804040404040e00000', '0000d0a8a8a8a80000', '0000e0909090900000', '000060909090600000',\n '0000e0909090e08080', '000070909090701010', '0000e0908080800000', '000070806010e00000', '4040e0404040200000', '000090909090700000', '000088888850200000', '00008888a8d8880000',\n '000090906090900000', '0000909090907010e0', '0000f0106080f00000', '204040804040200000', '808080808080808080', '804040204040800000', '106080000000000000', 'ff0000000000000000',\n];\n\n// the dots of a character's glyph, as [x, y] from its top left\nexport function dots(ch) {\n const c = ch.charCodeAt(0), hex = c >= 0x80 ? BIG[c - 0x80]?.[1] : GLYPHS[c - 0x20], out = [];\n if (!hex) return out;\n for (let y = 0; y < 9; y++) { const row = parseInt(hex.slice(2 * y, 2 * y + 2), 16); for (let x = 0; x < 8; x++) if (row >> (7 - x) & 1) out.push([x, y]); }\n return out;\n}\n\n// Nature Park's font is Animal Rescue's but for two glyphs, read from its PART0 through its tables\n// at $6A32 and $6A9C: a zero without the stroke, and the underline a band of seven dots, which its\n// menus put either side of their names. `useFont` makes a title's font the one these read.\nconst VARIANTS = { 'nature-park-adventure': { '0': [4, '609090909090600000'], '_': [7, '000054aa5400000000'] } };\nconst BASE = { widths: WIDTHS.slice(), glyphs: GLYPHS.slice() };\nexport function useFont(slug) {\n WIDTHS.splice(0, WIDTHS.length, ...BASE.widths); GLYPHS.splice(0, GLYPHS.length, ...BASE.glyphs);\n for (const [ch, [w, rows]] of Object.entries(VARIANTS[slug] ?? {})) { WIDTHS[ch.charCodeAt(0) - 0x20] = w; GLYPHS[ch.charCodeAt(0) - 0x20] = rows; }\n}\n", "// The data the engine's routines take inline, after the call: the text routine's text, the\n// picture routine's name and the sound routine's five bytes. `read` gives the byte at an\n// address, so the same reading works on a file and on a running machine's memory. Each returns\n// { length, value }: how many bytes the data takes, and what it says.\n\n// Text: ASCII with the high bit set, $8D a new line, $80 the end. $1B comes before a code, and a\n// code takes 0, 1, 2 or 4 bytes after it: $0F and $0C take two, $06 and $0B four, $07 to $0A,\n// $0D, $0E, $10 and $11 none, and every other code one. A code is kept in the text in braces,\n// {6:42,86,69,86}, its number and its bytes in hexadecimal, so that nothing is lost.\nexport function text(read, at) {\n let s = '', i = at;\n for (let guard = 0; guard < 4096; guard++) {\n const c = read(i++);\n if (c === undefined || c === 0x80) break;\n if (c === 0x1b) {\n const code = read(i++);\n const n = code === 0x0f || code === 0x0c ? 2 : code === 0x06 || code === 0x0b ? 4 : [7, 8, 9, 10, 13, 14, 16, 17].includes(code) ? 0 : 1;\n const bytes = []; for (let k = 0; k < n; k++) bytes.push(read(i + k).toString(16));\n s += '{' + code.toString(16) + (n ? ':' + bytes.join(',') : '') + '}';\n i += n; continue;\n }\n s += c === 0x8d ? '\\n' : String.fromCharCode(c & 0x7f);\n }\n return { length: i - at, value: s };\n}\n\n// A picture's name: a length with the high bit set, then the name in small letters with\n// underscores for spaces.\nexport function name(read, at) {\n const n = read(at) & 0x7f;\n let s = ''; for (let k = 1; k <= n; k++) s += String.fromCharCode(read(at + k) & 0x7f);\n return { length: n + 1, value: s };\n}\n\n// A sound: the end pitch, the start pitch, the clicks a step, the step and the repeats.\nexport function sound(read, at) {\n const b = []; for (let k = 0; k < 5; k++) b.push(read(at + k).toString(16));\n return { length: 5, value: b.join(' ') };\n}\n\nexport const INLINE = { text, picture: name, sound };\n", "// The engine's objects: the things drawn over a picture and moved about, a wheel, a troll, an\n// animal.\n//\n// A scene points the engine at a table of objects, a list of two-byte addresses, through $685A\n// with the address in X and Y. It then draws an object by its number, counted from 2, at a\n// column and row: through $686D, or through $686F when the ground under it is to be kept so\n// that it can be put back. Numbers 0 and 1 put back the ground kept.\n//\n// An object is four bytes, its flags, the bytes to a row, its width in dots and its height in\n// rows, then its rows, packed one of two ways.\n// - With bit 6 of the flags clear, the rows are double hi-res bytes, seven dots of a bit each,\n// and a byte with its high bit set is a run: the byte less the bit, repeated as many times as\n// the byte after it says.\n// - With bit 6 set, the rows are a nibble a dot, high nibble first, a dot's nibble being its\n// color, and the first byte of all is an escape: where it appears, the two bytes after it are\n// a count and a pair of nibbles to repeat.\n// A run may carry over a row's end.\n//\n// With bit 7 of the flags clear, the color in the low four bits is the object's clear color:\n// where one of its own dots is that color, the ground under it shows through. The routine takes\n// that dot from the ground it kept ($6A7D), and so only when it keeps the ground: through $686F,\n// for object 0 to put back, or through $6863, row by row in a buffer of its own ($6C12).\n// Through $686D it goes from $69C8 straight to putting the object down whole, its clear color\n// and all: PROB6's car, gray where the road is, wipes out where it was as it moves. PROB2's\n// elephants are drawn in black boxes and flagged black, so only the elephants show. With bit 7\n// set the object is put down whole.\n\n// the object at `at` in `bytes`: { flags, stride, width, height, packed, clear, rows, end }\nexport function decode(bytes, at) {\n const flags = bytes[at], stride = bytes[at + 1], width = bytes[at + 2], height = bytes[at + 3];\n let p = at + 4, run = 0, value = 0;\n const rows = [];\n if (flags & 0x40) {\n const escape = bytes[p++];\n const next = () => { if (run) { run--; return value; } const b = bytes[p++]; if (b === escape) { run = bytes[p] - 1; value = bytes[p + 1]; p += 2; return value; } return b; };\n for (let y = 0; y < height; y++) {\n const row = [];\n for (let k = 0; k < (width + 1) >> 1; k++) { const b = next(); row.push(b >> 4, b & 15); }\n rows.push(row.slice(0, width));\n }\n } else {\n const next = () => { if (run) { run--; return value; } const b = bytes[p++]; if (b & 0x80) { value = b & 0x7f; run = bytes[p++] - 1; return value; } return b; };\n for (let y = 0; y < height; y++) {\n const bits = [];\n for (let k = 0; k < stride; k++) { const b = next(); for (let i = 0; i < 7; i++) bits.push((b >> i) & 1); }\n const row = [];\n for (let x = 0; x < width; x++) row.push(bits[x * 4] << 3 | bits[x * 4 + 1] << 2 | bits[x * 4 + 2] << 1 | bits[x * 4 + 3]);\n rows.push(row);\n }\n }\n return { flags, stride, width, height, packed: flags & 0x40 ? 'nibbles' : 'bytes', clear: flags & 0x80 ? undefined : flags & 15, rows, end: p };\n}\n\n// An object put on a screen of rows of colors at column x and row y: every dot where the object\n// is put down, all but the dots of its clear color, where the screen stays as it was; or with\n// `whole`, as $686D puts it, every dot.\nexport function put(scr, object, x, y, whole = false) {\n object.rows.forEach((row, dy) => row.forEach((c, dx) => {\n const r = scr[y + dy];\n if (!r || x + dx < 0 || x + dx >= r.length) return;\n if (whole || c !== object.clear) r[x + dx] = c;\n }));\n return scr;\n}\n\n// Every table the scenes point the engine at, with its objects. `files` is each code file's\n// { name, bytes, origin }, and `setter` the routine a scene calls with the table's address in X\n// and Y. A table's entries are read while each points inside the file that holds it, at four\n// bytes that make sense as an object.\nexport function tables(files, setter) {\n const out = [];\n const word = (b, i) => b[i] | b[i + 1] << 8;\n const holder = address => files.find(f => address >= f.origin && address < f.origin + f.bytes.length);\n for (const f of files) {\n const b = f.bytes;\n for (let i = 0; i + 7 <= b.length; i++) {\n if (!(b[i] === 0xa2 && b[i + 2] === 0xa0 && b[i + 4] === 0x20 && word(b, i + 5) === setter)) continue;\n const address = b[i + 1] | b[i + 3] << 8;\n // the parts all load at one address, and so do the problems: a table is in the file that\n // sets it when it fits there, else in whichever file it does fit, PART0 for one Nature Park uses\n const h = address >= f.origin && address < f.origin + b.length ? f : holder(address);\n if (!h || out.some(t => t.file === h.name && t.address === address)) continue;\n const objects = [];\n for (let n = 2; ; n++) {\n const at = address - h.origin + (n - 2) * 2;\n if (at + 2 > h.bytes.length) break;\n const pointer = word(h.bytes, at);\n if (pointer < h.origin || pointer + 4 > h.origin + h.bytes.length) break;\n const o = pointer - h.origin, width = h.bytes[o + 2], height = h.bytes[o + 3];\n if (!h.bytes[o + 1] || !width || !height || width > 140 || height > 192) break;\n objects.push({ number: n, at: pointer, ...decode(h.bytes, o) });\n }\n if (objects.length) out.push({ file: h.name, setBy: f.name, address, objects });\n }\n }\n return out;\n}\n\n// The carry the object routine leaves, which a scene may add in without clearing it first, as the\n// elephants' problem does when it beeps each apple onto Pip's island ($F47A). The routine builds\n// the object in a buffer and copies it out a row at a time ($6B0F); its pointer starts at\n// $D000 less twice the dot's byte column and one bit of its place in the column, and each row adds\n// the object's bytes to a row and one, with the carry set first. The carry is the last add's.\nexport function carry(object, x) {\n const column = Math.floor(2 * x / 7), place = x % 7; // the tables $17A0 and $182C that $5A17 builds\n let lo = -(2 * column + (place >> 1 & 1)) & 0xff, c = 0;\n for (let row = 0; row < object.height; row++) { const sum = lo + object.stride + 1; c = sum > 0xff ? 1 : 0; lo = sum & 0xff; }\n return c;\n}\n", "// The engine's pictures: named lists of shapes on the 140 by 192 double hi-res screen.\n//\n// PICS1 to PICS3 are each a list of pictures. A picture is its name in 14 bytes, padded with\n// spaces (one, BREAK, has the high bit set on every byte), then two bytes for its length from\n// the start of the name, then its shapes. A shape is two bytes, the color it is filled with and\n// the color of its outline, then its points as (x, y) pairs, x from 1 to 138 and y from 1 down\n// the screen. Shapes are separated by $FE, and $FE $FF ends the picture. A shape of two points\n// is a line in its outline color and is not filled.\n//\n// The picture routine, $7AFE in Animal Rescue, takes a picture's name inline after the call,\n// has the loader copy its shapes to $E000, and draws them from there.\n\nconst text = bytes => String.fromCharCode(...Array.from(bytes, b => b & 0x7f));\n\n// shapes from `read`, a function of an address, starting at `at`, until $FF where the next shape\n// would start: the form the drawing loop at $7B8C reads from $E000\nexport function shapes(read, at, limit = 0x2000) {\n const out = [];\n let shape = null;\n for (let i = at; i < at + limit; i++) {\n const b = read(i);\n if (b === 0xff && !shape) break;\n if (b === 0xfe) { if (shape) out.push(shape); shape = null; continue; }\n if (!shape) { shape = { fill: b, line: read(i + 1), points: [] }; i++; continue; }\n shape.points.push([b, read(i + 1)]); i++;\n }\n if (shape) out.push(shape);\n return out;\n}\n\n// every picture in one of PICS1 to PICS3, as { name, at, length, shapes }\nexport function pictures(bytes) {\n const out = [];\n for (let at = 0; at + 16 <= bytes.length; ) {\n const name = text(bytes.subarray(at, at + 14)).trimEnd();\n const length = bytes[at + 14] | bytes[at + 15] << 8;\n if (length < 18 || at + length > bytes.length) break;\n out.push({ name, at, length, shapes: shapes(i => bytes[i], at + 16, length - 16) });\n at += length;\n }\n return out;\n}\n\n// The color behind each picture, from the scenes' code. A scene calls the box routine with three\n// colors in A, X and Y, the picture window's and then the two text boxes', and then the picture\n// routine with the picture's name inline after the call: a length byte with its high bit set,\n// then the name in small letters with underscores for spaces. `windows` reads every code file\n// for those two calls and gives each name the window color set before it. A scene that draws\n// TRACK 1 to 4 or COL PATTERN 1 to 6 writes `track_` with a byte left for the digit, filled in\n// as it runs, so a name with a trailing digit falls back to the name without it.\nexport function windows(codes, { box, picture }) {\n const seen = new Map();\n const word = (b, i) => b[i] | b[i + 1] << 8;\n for (const code of codes) {\n let color;\n for (let i = 0; i + 8 < code.length; i++) {\n if (code[i] === 0xa9 && code[i + 2] === 0xa2 && code[i + 4] === 0xa0 && code[i + 6] === 0x20 && word(code, i + 7) === box) { color = code[i + 1]; i += 8; continue; }\n if (code[i] === 0x20 && word(code, i + 1) === picture && code[i + 3] & 0x80) {\n const n = code[i + 3] & 0x7f;\n const name = text(code.subarray(i + 4, i + 4 + n)).toUpperCase().replace(/_/g, ' ').replace(/[\\x00-\\x1f]/g, '').trimEnd();\n if (color !== undefined && !seen.has(name)) seen.set(name, color);\n i += 3 + n;\n }\n }\n }\n return name => seen.get(name) ?? seen.get(name.replace(/ ?\\d$/, ''));\n}\n", "// The engine's shapes as SVG, which draws them at any size. A shape is a polygon in its fill\n// color outlined in its outline color, or, with two points, a line. The coordinates are the\n// engine's dots, 140 across and 192 down. A dot is four times as wide on the screen as the\n// number of dots suggests and twice as high, so a whole screen is drawn 560 by 384.\nimport { rgb } from '@moonbase-machines/apple2e/colors.js';\n\n// the columns and rows the shapes cover, a dot beyond them on each side\nexport function bounds(list) {\n let x0 = 140, y0 = 192, x1 = -1, y1 = -1;\n for (const sh of list) for (const [x, y] of sh.points) { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); }\n return x1 < 0 ? null : { x0: Math.max(0, x0 - 1), y0: Math.max(0, y0 - 1), x1: Math.min(139, x1 + 1), y1: Math.min(191, y1 + 1) };\n}\n\n// The SVG elements for the shapes, in the 140 by 192 space. A dot is a unit square, so the\n// points are moved half a unit to the dots' centers and a stroke is a dot wide. With a\n// background, a rectangle of it goes under the shapes where the engine's fills touch dots\n// beside them. `crisp` keeps the edges hard, as the screen had them; without it the shapes are\n// drawn smooth, which is what a sharper picture wants.\nexport function elements(list, { background, crisp = true } = {}) {\n const b = bounds(list), edge = crisp ? ' shape-rendering=\"crispEdges\"' : '';\n let s = b && background !== undefined ? `<rect x=\"${Math.max(0, b.x0 - 1)}\" y=\"${Math.max(0, b.y0 - 1)}\" width=\"${b.x1 - b.x0 + 3}\" height=\"${b.y1 - b.y0 + 3}\" fill=\"${rgb(background)}\"/>` : '';\n for (const sh of list) {\n const pts = sh.points.map(([x, y]) => (x + 0.5) + ',' + (y + 0.5)).join(' ');\n if (sh.points.length === 2) s += `<polyline points=\"${pts}\" fill=\"none\" stroke=\"${rgb(sh.line)}\" stroke-width=\"1\" stroke-linecap=\"square\"${edge}/>`;\n else if (sh.points.length > 2) s += `<polygon points=\"${pts}\" fill=\"${rgb(sh.fill)}\" stroke=\"${rgb(sh.line)}\" stroke-width=\"1\" stroke-linejoin=\"round\"${edge}/>`;\n }\n return s;\n}\n\n// a whole picture as an SVG document, `width` wide and in the screen's proportions, on its\n// background color\nexport function document(list, { background = 0, width = 560, crisp = false } = {}) {\n return `<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 140 192\" width=\"${width}\" height=\"${width * 384 / 560}\" preserveAspectRatio=\"none\">` +\n `<rect width=\"140\" height=\"192\" fill=\"${rgb(background)}\"/>` + elements(list, { crisp }) + '</svg>';\n}\n", "import { colors as m } from '@moonbase-machines/apple2e';\nexport const { COLORS, NAMES, rgb } = m;"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["draw_exports", "__export", "crossings", "draw", "fill", "inside", "line", "outline", "screen", "spans", "under", "background", "x0", "y0", "x1", "y1", "dx", "dy", "sx", "sy", "xMajor", "major", "minor", "err", "x", "y", "out", "k", "points", "rows", "keep", "l", "i", "n", "last", "xa", "ya", "xb", "yb", "dots", "way", "top", "bottom", "lo", "hi", "each", "c", "j", "set", "scr", "shape", "a", "b", "p", "list", "row", "hit", "font_exports", "__export", "GLYPHS", "WIDTHS", "dots", "start", "useFont", "width", "BIG", "ch", "c", "chars", "spacing", "x", "centre", "sum", "hex", "out", "y", "row", "VARIANTS", "BASE", "slug", "w", "rows", "inline_exports", "__export", "INLINE", "name", "sound", "text", "read", "at", "s", "i", "guard", "c", "code", "n", "bytes", "k", "b", "objects_exports", "__export", "carry", "decode", "put", "tables", "bytes", "at", "flags", "stride", "width", "height", "p", "run", "value", "rows", "escape", "next", "b", "y", "row", "k", "bits", "i", "x", "scr", "object", "whole", "dy", "c", "dx", "r", "files", "setter", "out", "word", "holder", "address", "f", "h", "t", "objects", "n", "pointer", "o", "column", "place", "lo", "sum", "pictures_exports", "__export", "pictures", "shapes", "windows", "text", "bytes", "b", "read", "at", "limit", "out", "shape", "i", "name", "length", "codes", "box", "picture", "seen", "word", "code", "color", "n", "svg_exports", "__export", "bounds", "document", "elements", "m", "COLORS", "NAMES", "rgb", "bounds", "list", "x0", "y0", "x1", "y1", "sh", "x", "y", "elements", "background", "crisp", "b", "edge", "s", "rgb", "pts", "document", "width"]
|
|
7
|
+
}
|
package/draw.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// The engine's drawing, dot for dot. Every rule here is the engine's own, read from PART0 and
|
|
2
|
+
// checked by running PART0's code on every shape of every picture of both games (test/).
|
|
3
|
+
// Addresses are Animal Rescue's; Nature Park's engine is the same code moved.
|
|
4
|
+
//
|
|
5
|
+
// A screen is 192 rows of 140 colors. Drawing a shape is its fill, then its outline.
|
|
6
|
+
|
|
7
|
+
// a screen of 192 rows of 140 dots, cleared to `background`
|
|
8
|
+
export const screen = (background = 0) => Array.from({ length: 192 }, () => new Array(140).fill(background));
|
|
9
|
+
|
|
10
|
+
// The line, $7A54: both ends drawn, and the major axis stepped every dot. The error starts at
|
|
11
|
+
// 2·minor − major; while it is negative only the major axis steps and it gains 2·minor,
|
|
12
|
+
// otherwise both axes step and it gains 2·minor − 2·major. Returns the dots in drawing order.
|
|
13
|
+
export function line(x0, y0, x1, y1) {
|
|
14
|
+
const dx = Math.abs(x1 - x0), dy = Math.abs(y1 - y0), sx = x1 >= x0 ? 1 : -1, sy = y1 >= y0 ? 1 : -1;
|
|
15
|
+
const xMajor = dx >= dy, major = xMajor ? dx : dy, minor = xMajor ? dy : dx;
|
|
16
|
+
let err = 2 * minor - major, x = x0, y = y0;
|
|
17
|
+
const out = [[x, y]];
|
|
18
|
+
for (let k = 0; k < major; k++) {
|
|
19
|
+
if (err < 0) { if (xMajor) x += sx; else y += sy; err += 2 * minor; }
|
|
20
|
+
else { x += sx; y += sy; err += 2 * minor - 2 * major; }
|
|
21
|
+
out.push([x, y]);
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// The crossings of a polygon's edges with each row, $7C57 to $7E24. Each row's list is kept
|
|
27
|
+
// sorted, an equal x going after the ones already there ($7E87).
|
|
28
|
+
//
|
|
29
|
+
// - Each edge that is not level is walked with the line's own steps, and each time the walk
|
|
30
|
+
// leaves a row, the x of its last dot on that row is kept for that row. So an edge gives
|
|
31
|
+
// nothing to the row it ends on, and the fill meets the outline's dots exactly.
|
|
32
|
+
// - Where an edge goes the other way up or down from the last edge that was not level, its
|
|
33
|
+
// first point is kept too. The direction starts at neither, so the first such edge always
|
|
34
|
+
// keeps its first point. Level edges are passed over and change nothing.
|
|
35
|
+
// - The rows filled run from the first row with a crossing to the row before the next row with
|
|
36
|
+
// none.
|
|
37
|
+
// - The shape is closed back to its first point, and a crossing is added there when that point
|
|
38
|
+
// is strictly between its neighbours in height and not on the first or last row filled.
|
|
39
|
+
//
|
|
40
|
+
// Returns { rows, top, bottom }, or null for a shape that crosses no row.
|
|
41
|
+
export function crossings(points) {
|
|
42
|
+
const rows = Array.from({ length: 192 }, () => []);
|
|
43
|
+
const keep = (y, x) => { if (y < 0 || y >= 192) return; const l = rows[y]; let i = 0; while (i < l.length && x >= l[i]) i++; l.splice(i, 0, x); };
|
|
44
|
+
const n = points.length;
|
|
45
|
+
let last = 0;
|
|
46
|
+
for (let i = 0; i < n; i++) {
|
|
47
|
+
const [xa, ya] = points[i], [xb, yb] = points[(i + 1) % n];
|
|
48
|
+
if (ya === yb) continue;
|
|
49
|
+
const dots = line(xa, ya, xb, yb);
|
|
50
|
+
for (let k = 1; k < dots.length; k++) if (dots[k][1] !== dots[k - 1][1]) keep(dots[k - 1][1], dots[k - 1][0]);
|
|
51
|
+
const way = yb > ya ? 1 : -1;
|
|
52
|
+
if (way !== last) { last = way; keep(ya, xa); }
|
|
53
|
+
}
|
|
54
|
+
const top = rows.findIndex(l => l.length);
|
|
55
|
+
if (top < 0) return null;
|
|
56
|
+
let bottom = top;
|
|
57
|
+
while (bottom + 1 < 192 && rows[bottom + 1].length) bottom++;
|
|
58
|
+
const [x0, y0] = points[0], lo = Math.min(points[1][1], points[n - 1][1]), hi = Math.max(points[1][1], points[n - 1][1]);
|
|
59
|
+
if (y0 !== top && y0 !== bottom && y0 > lo && y0 < hi) keep(y0, x0);
|
|
60
|
+
return { rows, top, bottom };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// The spans the fill draws, $7E25: on each row the crossings are paired from the end of the
|
|
64
|
+
// list, so a row with an odd number of crossings leaves out its largest x. Calls `each(left,
|
|
65
|
+
// right, y)` for every span, in the engine's order.
|
|
66
|
+
export function spans(points, each) {
|
|
67
|
+
const c = crossings(points);
|
|
68
|
+
if (!c) return;
|
|
69
|
+
for (let y = c.top; y <= c.bottom; y++) {
|
|
70
|
+
const l = c.rows[y];
|
|
71
|
+
for (let j = (l.length & ~1) - 2; j >= 0; j -= 2) each(l[j], l[j + 1], y);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const set = (scr, x, y, c) => { if (x >= 0 && x < 140 && y >= 0 && y < 192) scr[y][x] = c & 15; };
|
|
76
|
+
|
|
77
|
+
// The fill. A span of one dot goes to the dot routine with the wrong color and is drawn black
|
|
78
|
+
// ($7955); the outline, drawn next, always covers it.
|
|
79
|
+
export function fill(scr, shape) {
|
|
80
|
+
if (shape.points.length < 3) return scr;
|
|
81
|
+
spans(shape.points, (a, b, y) => { if (a === b) set(scr, a, y, 0); else for (let x = a; x <= b; x++) set(scr, x, y, shape.fill); });
|
|
82
|
+
return scr;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The outline, $7BAA: every edge in the outline color, closing back to the first point. A shape
|
|
86
|
+
// of two points is one line in the outline color, and its fill color is not used.
|
|
87
|
+
export function outline(scr, shape) {
|
|
88
|
+
const p = shape.points, n = p.length;
|
|
89
|
+
if (n < 2) return scr;
|
|
90
|
+
for (let i = 0; i < (n === 2 ? 1 : n); i++) for (const [x, y] of line(...p[i], ...p[(i + 1) % n])) set(scr, x, y, shape.line);
|
|
91
|
+
return scr;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// a list of shapes, each filled and then outlined, as the loop at $7B8C draws a picture
|
|
95
|
+
export function draw(list, scr = screen()) {
|
|
96
|
+
for (const shape of list) { fill(scr, shape); outline(scr, shape); }
|
|
97
|
+
return scr;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The point test, $7C54: the fill's own spans, with nothing drawn. A point is in a shape when
|
|
101
|
+
// a span on its row has it between its ends, both ends counted. Returns what the engine returns
|
|
102
|
+
// in A: $80 inside, 0 outside, and $FE for a shape of two points, which a caller takes as inside.
|
|
103
|
+
// Nature Park's colouring-in problem uses it to find the shape under the pointer.
|
|
104
|
+
export function inside(shape, x, y) {
|
|
105
|
+
if (shape.points.length === 2) return 0xfe;
|
|
106
|
+
let r = 0;
|
|
107
|
+
spans(shape.points, (a, b, row) => { if (row === y && x >= a && x <= b) r = 0x80; });
|
|
108
|
+
return r;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// The shape under a point: the last of `list` that the point is inside, since later shapes are
|
|
112
|
+
// drawn over earlier ones. Returns its index, or -1.
|
|
113
|
+
export function under(list, x, y) {
|
|
114
|
+
let hit = -1;
|
|
115
|
+
list.forEach((shape, i) => { if (inside(shape, x, y)) hit = i; });
|
|
116
|
+
return hit;
|
|
117
|
+
}
|
package/font.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// The engine's font, measured: the width in dots of each character from space to $7F, the first
|
|
2
|
+
// byte of each glyph, found through the tables of pointers at $7551 and $75BB in Animal Rescue's
|
|
3
|
+
// PART0. A line's width is each character's width and the spacing after it; a line is centred
|
|
4
|
+
// at (140 − its width) / 2, worked in a byte as the engine did ($6F4D).
|
|
5
|
+
export const WIDTHS = [
|
|
6
|
+
3, 1, 3, 5, 5, 5, 4, 1, 3, 3, 5, 5, 2, 5, 1, 5, 4, 3, 4, 4, 4, 4, 4, 4, 4, 4, 1, 2, 3, 3, 3, 4,
|
|
7
|
+
8, 4, 4, 4, 4, 4, 4, 4, 4, 3, 4, 4, 4, 5, 4, 4, 4, 4, 4, 4, 3, 4, 5, 5, 4, 5, 4, 5, 5, 5, 5, 5,
|
|
8
|
+
3, 4, 4, 4, 4, 4, 3, 4, 4, 3, 4, 4, 3, 5, 4, 4, 4, 4, 4, 4, 3, 4, 5, 5, 4, 4, 4, 3, 1, 3, 4, 5,
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
// The text's code $10 turns to a second set of digits, bigger, read through the same tables at
|
|
12
|
+
// $80 to $89 (the digit plus $50, $63FD in Nature Park), and $11 back; each text starts with the
|
|
13
|
+
// first. Both engines have the same: six dots wide, the one four, nine rows.
|
|
14
|
+
const BIG = [[6, '78ccccdceccccccc78'], [4, '60e0606060606060f0'], [6, '78cc0c183060c0c0fc'], [6, '78cc0c0c380c0ccc78'], [6, '0c1c2c4cccfc0c0c0c'],
|
|
15
|
+
[6, 'fcc0c0f80c0c0ccc78'], [6, '78ccc0c0f8cccccc78'], [6, 'fc0c0c181830303030'], [6, '78cccccc78cccccc78'], [6, '78cccccc7c0c0ccc78']];
|
|
16
|
+
|
|
17
|
+
// a character's width in dots; a byte below a space is a spacer that takes its own value
|
|
18
|
+
export const width = ch => { const c = ch.charCodeAt(0); return c < 0x20 ? c : c >= 0x80 ? BIG[c - 0x80]?.[0] ?? 6 : WIDTHS[c - 0x20] ?? 4; };
|
|
19
|
+
|
|
20
|
+
// where a line of characters starts, in dots: its own x, or centred as the engine centres
|
|
21
|
+
export function start(chars, spacing, x, centre) {
|
|
22
|
+
if (!centre) return x;
|
|
23
|
+
let sum = 0;
|
|
24
|
+
for (const ch of chars) sum += ch.charCodeAt(0) < 0x20 ? width(ch) : width(ch) + spacing;
|
|
25
|
+
return ((0x8c - sum) & 0xff) >> 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Each character's glyph, space to $7F: nine rows from the top, a byte a row, its high bit the
|
|
29
|
+
// leftmost dot, as the glyph follows its width in PART0. Written in hexadecimal, two digits a row.
|
|
30
|
+
export const GLYPHS = [
|
|
31
|
+
'000000000000000000', '808080808000800000', 'a0a0a0000000000000', '0050f850f850000000', '207080700870200000', '808810204088080000', '609060909090700000', '808080000000000000',
|
|
32
|
+
'204080808040200000', '804020202040800000', '008850205088000000', '002020f82020000000', '000000000000408000', '000000f80000000000', '000000000080800000', '000810204080000000',
|
|
33
|
+
'6090b0d09090600000', '40c040404040e00000', '609010608080f00000', '609010201090600000', '10305090f010100000', 'f080e0101090600000', '609080e09090600000', 'f01010202040400000',
|
|
34
|
+
'609090609090600000', '609090701090600000', '000080800080800000', '000040400040404080', '002040804020000000', '0000e000e000000000', '008040204080000000', '609010204000400000',
|
|
35
|
+
'ffffffffffffffffff', '609090f09090900000', 'e09090e09090e00000', '609080808090600000', 'e09090909090e00000', 'f08080c08080f00000', 'f08080c08080800000', '609080b09090700000',
|
|
36
|
+
'909090f09090900000', 'e04040404040e00000', '301010101090600000', '90a0c0a0a090900000', '808080808080f00000', '88d8a8888888880000', '90d0b0909090900000', '609090909090600000',
|
|
37
|
+
'e09090e08080800000', '60909090b0b0700000', 'e09090e09090900000', '609080601090600000', 'e04040404040400000', '909090909090600000', '888888888850200000', '88888888a8d8880000',
|
|
38
|
+
'909090609090900000', '888850202020200000', 'f01020408080f00000', '002060f86020000000', '002030f83020000000', '002020f87020000000', '002070f82020000000', '0000000000000000f8',
|
|
39
|
+
'804020000000000000', '0000709090b0500000', '8080e0909090e00000', '000060908090600000', '101070909090700000', '00006090f080600000', '2040e0404040400000', '0000709090907010e0',
|
|
40
|
+
'8080e0909090900000', '4000c0404040e00000', '2000602020202020c0', '808090a0c0a0900000', '804040404040e00000', '0000d0a8a8a8a80000', '0000e0909090900000', '000060909090600000',
|
|
41
|
+
'0000e0909090e08080', '000070909090701010', '0000e0908080800000', '000070806010e00000', '4040e0404040200000', '000090909090700000', '000088888850200000', '00008888a8d8880000',
|
|
42
|
+
'000090906090900000', '0000909090907010e0', '0000f0106080f00000', '204040804040200000', '808080808080808080', '804040204040800000', '106080000000000000', 'ff0000000000000000',
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
// the dots of a character's glyph, as [x, y] from its top left
|
|
46
|
+
export function dots(ch) {
|
|
47
|
+
const c = ch.charCodeAt(0), hex = c >= 0x80 ? BIG[c - 0x80]?.[1] : GLYPHS[c - 0x20], out = [];
|
|
48
|
+
if (!hex) return out;
|
|
49
|
+
for (let y = 0; y < 9; y++) { const row = parseInt(hex.slice(2 * y, 2 * y + 2), 16); for (let x = 0; x < 8; x++) if (row >> (7 - x) & 1) out.push([x, y]); }
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Nature Park's font is Animal Rescue's but for two glyphs, read from its PART0 through its tables
|
|
54
|
+
// at $6A32 and $6A9C: a zero without the stroke, and the underline a band of seven dots, which its
|
|
55
|
+
// menus put either side of their names. `useFont` makes a title's font the one these read.
|
|
56
|
+
const VARIANTS = { 'nature-park-adventure': { '0': [4, '609090909090600000'], '_': [7, '000054aa5400000000'] } };
|
|
57
|
+
const BASE = { widths: WIDTHS.slice(), glyphs: GLYPHS.slice() };
|
|
58
|
+
export function useFont(slug) {
|
|
59
|
+
WIDTHS.splice(0, WIDTHS.length, ...BASE.widths); GLYPHS.splice(0, GLYPHS.length, ...BASE.glyphs);
|
|
60
|
+
for (const [ch, [w, rows]] of Object.entries(VARIANTS[slug] ?? {})) { WIDTHS[ch.charCodeAt(0) - 0x20] = w; GLYPHS[ch.charCodeAt(0) - 0x20] = rows; }
|
|
61
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// @moonbase-machines/vector: Mark Wharton's vector graphics engine, every module by name, for one bundled file
|
|
2
|
+
// or to take the whole package at once: import { draw } from '@moonbase-machines/vector'. Each module can be
|
|
3
|
+
// imported on its own too: '@moonbase-machines/vector/<module>.js'.
|
|
4
|
+
export * as draw from './draw.js';
|
|
5
|
+
export * as font from './font.js';
|
|
6
|
+
export * as inline from './inline.js';
|
|
7
|
+
export * as objects from './objects.js';
|
|
8
|
+
export * as pictures from './pictures.js';
|
|
9
|
+
export * as svg from './svg.js';
|
package/inline.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// The data the engine's routines take inline, after the call: the text routine's text, the
|
|
2
|
+
// picture routine's name and the sound routine's five bytes. `read` gives the byte at an
|
|
3
|
+
// address, so the same reading works on a file and on a running machine's memory. Each returns
|
|
4
|
+
// { length, value }: how many bytes the data takes, and what it says.
|
|
5
|
+
|
|
6
|
+
// Text: ASCII with the high bit set, $8D a new line, $80 the end. $1B comes before a code, and a
|
|
7
|
+
// code takes 0, 1, 2 or 4 bytes after it: $0F and $0C take two, $06 and $0B four, $07 to $0A,
|
|
8
|
+
// $0D, $0E, $10 and $11 none, and every other code one. A code is kept in the text in braces,
|
|
9
|
+
// {6:42,86,69,86}, its number and its bytes in hexadecimal, so that nothing is lost.
|
|
10
|
+
export function text(read, at) {
|
|
11
|
+
let s = '', i = at;
|
|
12
|
+
for (let guard = 0; guard < 4096; guard++) {
|
|
13
|
+
const c = read(i++);
|
|
14
|
+
if (c === undefined || c === 0x80) break;
|
|
15
|
+
if (c === 0x1b) {
|
|
16
|
+
const code = read(i++);
|
|
17
|
+
const n = code === 0x0f || code === 0x0c ? 2 : code === 0x06 || code === 0x0b ? 4 : [7, 8, 9, 10, 13, 14, 16, 17].includes(code) ? 0 : 1;
|
|
18
|
+
const bytes = []; for (let k = 0; k < n; k++) bytes.push(read(i + k).toString(16));
|
|
19
|
+
s += '{' + code.toString(16) + (n ? ':' + bytes.join(',') : '') + '}';
|
|
20
|
+
i += n; continue;
|
|
21
|
+
}
|
|
22
|
+
s += c === 0x8d ? '\n' : String.fromCharCode(c & 0x7f);
|
|
23
|
+
}
|
|
24
|
+
return { length: i - at, value: s };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// A picture's name: a length with the high bit set, then the name in small letters with
|
|
28
|
+
// underscores for spaces.
|
|
29
|
+
export function name(read, at) {
|
|
30
|
+
const n = read(at) & 0x7f;
|
|
31
|
+
let s = ''; for (let k = 1; k <= n; k++) s += String.fromCharCode(read(at + k) & 0x7f);
|
|
32
|
+
return { length: n + 1, value: s };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// A sound: the end pitch, the start pitch, the clicks a step, the step and the repeats.
|
|
36
|
+
export function sound(read, at) {
|
|
37
|
+
const b = []; for (let k = 0; k < 5; k++) b.push(read(at + k).toString(16));
|
|
38
|
+
return { length: 5, value: b.join(' ') };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const INLINE = { text, picture: name, sound };
|
package/objects.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// The engine's objects: the things drawn over a picture and moved about, a wheel, a troll, an
|
|
2
|
+
// animal.
|
|
3
|
+
//
|
|
4
|
+
// A scene points the engine at a table of objects, a list of two-byte addresses, through $685A
|
|
5
|
+
// with the address in X and Y. It then draws an object by its number, counted from 2, at a
|
|
6
|
+
// column and row: through $686D, or through $686F when the ground under it is to be kept so
|
|
7
|
+
// that it can be put back. Numbers 0 and 1 put back the ground kept.
|
|
8
|
+
//
|
|
9
|
+
// An object is four bytes, its flags, the bytes to a row, its width in dots and its height in
|
|
10
|
+
// rows, then its rows, packed one of two ways.
|
|
11
|
+
// - With bit 6 of the flags clear, the rows are double hi-res bytes, seven dots of a bit each,
|
|
12
|
+
// and a byte with its high bit set is a run: the byte less the bit, repeated as many times as
|
|
13
|
+
// the byte after it says.
|
|
14
|
+
// - With bit 6 set, the rows are a nibble a dot, high nibble first, a dot's nibble being its
|
|
15
|
+
// color, and the first byte of all is an escape: where it appears, the two bytes after it are
|
|
16
|
+
// a count and a pair of nibbles to repeat.
|
|
17
|
+
// A run may carry over a row's end.
|
|
18
|
+
//
|
|
19
|
+
// With bit 7 of the flags clear, the color in the low four bits is the object's clear color:
|
|
20
|
+
// where one of its own dots is that color, the ground under it shows through. The routine takes
|
|
21
|
+
// that dot from the ground it kept ($6A7D), and so only when it keeps the ground: through $686F,
|
|
22
|
+
// for object 0 to put back, or through $6863, row by row in a buffer of its own ($6C12).
|
|
23
|
+
// Through $686D it goes from $69C8 straight to putting the object down whole, its clear color
|
|
24
|
+
// and all: PROB6's car, gray where the road is, wipes out where it was as it moves. PROB2's
|
|
25
|
+
// elephants are drawn in black boxes and flagged black, so only the elephants show. With bit 7
|
|
26
|
+
// set the object is put down whole.
|
|
27
|
+
|
|
28
|
+
// the object at `at` in `bytes`: { flags, stride, width, height, packed, clear, rows, end }
|
|
29
|
+
export function decode(bytes, at) {
|
|
30
|
+
const flags = bytes[at], stride = bytes[at + 1], width = bytes[at + 2], height = bytes[at + 3];
|
|
31
|
+
let p = at + 4, run = 0, value = 0;
|
|
32
|
+
const rows = [];
|
|
33
|
+
if (flags & 0x40) {
|
|
34
|
+
const escape = bytes[p++];
|
|
35
|
+
const next = () => { if (run) { run--; return value; } const b = bytes[p++]; if (b === escape) { run = bytes[p] - 1; value = bytes[p + 1]; p += 2; return value; } return b; };
|
|
36
|
+
for (let y = 0; y < height; y++) {
|
|
37
|
+
const row = [];
|
|
38
|
+
for (let k = 0; k < (width + 1) >> 1; k++) { const b = next(); row.push(b >> 4, b & 15); }
|
|
39
|
+
rows.push(row.slice(0, width));
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
const next = () => { if (run) { run--; return value; } const b = bytes[p++]; if (b & 0x80) { value = b & 0x7f; run = bytes[p++] - 1; return value; } return b; };
|
|
43
|
+
for (let y = 0; y < height; y++) {
|
|
44
|
+
const bits = [];
|
|
45
|
+
for (let k = 0; k < stride; k++) { const b = next(); for (let i = 0; i < 7; i++) bits.push((b >> i) & 1); }
|
|
46
|
+
const row = [];
|
|
47
|
+
for (let x = 0; x < width; x++) row.push(bits[x * 4] << 3 | bits[x * 4 + 1] << 2 | bits[x * 4 + 2] << 1 | bits[x * 4 + 3]);
|
|
48
|
+
rows.push(row);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return { flags, stride, width, height, packed: flags & 0x40 ? 'nibbles' : 'bytes', clear: flags & 0x80 ? undefined : flags & 15, rows, end: p };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// An object put on a screen of rows of colors at column x and row y: every dot where the object
|
|
55
|
+
// is put down, all but the dots of its clear color, where the screen stays as it was; or with
|
|
56
|
+
// `whole`, as $686D puts it, every dot.
|
|
57
|
+
export function put(scr, object, x, y, whole = false) {
|
|
58
|
+
object.rows.forEach((row, dy) => row.forEach((c, dx) => {
|
|
59
|
+
const r = scr[y + dy];
|
|
60
|
+
if (!r || x + dx < 0 || x + dx >= r.length) return;
|
|
61
|
+
if (whole || c !== object.clear) r[x + dx] = c;
|
|
62
|
+
}));
|
|
63
|
+
return scr;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Every table the scenes point the engine at, with its objects. `files` is each code file's
|
|
67
|
+
// { name, bytes, origin }, and `setter` the routine a scene calls with the table's address in X
|
|
68
|
+
// and Y. A table's entries are read while each points inside the file that holds it, at four
|
|
69
|
+
// bytes that make sense as an object.
|
|
70
|
+
export function tables(files, setter) {
|
|
71
|
+
const out = [];
|
|
72
|
+
const word = (b, i) => b[i] | b[i + 1] << 8;
|
|
73
|
+
const holder = address => files.find(f => address >= f.origin && address < f.origin + f.bytes.length);
|
|
74
|
+
for (const f of files) {
|
|
75
|
+
const b = f.bytes;
|
|
76
|
+
for (let i = 0; i + 7 <= b.length; i++) {
|
|
77
|
+
if (!(b[i] === 0xa2 && b[i + 2] === 0xa0 && b[i + 4] === 0x20 && word(b, i + 5) === setter)) continue;
|
|
78
|
+
const address = b[i + 1] | b[i + 3] << 8;
|
|
79
|
+
// the parts all load at one address, and so do the problems: a table is in the file that
|
|
80
|
+
// sets it when it fits there, else in whichever file it does fit, PART0 for one Nature Park uses
|
|
81
|
+
const h = address >= f.origin && address < f.origin + b.length ? f : holder(address);
|
|
82
|
+
if (!h || out.some(t => t.file === h.name && t.address === address)) continue;
|
|
83
|
+
const objects = [];
|
|
84
|
+
for (let n = 2; ; n++) {
|
|
85
|
+
const at = address - h.origin + (n - 2) * 2;
|
|
86
|
+
if (at + 2 > h.bytes.length) break;
|
|
87
|
+
const pointer = word(h.bytes, at);
|
|
88
|
+
if (pointer < h.origin || pointer + 4 > h.origin + h.bytes.length) break;
|
|
89
|
+
const o = pointer - h.origin, width = h.bytes[o + 2], height = h.bytes[o + 3];
|
|
90
|
+
if (!h.bytes[o + 1] || !width || !height || width > 140 || height > 192) break;
|
|
91
|
+
objects.push({ number: n, at: pointer, ...decode(h.bytes, o) });
|
|
92
|
+
}
|
|
93
|
+
if (objects.length) out.push({ file: h.name, setBy: f.name, address, objects });
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The carry the object routine leaves, which a scene may add in without clearing it first, as the
|
|
100
|
+
// elephants' problem does when it beeps each apple onto Pip's island ($F47A). The routine builds
|
|
101
|
+
// the object in a buffer and copies it out a row at a time ($6B0F); its pointer starts at
|
|
102
|
+
// $D000 less twice the dot's byte column and one bit of its place in the column, and each row adds
|
|
103
|
+
// the object's bytes to a row and one, with the carry set first. The carry is the last add's.
|
|
104
|
+
export function carry(object, x) {
|
|
105
|
+
const column = Math.floor(2 * x / 7), place = x % 7; // the tables $17A0 and $182C that $5A17 builds
|
|
106
|
+
let lo = -(2 * column + (place >> 1 & 1)) & 0xff, c = 0;
|
|
107
|
+
for (let row = 0; row < object.height; row++) { const sum = lo + object.stride + 1; c = sum > 0xff ? 1 : 0; lo = sum & 0xff; }
|
|
108
|
+
return c;
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moonbase-machines/vector",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Mark Wharton's vector graphics engine for the Apple IIe (Animal Rescue, Nature Park Adventure, 1989): its pictures, objects, font and drawing, dot for dot.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Mark Wharton",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./index.js",
|
|
10
|
+
"./*": "./*"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@moonbase-machines/apple2e": "0.6.0"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "node --test test/*.test.js"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/pictures.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// The engine's pictures: named lists of shapes on the 140 by 192 double hi-res screen.
|
|
2
|
+
//
|
|
3
|
+
// PICS1 to PICS3 are each a list of pictures. A picture is its name in 14 bytes, padded with
|
|
4
|
+
// spaces (one, BREAK, has the high bit set on every byte), then two bytes for its length from
|
|
5
|
+
// the start of the name, then its shapes. A shape is two bytes, the color it is filled with and
|
|
6
|
+
// the color of its outline, then its points as (x, y) pairs, x from 1 to 138 and y from 1 down
|
|
7
|
+
// the screen. Shapes are separated by $FE, and $FE $FF ends the picture. A shape of two points
|
|
8
|
+
// is a line in its outline color and is not filled.
|
|
9
|
+
//
|
|
10
|
+
// The picture routine, $7AFE in Animal Rescue, takes a picture's name inline after the call,
|
|
11
|
+
// has the loader copy its shapes to $E000, and draws them from there.
|
|
12
|
+
|
|
13
|
+
const text = bytes => String.fromCharCode(...Array.from(bytes, b => b & 0x7f));
|
|
14
|
+
|
|
15
|
+
// shapes from `read`, a function of an address, starting at `at`, until $FF where the next shape
|
|
16
|
+
// would start: the form the drawing loop at $7B8C reads from $E000
|
|
17
|
+
export function shapes(read, at, limit = 0x2000) {
|
|
18
|
+
const out = [];
|
|
19
|
+
let shape = null;
|
|
20
|
+
for (let i = at; i < at + limit; i++) {
|
|
21
|
+
const b = read(i);
|
|
22
|
+
if (b === 0xff && !shape) break;
|
|
23
|
+
if (b === 0xfe) { if (shape) out.push(shape); shape = null; continue; }
|
|
24
|
+
if (!shape) { shape = { fill: b, line: read(i + 1), points: [] }; i++; continue; }
|
|
25
|
+
shape.points.push([b, read(i + 1)]); i++;
|
|
26
|
+
}
|
|
27
|
+
if (shape) out.push(shape);
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// every picture in one of PICS1 to PICS3, as { name, at, length, shapes }
|
|
32
|
+
export function pictures(bytes) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let at = 0; at + 16 <= bytes.length; ) {
|
|
35
|
+
const name = text(bytes.subarray(at, at + 14)).trimEnd();
|
|
36
|
+
const length = bytes[at + 14] | bytes[at + 15] << 8;
|
|
37
|
+
if (length < 18 || at + length > bytes.length) break;
|
|
38
|
+
out.push({ name, at, length, shapes: shapes(i => bytes[i], at + 16, length - 16) });
|
|
39
|
+
at += length;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The color behind each picture, from the scenes' code. A scene calls the box routine with three
|
|
45
|
+
// colors in A, X and Y, the picture window's and then the two text boxes', and then the picture
|
|
46
|
+
// routine with the picture's name inline after the call: a length byte with its high bit set,
|
|
47
|
+
// then the name in small letters with underscores for spaces. `windows` reads every code file
|
|
48
|
+
// for those two calls and gives each name the window color set before it. A scene that draws
|
|
49
|
+
// TRACK 1 to 4 or COL PATTERN 1 to 6 writes `track_` with a byte left for the digit, filled in
|
|
50
|
+
// as it runs, so a name with a trailing digit falls back to the name without it.
|
|
51
|
+
export function windows(codes, { box, picture }) {
|
|
52
|
+
const seen = new Map();
|
|
53
|
+
const word = (b, i) => b[i] | b[i + 1] << 8;
|
|
54
|
+
for (const code of codes) {
|
|
55
|
+
let color;
|
|
56
|
+
for (let i = 0; i + 8 < code.length; i++) {
|
|
57
|
+
if (code[i] === 0xa9 && code[i + 2] === 0xa2 && code[i + 4] === 0xa0 && code[i + 6] === 0x20 && word(code, i + 7) === box) { color = code[i + 1]; i += 8; continue; }
|
|
58
|
+
if (code[i] === 0x20 && word(code, i + 1) === picture && code[i + 3] & 0x80) {
|
|
59
|
+
const n = code[i + 3] & 0x7f;
|
|
60
|
+
const name = text(code.subarray(i + 4, i + 4 + n)).toUpperCase().replace(/_/g, ' ').replace(/[\x00-\x1f]/g, '').trimEnd();
|
|
61
|
+
if (color !== undefined && !seen.has(name)) seen.set(name, color);
|
|
62
|
+
i += 3 + n;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return name => seen.get(name) ?? seen.get(name.replace(/ ?\d$/, ''));
|
|
67
|
+
}
|
package/svg.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// The engine's shapes as SVG, which draws them at any size. A shape is a polygon in its fill
|
|
2
|
+
// color outlined in its outline color, or, with two points, a line. The coordinates are the
|
|
3
|
+
// engine's dots, 140 across and 192 down. A dot is four times as wide on the screen as the
|
|
4
|
+
// number of dots suggests and twice as high, so a whole screen is drawn 560 by 384.
|
|
5
|
+
import { rgb } from '@moonbase-machines/apple2e/colors.js';
|
|
6
|
+
|
|
7
|
+
// the columns and rows the shapes cover, a dot beyond them on each side
|
|
8
|
+
export function bounds(list) {
|
|
9
|
+
let x0 = 140, y0 = 192, x1 = -1, y1 = -1;
|
|
10
|
+
for (const sh of list) for (const [x, y] of sh.points) { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); }
|
|
11
|
+
return x1 < 0 ? null : { x0: Math.max(0, x0 - 1), y0: Math.max(0, y0 - 1), x1: Math.min(139, x1 + 1), y1: Math.min(191, y1 + 1) };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// The SVG elements for the shapes, in the 140 by 192 space. A dot is a unit square, so the
|
|
15
|
+
// points are moved half a unit to the dots' centers and a stroke is a dot wide. With a
|
|
16
|
+
// background, a rectangle of it goes under the shapes where the engine's fills touch dots
|
|
17
|
+
// beside them. `crisp` keeps the edges hard, as the screen had them; without it the shapes are
|
|
18
|
+
// drawn smooth, which is what a sharper picture wants.
|
|
19
|
+
export function elements(list, { background, crisp = true } = {}) {
|
|
20
|
+
const b = bounds(list), edge = crisp ? ' shape-rendering="crispEdges"' : '';
|
|
21
|
+
let s = b && background !== undefined ? `<rect x="${Math.max(0, b.x0 - 1)}" y="${Math.max(0, b.y0 - 1)}" width="${b.x1 - b.x0 + 3}" height="${b.y1 - b.y0 + 3}" fill="${rgb(background)}"/>` : '';
|
|
22
|
+
for (const sh of list) {
|
|
23
|
+
const pts = sh.points.map(([x, y]) => (x + 0.5) + ',' + (y + 0.5)).join(' ');
|
|
24
|
+
if (sh.points.length === 2) s += `<polyline points="${pts}" fill="none" stroke="${rgb(sh.line)}" stroke-width="1" stroke-linecap="square"${edge}/>`;
|
|
25
|
+
else if (sh.points.length > 2) s += `<polygon points="${pts}" fill="${rgb(sh.fill)}" stroke="${rgb(sh.line)}" stroke-width="1" stroke-linejoin="round"${edge}/>`;
|
|
26
|
+
}
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// a whole picture as an SVG document, `width` wide and in the screen's proportions, on its
|
|
31
|
+
// background color
|
|
32
|
+
export function document(list, { background = 0, width = 560, crisp = false } = {}) {
|
|
33
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 140 192" width="${width}" height="${width * 384 / 560}" preserveAspectRatio="none">` +
|
|
34
|
+
`<rect width="140" height="192" fill="${rgb(background)}"/>` + elements(list, { crisp }) + '</svg>';
|
|
35
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// The engine's formats read and its drawing done, from bytes the tests build, nothing of a title's: pictures
|
|
2
|
+
// and their shapes, a filled shape and a line drawn, objects packed both ways and put down, a picture's window
|
|
3
|
+
// colour found in code, the shapes as SVG. (PART0's own drawing against draw.js runs where the games' disks are.)
|
|
4
|
+
//
|
|
5
|
+
// node --test test/*.test.js
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import { pictures, windows } from '@moonbase-machines/vector/pictures.js';
|
|
9
|
+
import { draw, inside, screen } from '@moonbase-machines/vector/draw.js';
|
|
10
|
+
import { decode, put } from '@moonbase-machines/vector/objects.js';
|
|
11
|
+
import { document } from '@moonbase-machines/vector/svg.js';
|
|
12
|
+
|
|
13
|
+
const name14 = s => [...s.padEnd(14)].map(c => c.charCodeAt(0));
|
|
14
|
+
const picture = (name, shapes) => { const body = shapes.flatMap((s, i) => [...(i ? [0xfe] : []), s.fill, s.line, ...s.points.flat()]).concat([0xfe, 0xff]); const length = 16 + body.length; return [...name14(name), length & 0xff, length >> 8, ...body]; };
|
|
15
|
+
|
|
16
|
+
test('a list of pictures read: each name, and its shapes with their colours and points', () => {
|
|
17
|
+
const box = { fill: 5, line: 15, points: [[10, 10], [30, 10], [30, 30], [10, 30]] }, wire = { fill: 0, line: 6, points: [[1, 1], [40, 40]] };
|
|
18
|
+
const list = pictures(Uint8Array.from([...picture('HOUSE', [box]), ...picture('WIRE', [wire])]));
|
|
19
|
+
assert.deepEqual(list.map(p => p.name), ['HOUSE', 'WIRE']);
|
|
20
|
+
assert.deepEqual(list[0].shapes, [box]);
|
|
21
|
+
assert.deepEqual(list[1].shapes, [wire]);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('a shape filled and outlined as the engine draws it; a shape of two points a line, not filled', () => {
|
|
25
|
+
const box = { fill: 5, line: 15, points: [[10, 10], [30, 10], [30, 30], [10, 30]] };
|
|
26
|
+
const scr = draw([box, { fill: 3, line: 6, points: [[50, 50], [60, 50]] }], screen(0));
|
|
27
|
+
assert.equal(scr[20][20], 5, 'inside: the fill');
|
|
28
|
+
assert.equal(scr[10][20], 15, 'its edge: the outline');
|
|
29
|
+
assert.equal(scr[40][40], 0, 'outside: the ground');
|
|
30
|
+
assert.equal(scr[50][55], 6, 'the line in its line colour');
|
|
31
|
+
assert.ok(inside(box, 20, 20), 'the point test: inside'); assert.ok(!inside(box, 40, 40), 'and outside');
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('an object packed as nibbles, with its escape run, and one packed as bytes with a run', () => {
|
|
35
|
+
// nibbles: flags $43 (bit 6, clear colour 3), 2 bytes a row, 4 dots, 2 rows; escape $EE; row 2 a run of $56 twice
|
|
36
|
+
const n = decode(Uint8Array.from([0x43, 2, 4, 2, 0xee, 0x12, 0x34, 0xee, 2, 0x56]), 0);
|
|
37
|
+
assert.equal(n.packed, 'nibbles'); assert.equal(n.clear, 3);
|
|
38
|
+
assert.deepEqual(n.rows, [[1, 2, 3, 4], [5, 6, 5, 6]]);
|
|
39
|
+
// bytes: flags $80 (put down whole), a byte a row, 1 dot, 2 rows: $0F (colour 15), then a run of 0 once
|
|
40
|
+
const b = decode(Uint8Array.from([0x80, 1, 1, 2, 0x0f, 0x80, 1]), 0);
|
|
41
|
+
assert.equal(b.packed, 'bytes'); assert.equal(b.clear, undefined);
|
|
42
|
+
assert.deepEqual(b.rows, [[15], [0]]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test('an object put down: its clear colour lets the ground show, unless it is put down whole', () => {
|
|
46
|
+
const object = decode(Uint8Array.from([0x43, 2, 4, 1, 0xee, 0x12, 0x34]), 0);
|
|
47
|
+
const scr = screen(9);
|
|
48
|
+
put(scr, object, 10, 20);
|
|
49
|
+
assert.deepEqual(scr[20].slice(10, 14), [1, 2, 9, 4], 'its colour 3 not put down');
|
|
50
|
+
put(scr, object, 10, 20, true);
|
|
51
|
+
assert.deepEqual(scr[20].slice(10, 14), [1, 2, 3, 4], 'whole, every dot');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('a picture\'s window colour, from the box call before the picture call, a trailing digit falling back', () => {
|
|
55
|
+
const BOX = 0x65d2, PICTURE = 0x7afe;
|
|
56
|
+
const code = Uint8Array.from([0xa9, 7, 0xa2, 1, 0xa0, 2, 0x20, BOX & 0xff, BOX >> 8, 0x20, PICTURE & 0xff, PICTURE >> 8, 0x83, ...'hut'.split('').map(c => c.charCodeAt(0)), 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea]);
|
|
57
|
+
const colour = windows([code], { box: BOX, picture: PICTURE });
|
|
58
|
+
assert.equal(colour('HUT'), 7); assert.equal(colour('HUT 2'), 7); assert.equal(colour('BARN'), undefined);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('the shapes as SVG, each in its colour', () => {
|
|
62
|
+
const svg = document([{ fill: 5, line: 15, points: [[10, 10], [30, 10], [30, 30], [10, 30]] }]);
|
|
63
|
+
assert.match(svg, /^<svg/);
|
|
64
|
+
assert.match(svg, /fill="rgb\(156,156,156\)" stroke="rgb\(255,255,255\)"/, 'colour 5, gray, filled; 15, white, its outline');
|
|
65
|
+
});
|