@guinetik/gcanvas 1.0.2 → 1.0.4
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/dist/gcanvas.es.js +25656 -0
- package/dist/gcanvas.es.min.js +1 -0
- package/dist/gcanvas.umd.js +1 -0
- package/dist/gcanvas.umd.min.js +1 -0
- package/package.json +23 -6
- package/src/game/objects/index.js +1 -0
- package/src/game/objects/spritesheet.js +260 -0
- package/src/game/ui/theme.js +6 -0
- package/src/io/keys.js +9 -1
- package/src/math/boolean.js +481 -0
- package/src/math/index.js +1 -0
- package/.github/workflows/release.yaml +0 -70
- package/.jshintrc +0 -4
- package/.vscode/settings.json +0 -22
- package/CLAUDE.md +0 -310
- package/blackhole.jpg +0 -0
- package/demo.png +0 -0
- package/demos/CNAME +0 -1
- package/demos/animations.html +0 -31
- package/demos/basic.html +0 -38
- package/demos/baskara.html +0 -31
- package/demos/bezier.html +0 -35
- package/demos/beziersignature.html +0 -29
- package/demos/blackhole.html +0 -28
- package/demos/blob.html +0 -35
- package/demos/coordinates.html +0 -698
- package/demos/cube3d.html +0 -23
- package/demos/demos.css +0 -303
- package/demos/dino.html +0 -42
- package/demos/easing.html +0 -28
- package/demos/events.html +0 -195
- package/demos/fluent.html +0 -647
- package/demos/fluid-simple.html +0 -22
- package/demos/fluid.html +0 -37
- package/demos/fractals.html +0 -36
- package/demos/gameobjects.html +0 -626
- package/demos/genart.html +0 -26
- package/demos/gendream.html +0 -26
- package/demos/group.html +0 -36
- package/demos/home.html +0 -587
- package/demos/index.html +0 -376
- package/demos/isometric.html +0 -34
- package/demos/js/animations.js +0 -452
- package/demos/js/basic.js +0 -204
- package/demos/js/baskara.js +0 -751
- package/demos/js/bezier.js +0 -692
- package/demos/js/beziersignature.js +0 -241
- package/demos/js/blackhole/accretiondisk.obj.js +0 -379
- package/demos/js/blackhole/blackhole.obj.js +0 -318
- package/demos/js/blackhole/index.js +0 -409
- package/demos/js/blackhole/particle.js +0 -56
- package/demos/js/blackhole/starfield.obj.js +0 -218
- package/demos/js/blob.js +0 -2276
- package/demos/js/coordinates.js +0 -840
- package/demos/js/cube3d.js +0 -789
- package/demos/js/dino.js +0 -1420
- package/demos/js/easing.js +0 -477
- package/demos/js/fluent.js +0 -183
- package/demos/js/fluid-simple.js +0 -253
- package/demos/js/fluid.js +0 -527
- package/demos/js/fractals.js +0 -931
- package/demos/js/fractalworker.js +0 -93
- package/demos/js/gameobjects.js +0 -176
- package/demos/js/genart.js +0 -268
- package/demos/js/gendream.js +0 -209
- package/demos/js/group.js +0 -140
- package/demos/js/info-toggle.js +0 -25
- package/demos/js/isometric.js +0 -863
- package/demos/js/kerr.js +0 -1556
- package/demos/js/lavalamp.js +0 -590
- package/demos/js/layout.js +0 -354
- package/demos/js/mondrian.js +0 -285
- package/demos/js/opacity.js +0 -275
- package/demos/js/painter.js +0 -484
- package/demos/js/particles-showcase.js +0 -514
- package/demos/js/particles.js +0 -299
- package/demos/js/patterns.js +0 -397
- package/demos/js/penrose/artifact.js +0 -69
- package/demos/js/penrose/blackhole.js +0 -121
- package/demos/js/penrose/constants.js +0 -73
- package/demos/js/penrose/game.js +0 -943
- package/demos/js/penrose/lore.js +0 -278
- package/demos/js/penrose/penrosescene.js +0 -892
- package/demos/js/penrose/ship.js +0 -216
- package/demos/js/penrose/sounds.js +0 -211
- package/demos/js/penrose/voidparticle.js +0 -55
- package/demos/js/penrose/voidscene.js +0 -258
- package/demos/js/penrose/voidship.js +0 -144
- package/demos/js/penrose/wormhole.js +0 -46
- package/demos/js/pipeline.js +0 -555
- package/demos/js/plane3d.js +0 -256
- package/demos/js/platformer.js +0 -1579
- package/demos/js/scene.js +0 -304
- package/demos/js/scenes.js +0 -320
- package/demos/js/schrodinger.js +0 -410
- package/demos/js/schwarzschild.js +0 -1023
- package/demos/js/shapes.js +0 -628
- package/demos/js/space/alien.js +0 -171
- package/demos/js/space/boom.js +0 -98
- package/demos/js/space/boss.js +0 -353
- package/demos/js/space/buff.js +0 -73
- package/demos/js/space/bullet.js +0 -102
- package/demos/js/space/constants.js +0 -85
- package/demos/js/space/game.js +0 -1884
- package/demos/js/space/hud.js +0 -112
- package/demos/js/space/laserbeam.js +0 -179
- package/demos/js/space/lightning.js +0 -277
- package/demos/js/space/minion.js +0 -192
- package/demos/js/space/missile.js +0 -212
- package/demos/js/space/player.js +0 -430
- package/demos/js/space/powerup.js +0 -90
- package/demos/js/space/starfield.js +0 -58
- package/demos/js/space/starpower.js +0 -90
- package/demos/js/spacetime.js +0 -559
- package/demos/js/sphere3d.js +0 -229
- package/demos/js/sprite.js +0 -473
- package/demos/js/svgtween.js +0 -204
- package/demos/js/tde/accretiondisk.js +0 -471
- package/demos/js/tde/blackhole.js +0 -219
- package/demos/js/tde/blackholescene.js +0 -209
- package/demos/js/tde/config.js +0 -59
- package/demos/js/tde/index.js +0 -820
- package/demos/js/tde/jets.js +0 -290
- package/demos/js/tde/lensedstarfield.js +0 -154
- package/demos/js/tde/tdestar.js +0 -297
- package/demos/js/tde/tidalstream.js +0 -372
- package/demos/js/tde_old/blackhole.obj.js +0 -354
- package/demos/js/tde_old/debris.obj.js +0 -791
- package/demos/js/tde_old/flare.obj.js +0 -239
- package/demos/js/tde_old/index.js +0 -448
- package/demos/js/tde_old/star.obj.js +0 -812
- package/demos/js/tiles.js +0 -312
- package/demos/js/tweendemo.js +0 -79
- package/demos/js/visibility.js +0 -102
- package/demos/kerr.html +0 -28
- package/demos/lavalamp.html +0 -27
- package/demos/layouts.html +0 -37
- package/demos/logo.svg +0 -4
- package/demos/loop.html +0 -84
- package/demos/mondrian.html +0 -32
- package/demos/og_image.png +0 -0
- package/demos/opacity.html +0 -36
- package/demos/painter.html +0 -39
- package/demos/particles-showcase.html +0 -28
- package/demos/particles.html +0 -24
- package/demos/patterns.html +0 -33
- package/demos/penrose-game.html +0 -31
- package/demos/pipeline.html +0 -737
- package/demos/plane3d.html +0 -24
- package/demos/platformer.html +0 -43
- package/demos/scene.html +0 -33
- package/demos/scenes.html +0 -96
- package/demos/schrodinger.html +0 -27
- package/demos/schwarzschild.html +0 -27
- package/demos/shapes.html +0 -16
- package/demos/space.html +0 -85
- package/demos/spacetime.html +0 -27
- package/demos/sphere3d.html +0 -24
- package/demos/sprite.html +0 -18
- package/demos/svgtween.html +0 -29
- package/demos/tde.html +0 -28
- package/demos/tiles.html +0 -28
- package/demos/transforms.html +0 -400
- package/demos/tween.html +0 -45
- package/demos/visibility.html +0 -33
- package/docs/README.md +0 -230
- package/docs/api/FluidSystem.md +0 -173
- package/docs/concepts/architecture-overview.md +0 -204
- package/docs/concepts/coordinate-system.md +0 -384
- package/docs/concepts/lifecycle.md +0 -255
- package/docs/concepts/rendering-pipeline.md +0 -279
- package/docs/concepts/shapes-vs-gameobjects.md +0 -187
- package/docs/concepts/tde-zorder.md +0 -106
- package/docs/concepts/two-layer-architecture.md +0 -229
- package/docs/fluid-dynamics.md +0 -99
- package/docs/getting-started/first-game.md +0 -354
- package/docs/getting-started/hello-world.md +0 -269
- package/docs/getting-started/installation.md +0 -175
- package/docs/modules/collision/README.md +0 -453
- package/docs/modules/fluent/README.md +0 -1075
- package/docs/modules/game/README.md +0 -303
- package/docs/modules/isometric-camera.md +0 -210
- package/docs/modules/isometric.md +0 -275
- package/docs/modules/painter/README.md +0 -328
- package/docs/modules/particle/README.md +0 -559
- package/docs/modules/shapes/README.md +0 -221
- package/docs/modules/shapes/base/euclidian.md +0 -123
- package/docs/modules/shapes/base/geometry2d.md +0 -204
- package/docs/modules/shapes/base/renderable.md +0 -215
- package/docs/modules/shapes/base/shape.md +0 -262
- package/docs/modules/shapes/base/transformable.md +0 -243
- package/docs/modules/shapes/hierarchy.md +0 -218
- package/docs/modules/state/README.md +0 -577
- package/docs/modules/util/README.md +0 -99
- package/docs/modules/util/camera3d.md +0 -412
- package/docs/modules/util/scene3d.md +0 -395
- package/index.html +0 -17
- package/jsdoc.json +0 -50
- package/scripts/build-demo.js +0 -69
- package/scripts/bundle4llm.js +0 -276
- package/scripts/clearconsole.js +0 -48
- package/test/math/orbital.test.js +0 -61
- package/test/math/tensor.test.js +0 -114
- package/test/particle/emitter.test.js +0 -204
- package/test/particle/particle-system.test.js +0 -310
- package/test/particle/particle.test.js +0 -116
- package/test/particle/updaters.test.js +0 -386
- package/test/setup.js +0 -120
- package/test/shapes/euclidian.test.js +0 -44
- package/test/shapes/geometry.test.js +0 -86
- package/test/shapes/group.test.js +0 -86
- package/test/shapes/rectangle.test.js +0 -64
- package/test/shapes/transform.test.js +0 -379
- package/test/util/camera3d.test.js +0 -428
- package/test/util/scene3d.test.js +0 -352
- package/vite.config.js +0 -50
- package/vitest.config.js +0 -13
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Boolean Algebra Utilities
|
|
3
|
+
*
|
|
4
|
+
* Pure math helpers for:
|
|
5
|
+
* - Logic gates (AND/OR/XOR/NOT + NAND/NOR/XNOR)
|
|
6
|
+
* - Parsing boolean expressions into an AST
|
|
7
|
+
* - Evaluating expressions with a variable assignment
|
|
8
|
+
* - Building truth tables (optionally in Gray-code order)
|
|
9
|
+
*
|
|
10
|
+
* This module is intentionally **rendering-agnostic**.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const CONFIG = {
|
|
14
|
+
tokens: {
|
|
15
|
+
// Supported operator aliases. Words are case-insensitive.
|
|
16
|
+
unaryNot: ["!", "~", "¬", "NOT"],
|
|
17
|
+
and: ["&", "∧", "AND"],
|
|
18
|
+
nand: ["NAND"],
|
|
19
|
+
or: ["|", "∨", "OR"],
|
|
20
|
+
nor: ["NOR"],
|
|
21
|
+
xor: ["^", "⊕", "XOR"],
|
|
22
|
+
xnor: ["XNOR"],
|
|
23
|
+
},
|
|
24
|
+
precedence: {
|
|
25
|
+
// Higher number = higher precedence
|
|
26
|
+
OR: 1,
|
|
27
|
+
NOR: 1,
|
|
28
|
+
XOR: 2,
|
|
29
|
+
XNOR: 2,
|
|
30
|
+
AND: 3,
|
|
31
|
+
NAND: 3,
|
|
32
|
+
NOT: 4,
|
|
33
|
+
},
|
|
34
|
+
constants: {
|
|
35
|
+
true: ["1", "TRUE"],
|
|
36
|
+
false: ["0", "FALSE"],
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {Object} BooleanAst
|
|
42
|
+
* @property {"const"|"var"|"not"|"and"|"or"|"xor"|"nand"|"nor"|"xnor"} type
|
|
43
|
+
* @property {boolean} [value]
|
|
44
|
+
* @property {string} [name]
|
|
45
|
+
* @property {BooleanAst} [left]
|
|
46
|
+
* @property {BooleanAst} [right]
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {Object.<string, boolean|0|1>} BooleanEnv
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Normalize a truthy-ish value to boolean.
|
|
55
|
+
* @param {boolean|number} v
|
|
56
|
+
* @returns {boolean}
|
|
57
|
+
*/
|
|
58
|
+
function toBool(v) {
|
|
59
|
+
return Boolean(v);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {string} s
|
|
64
|
+
* @returns {string}
|
|
65
|
+
*/
|
|
66
|
+
function upper(s) {
|
|
67
|
+
return String(s).toUpperCase();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} src
|
|
72
|
+
* @returns {Array<{type:"lp"|"rp"|"op"|"ident"|"const", value:string}>}
|
|
73
|
+
*/
|
|
74
|
+
function tokenize(src) {
|
|
75
|
+
const out = [];
|
|
76
|
+
const s = String(src ?? "");
|
|
77
|
+
let i = 0;
|
|
78
|
+
|
|
79
|
+
const isWs = (c) => c === " " || c === "\n" || c === "\t" || c === "\r";
|
|
80
|
+
const isAlpha = (c) =>
|
|
81
|
+
(c >= "A" && c <= "Z") || (c >= "a" && c <= "z") || c === "_";
|
|
82
|
+
const isAlnum = (c) => isAlpha(c) || (c >= "0" && c <= "9");
|
|
83
|
+
|
|
84
|
+
while (i < s.length) {
|
|
85
|
+
const c = s[i];
|
|
86
|
+
if (isWs(c)) {
|
|
87
|
+
i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (c === "(") {
|
|
92
|
+
out.push({ type: "lp", value: c });
|
|
93
|
+
i++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (c === ")") {
|
|
97
|
+
out.push({ type: "rp", value: c });
|
|
98
|
+
i++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Single-char operators
|
|
103
|
+
if (c === "!" || c === "~" || c === "¬" || c === "&" || c === "∧" || c === "|" || c === "∨" || c === "^" || c === "⊕") {
|
|
104
|
+
out.push({ type: "op", value: c });
|
|
105
|
+
i++;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Identifiers / word-operators / constants
|
|
110
|
+
if (isAlpha(c) || (c >= "0" && c <= "9")) {
|
|
111
|
+
let j = i + 1;
|
|
112
|
+
while (j < s.length && isAlnum(s[j])) j++;
|
|
113
|
+
const raw = s.slice(i, j);
|
|
114
|
+
const u = upper(raw);
|
|
115
|
+
|
|
116
|
+
if (CONFIG.constants.true.includes(u) || CONFIG.constants.false.includes(u)) {
|
|
117
|
+
out.push({ type: "const", value: u });
|
|
118
|
+
} else if (
|
|
119
|
+
CONFIG.tokens.unaryNot.includes(u) ||
|
|
120
|
+
CONFIG.tokens.and.includes(u) ||
|
|
121
|
+
CONFIG.tokens.nand.includes(u) ||
|
|
122
|
+
CONFIG.tokens.or.includes(u) ||
|
|
123
|
+
CONFIG.tokens.nor.includes(u) ||
|
|
124
|
+
CONFIG.tokens.xor.includes(u) ||
|
|
125
|
+
CONFIG.tokens.xnor.includes(u)
|
|
126
|
+
) {
|
|
127
|
+
out.push({ type: "op", value: u });
|
|
128
|
+
} else {
|
|
129
|
+
out.push({ type: "ident", value: raw });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
i = j;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
throw new Error(`[BooleanAlgebra] Unexpected character "${c}" at ${i}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Map an operator token to a canonical operator name.
|
|
144
|
+
* @param {string} op
|
|
145
|
+
* @returns {"NOT"|"AND"|"NAND"|"OR"|"NOR"|"XOR"|"XNOR"}
|
|
146
|
+
*/
|
|
147
|
+
function normalizeOperator(op) {
|
|
148
|
+
const u = upper(op);
|
|
149
|
+
if (CONFIG.tokens.unaryNot.map(upper).includes(u)) return "NOT";
|
|
150
|
+
if (CONFIG.tokens.and.map(upper).includes(u)) return "AND";
|
|
151
|
+
if (CONFIG.tokens.nand.map(upper).includes(u)) return "NAND";
|
|
152
|
+
if (CONFIG.tokens.or.map(upper).includes(u)) return "OR";
|
|
153
|
+
if (CONFIG.tokens.nor.map(upper).includes(u)) return "NOR";
|
|
154
|
+
if (CONFIG.tokens.xor.map(upper).includes(u)) return "XOR";
|
|
155
|
+
if (CONFIG.tokens.xnor.map(upper).includes(u)) return "XNOR";
|
|
156
|
+
throw new Error(`[BooleanAlgebra] Unknown operator "${op}"`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {string} opName
|
|
161
|
+
* @returns {number}
|
|
162
|
+
*/
|
|
163
|
+
function precedence(opName) {
|
|
164
|
+
return CONFIG.precedence[opName] ?? 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Parse boolean expression tokens into an AST using precedence climbing.
|
|
169
|
+
* @param {ReturnType<typeof tokenize>} tokens
|
|
170
|
+
* @returns {BooleanAst}
|
|
171
|
+
*/
|
|
172
|
+
function parseTokens(tokens) {
|
|
173
|
+
let idx = 0;
|
|
174
|
+
|
|
175
|
+
const peek = () => tokens[idx];
|
|
176
|
+
const next = () => tokens[idx++];
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @returns {BooleanAst}
|
|
180
|
+
*/
|
|
181
|
+
function parsePrimary() {
|
|
182
|
+
const t = next();
|
|
183
|
+
if (!t) throw new Error("[BooleanAlgebra] Unexpected end of input");
|
|
184
|
+
|
|
185
|
+
if (t.type === "const") {
|
|
186
|
+
const u = upper(t.value);
|
|
187
|
+
return { type: "const", value: CONFIG.constants.true.includes(u) };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (t.type === "ident") {
|
|
191
|
+
return { type: "var", name: t.value };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (t.type === "lp") {
|
|
195
|
+
const expr = parseExpr(0);
|
|
196
|
+
const r = next();
|
|
197
|
+
if (!r || r.type !== "rp") {
|
|
198
|
+
throw new Error("[BooleanAlgebra] Missing closing ')'");
|
|
199
|
+
}
|
|
200
|
+
return expr;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (t.type === "op") {
|
|
204
|
+
const opName = normalizeOperator(t.value);
|
|
205
|
+
if (opName !== "NOT") {
|
|
206
|
+
throw new Error(`[BooleanAlgebra] Unexpected binary operator "${t.value}"`);
|
|
207
|
+
}
|
|
208
|
+
return { type: "not", left: parsePrimary() };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
throw new Error(`[BooleanAlgebra] Unexpected token "${t.type}"`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* @param {number} minPrec
|
|
216
|
+
* @returns {BooleanAst}
|
|
217
|
+
*/
|
|
218
|
+
function parseExpr(minPrec) {
|
|
219
|
+
let left = parsePrimary();
|
|
220
|
+
|
|
221
|
+
while (true) {
|
|
222
|
+
const t = peek();
|
|
223
|
+
if (!t || t.type !== "op") break;
|
|
224
|
+
const opName = normalizeOperator(t.value);
|
|
225
|
+
if (opName === "NOT") break; // unary NOT handled in primary
|
|
226
|
+
|
|
227
|
+
const prec = precedence(opName);
|
|
228
|
+
if (prec < minPrec) break;
|
|
229
|
+
|
|
230
|
+
// Consume operator
|
|
231
|
+
next();
|
|
232
|
+
|
|
233
|
+
// Left-associative for all binary ops here
|
|
234
|
+
const right = parseExpr(prec + 1);
|
|
235
|
+
|
|
236
|
+
/** @type {BooleanAst["type"]} */
|
|
237
|
+
const type =
|
|
238
|
+
opName === "AND"
|
|
239
|
+
? "and"
|
|
240
|
+
: opName === "OR"
|
|
241
|
+
? "or"
|
|
242
|
+
: opName === "XOR"
|
|
243
|
+
? "xor"
|
|
244
|
+
: opName === "NAND"
|
|
245
|
+
? "nand"
|
|
246
|
+
: opName === "NOR"
|
|
247
|
+
? "nor"
|
|
248
|
+
: "xnor";
|
|
249
|
+
|
|
250
|
+
left = { type, left, right };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return left;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const ast = parseExpr(0);
|
|
257
|
+
if (idx < tokens.length) {
|
|
258
|
+
throw new Error("[BooleanAlgebra] Unexpected trailing tokens");
|
|
259
|
+
}
|
|
260
|
+
return ast;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @param {BooleanAst} ast
|
|
265
|
+
* @param {BooleanEnv} env
|
|
266
|
+
* @returns {boolean}
|
|
267
|
+
*/
|
|
268
|
+
function evalAst(ast, env) {
|
|
269
|
+
switch (ast.type) {
|
|
270
|
+
case "const":
|
|
271
|
+
return Boolean(ast.value);
|
|
272
|
+
case "var": {
|
|
273
|
+
const v = env?.[ast.name] ?? false;
|
|
274
|
+
return toBool(v);
|
|
275
|
+
}
|
|
276
|
+
case "not":
|
|
277
|
+
return !evalAst(ast.left, env);
|
|
278
|
+
case "and":
|
|
279
|
+
return evalAst(ast.left, env) && evalAst(ast.right, env);
|
|
280
|
+
case "nand":
|
|
281
|
+
return !(evalAst(ast.left, env) && evalAst(ast.right, env));
|
|
282
|
+
case "or":
|
|
283
|
+
return evalAst(ast.left, env) || evalAst(ast.right, env);
|
|
284
|
+
case "nor":
|
|
285
|
+
return !(evalAst(ast.left, env) || evalAst(ast.right, env));
|
|
286
|
+
case "xor": {
|
|
287
|
+
const a = evalAst(ast.left, env);
|
|
288
|
+
const b = evalAst(ast.right, env);
|
|
289
|
+
return (a && !b) || (!a && b);
|
|
290
|
+
}
|
|
291
|
+
case "xnor": {
|
|
292
|
+
const a = evalAst(ast.left, env);
|
|
293
|
+
const b = evalAst(ast.right, env);
|
|
294
|
+
return a === b;
|
|
295
|
+
}
|
|
296
|
+
default:
|
|
297
|
+
throw new Error(`[BooleanAlgebra] Unknown AST node type "${ast.type}"`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Collect variables from an AST.
|
|
303
|
+
* @param {BooleanAst} ast
|
|
304
|
+
* @param {Set<string>} out
|
|
305
|
+
*/
|
|
306
|
+
function collectVars(ast, out) {
|
|
307
|
+
if (!ast) return;
|
|
308
|
+
if (ast.type === "var" && ast.name) out.add(ast.name);
|
|
309
|
+
if (ast.left) collectVars(ast.left, out);
|
|
310
|
+
if (ast.right) collectVars(ast.right, out);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Return Gray-code ordering for integers in [0..2^n-1].
|
|
315
|
+
* @param {number} n
|
|
316
|
+
* @returns {number[]}
|
|
317
|
+
*/
|
|
318
|
+
function grayOrder(n) {
|
|
319
|
+
const size = 1 << n;
|
|
320
|
+
const out = new Array(size);
|
|
321
|
+
for (let i = 0; i < size; i++) {
|
|
322
|
+
out[i] = i ^ (i >> 1);
|
|
323
|
+
}
|
|
324
|
+
return out;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export class BooleanAlgebra {
|
|
328
|
+
/**
|
|
329
|
+
* Basic gates.
|
|
330
|
+
* @param {boolean|number} a
|
|
331
|
+
* @param {boolean|number} b
|
|
332
|
+
* @returns {boolean}
|
|
333
|
+
*/
|
|
334
|
+
static and(a, b) {
|
|
335
|
+
return toBool(a) && toBool(b);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* @param {boolean|number} a
|
|
340
|
+
* @param {boolean|number} b
|
|
341
|
+
* @returns {boolean}
|
|
342
|
+
*/
|
|
343
|
+
static nand(a, b) {
|
|
344
|
+
return !(toBool(a) && toBool(b));
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* @param {boolean|number} a
|
|
349
|
+
* @param {boolean|number} b
|
|
350
|
+
* @returns {boolean}
|
|
351
|
+
*/
|
|
352
|
+
static or(a, b) {
|
|
353
|
+
return toBool(a) || toBool(b);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* @param {boolean|number} a
|
|
358
|
+
* @param {boolean|number} b
|
|
359
|
+
* @returns {boolean}
|
|
360
|
+
*/
|
|
361
|
+
static nor(a, b) {
|
|
362
|
+
return !(toBool(a) || toBool(b));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @param {boolean|number} a
|
|
367
|
+
* @param {boolean|number} b
|
|
368
|
+
* @returns {boolean}
|
|
369
|
+
*/
|
|
370
|
+
static xor(a, b) {
|
|
371
|
+
const aa = toBool(a);
|
|
372
|
+
const bb = toBool(b);
|
|
373
|
+
return (aa && !bb) || (!aa && bb);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @param {boolean|number} a
|
|
378
|
+
* @param {boolean|number} b
|
|
379
|
+
* @returns {boolean}
|
|
380
|
+
*/
|
|
381
|
+
static xnor(a, b) {
|
|
382
|
+
return toBool(a) === toBool(b);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @param {boolean|number} a
|
|
387
|
+
* @returns {boolean}
|
|
388
|
+
*/
|
|
389
|
+
static not(a) {
|
|
390
|
+
return !toBool(a);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Parse an expression string into an AST.
|
|
395
|
+
*
|
|
396
|
+
* Supported operators:
|
|
397
|
+
* - NOT: `!`, `~`, `¬`, `NOT`
|
|
398
|
+
* - AND: `&`, `∧`, `AND`
|
|
399
|
+
* - OR: `|`, `∨`, `OR`
|
|
400
|
+
* - XOR: `^`, `⊕`, `XOR`
|
|
401
|
+
* - NAND/NOR/XNOR as words (case-insensitive)
|
|
402
|
+
*
|
|
403
|
+
* Constants: `0/1`, `false/true` (case-insensitive)
|
|
404
|
+
*
|
|
405
|
+
* @param {string} expr
|
|
406
|
+
* @returns {BooleanAst}
|
|
407
|
+
*/
|
|
408
|
+
static parse(expr) {
|
|
409
|
+
return parseTokens(tokenize(expr));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Evaluate an expression string or AST against an environment.
|
|
414
|
+
* @param {string|BooleanAst} expr
|
|
415
|
+
* @param {BooleanEnv} env
|
|
416
|
+
* @returns {boolean}
|
|
417
|
+
*/
|
|
418
|
+
static evaluate(expr, env = {}) {
|
|
419
|
+
const ast = typeof expr === "string" ? BooleanAlgebra.parse(expr) : expr;
|
|
420
|
+
return evalAst(ast, env);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Get sorted variable names used by an expression.
|
|
425
|
+
* @param {string|BooleanAst} expr
|
|
426
|
+
* @returns {string[]}
|
|
427
|
+
*/
|
|
428
|
+
static variables(expr) {
|
|
429
|
+
const ast = typeof expr === "string" ? BooleanAlgebra.parse(expr) : expr;
|
|
430
|
+
const vars = new Set();
|
|
431
|
+
collectVars(ast, vars);
|
|
432
|
+
return [...vars].sort((a, b) => a.localeCompare(b));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Gray-code ordering for n bits.
|
|
437
|
+
* @param {number} n
|
|
438
|
+
* @returns {number[]}
|
|
439
|
+
*/
|
|
440
|
+
static grayCode(n) {
|
|
441
|
+
return grayOrder(n);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Build a truth table for an expression.
|
|
446
|
+
* @param {string|BooleanAst} expr
|
|
447
|
+
* @param {string[]} [variables] - Optional variable ordering. If omitted, derived from expression.
|
|
448
|
+
* @param {{ order?: "binary"|"gray" }} [options]
|
|
449
|
+
* @returns {{
|
|
450
|
+
* variables: string[],
|
|
451
|
+
* order: "binary"|"gray",
|
|
452
|
+
* rows: Array<{ index: number, inputs: Record<string, boolean>, output: boolean }>
|
|
453
|
+
* }}
|
|
454
|
+
*/
|
|
455
|
+
static truthTable(expr, variables, options = {}) {
|
|
456
|
+
const ast = typeof expr === "string" ? BooleanAlgebra.parse(expr) : expr;
|
|
457
|
+
const vars = (variables && variables.length > 0) ? [...variables] : BooleanAlgebra.variables(ast);
|
|
458
|
+
const n = vars.length;
|
|
459
|
+
const size = 1 << n;
|
|
460
|
+
const order = options.order === "gray" ? "gray" : "binary";
|
|
461
|
+
const indices = order === "gray" ? grayOrder(n) : Array.from({ length: size }, (_, i) => i);
|
|
462
|
+
|
|
463
|
+
const rows = indices.map((idx) => {
|
|
464
|
+
/** @type {Record<string, boolean>} */
|
|
465
|
+
const inputs = {};
|
|
466
|
+
for (let bit = 0; bit < n; bit++) {
|
|
467
|
+
const mask = 1 << (n - 1 - bit);
|
|
468
|
+
inputs[vars[bit]] = Boolean(idx & mask);
|
|
469
|
+
}
|
|
470
|
+
return {
|
|
471
|
+
index: idx,
|
|
472
|
+
inputs,
|
|
473
|
+
output: evalAst(ast, inputs),
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
return { variables: vars, order, rows };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
|
package/src/math/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export {Patterns} from "./patterns.js";
|
|
|
5
5
|
export {Noise} from "./noise.js";
|
|
6
6
|
export {Tensor} from "./tensor.js";
|
|
7
7
|
export {generatePenroseTilingPixels} from "./penrose.js";
|
|
8
|
+
export { BooleanAlgebra } from "./boolean.js";
|
|
8
9
|
|
|
9
10
|
// Physics modules
|
|
10
11
|
export * from "./gr.js";
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
name: Release on Master Merge
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches:
|
|
5
|
-
- master
|
|
6
|
-
permissions:
|
|
7
|
-
contents: write
|
|
8
|
-
jobs:
|
|
9
|
-
release:
|
|
10
|
-
name: Build and Release GCanvas
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- name: Checkout code
|
|
14
|
-
uses: actions/checkout@v3
|
|
15
|
-
with:
|
|
16
|
-
fetch-depth: 0
|
|
17
|
-
- name: Setup Node.js
|
|
18
|
-
uses: actions/setup-node@v3
|
|
19
|
-
with:
|
|
20
|
-
node-version: "20"
|
|
21
|
-
- name: Install dependencies
|
|
22
|
-
run: npm ci
|
|
23
|
-
- name: Build library
|
|
24
|
-
run: npm run build
|
|
25
|
-
- name: Read version from package.json
|
|
26
|
-
id: pkg
|
|
27
|
-
run: |
|
|
28
|
-
VERSION=$(node -p "require('./package.json').version")
|
|
29
|
-
echo "VERSION=v$VERSION" >> $GITHUB_OUTPUT
|
|
30
|
-
- name: Show version used
|
|
31
|
-
run: |
|
|
32
|
-
echo "Publishing version: ${{ steps.pkg.outputs.VERSION }}"
|
|
33
|
-
- name: Generate Changelog
|
|
34
|
-
id: changelog
|
|
35
|
-
run: |
|
|
36
|
-
LAST_TAG=$(git describe --tags --abbrev=0)
|
|
37
|
-
echo "Last tag was: $LAST_TAG"
|
|
38
|
-
LOG=$(git log $LAST_TAG..HEAD --pretty=format:"- %s")
|
|
39
|
-
echo "LOG<<EOF" >> $GITHUB_OUTPUT
|
|
40
|
-
echo "$LOG" >> $GITHUB_OUTPUT
|
|
41
|
-
echo "EOF" >> $GITHUB_OUTPUT
|
|
42
|
-
- name: Create GitHub Release
|
|
43
|
-
id: create_release
|
|
44
|
-
uses: actions/create-release@v1
|
|
45
|
-
env:
|
|
46
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
47
|
-
with:
|
|
48
|
-
tag_name: ${{ steps.pkg.outputs.VERSION }}
|
|
49
|
-
body: ${{ steps.changelog.outputs.LOG }}
|
|
50
|
-
release_name: GCanvas ${{ steps.pkg.outputs.VERSION }}
|
|
51
|
-
draft: false
|
|
52
|
-
prerelease: false
|
|
53
|
-
- name: Upload UMD build
|
|
54
|
-
uses: actions/upload-release-asset@v1
|
|
55
|
-
env:
|
|
56
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
57
|
-
with:
|
|
58
|
-
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
59
|
-
asset_path: dist/gcanvas.umd.min.js
|
|
60
|
-
asset_name: gcanvas.umd.min.js
|
|
61
|
-
asset_content_type: application/javascript
|
|
62
|
-
- name: Upload ESM build
|
|
63
|
-
uses: actions/upload-release-asset@v1
|
|
64
|
-
env:
|
|
65
|
-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
66
|
-
with:
|
|
67
|
-
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
68
|
-
asset_path: dist/gcanvas.es.min.js
|
|
69
|
-
asset_name: gcanvas.es.min.js
|
|
70
|
-
asset_content_type: application/javascript
|
package/.jshintrc
DELETED
package/.vscode/settings.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"workbench.colorCustomizations": {
|
|
3
|
-
"activityBar.activeBackground": "#2f7c47",
|
|
4
|
-
"activityBar.background": "#2f7c47",
|
|
5
|
-
"activityBar.foreground": "#e7e7e7",
|
|
6
|
-
"activityBar.inactiveForeground": "#e7e7e799",
|
|
7
|
-
"activityBarBadge.background": "#422c74",
|
|
8
|
-
"activityBarBadge.foreground": "#e7e7e7",
|
|
9
|
-
"commandCenter.border": "#e7e7e799",
|
|
10
|
-
"sash.hoverBorder": "#2f7c47",
|
|
11
|
-
"statusBar.background": "#215732",
|
|
12
|
-
"statusBar.foreground": "#e7e7e7",
|
|
13
|
-
"statusBarItem.hoverBackground": "#2f7c47",
|
|
14
|
-
"statusBarItem.remoteBackground": "#215732",
|
|
15
|
-
"statusBarItem.remoteForeground": "#e7e7e7",
|
|
16
|
-
"titleBar.activeBackground": "#215732",
|
|
17
|
-
"titleBar.activeForeground": "#e7e7e7",
|
|
18
|
-
"titleBar.inactiveBackground": "#21573299",
|
|
19
|
-
"titleBar.inactiveForeground": "#e7e7e799"
|
|
20
|
-
},
|
|
21
|
-
"peacock.color": "#215732"
|
|
22
|
-
}
|