squishjs 0.6.43 → 0.7.1

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.
Files changed (77) hide show
  1. package/.github/workflows/node.js.yml +1 -1
  2. package/LICENSE.md +21 -0
  3. package/README.md +116 -42
  4. package/index.js +11 -1
  5. package/oldsvgexport.js +89 -0
  6. package/package.json +1 -1
  7. package/render-tester.js +120 -0
  8. package/src/BaseNode.js +101 -0
  9. package/src/Game.js +15 -3
  10. package/src/GameNode.js +82 -79
  11. package/src/InternalGameNode.js +4 -3
  12. package/src/Squisher.js +224 -0
  13. package/src/ViewableGame.js +60 -0
  14. package/src/node-types.js +15 -0
  15. package/src/squish.js +90 -349
  16. package/src/squishHelpers/asset.js +63 -0
  17. package/src/squishHelpers/border.js +16 -0
  18. package/src/squishHelpers/color.js +16 -0
  19. package/src/squishHelpers/coordinates2d.js +98 -0
  20. package/src/squishHelpers/effect.js +55 -0
  21. package/src/squishHelpers/fill.js +16 -0
  22. package/src/squishHelpers/handleClick.js +16 -0
  23. package/src/squishHelpers/id.js +16 -0
  24. package/src/squishHelpers/input.js +22 -0
  25. package/src/squishHelpers/playerIds.js +12 -0
  26. package/src/squishHelpers/pos.js +23 -0
  27. package/src/squishHelpers/size.js +23 -0
  28. package/src/squishHelpers/subType.js +16 -0
  29. package/src/squishHelpers/text.js +154 -0
  30. package/src/subtype-mappings.js +10 -0
  31. package/src/subtypes.js +7 -0
  32. package/{dist/terrain/defs.d.ts → src/terrain/defs.ts} +7 -6
  33. package/src/terrain/index.ts +156 -0
  34. package/src/terrain/terrainFunctions.ts +142 -0
  35. package/src/util/geometry.js +45 -0
  36. package/src/util/index.ts +6 -0
  37. package/src/util/listenable.ts +20 -0
  38. package/src/util/shapes.ts +18 -0
  39. package/src/util/views.js +108 -0
  40. package/test/Layers.test.js +63 -0
  41. package/test/Scale.test.js +235 -0
  42. package/test/Squisher.test.js +49 -0
  43. package/test/StateUpdates.js +10 -0
  44. package/test/Terrain.test.js +0 -1
  45. package/test/main.test.js +55 -35
  46. package/test/utils.js +91 -0
  47. package/testapp.html +26 -0
  48. package/testapp.js +988 -0
  49. package/tsconfig.json +22 -0
  50. package/dist/Colors.d.ts +0 -7
  51. package/dist/Colors.js +0 -124
  52. package/dist/Game.d.ts +0 -20
  53. package/dist/Game.js +0 -48
  54. package/dist/GameNode.d.ts +0 -66
  55. package/dist/GameNode.js +0 -148
  56. package/dist/InternalGameNode.d.ts +0 -20
  57. package/dist/InternalGameNode.js +0 -32
  58. package/dist/Shapes.d.ts +0 -6
  59. package/dist/Shapes.js +0 -8
  60. package/dist/index.d.ts +0 -7
  61. package/dist/index.js +0 -18
  62. package/dist/sharedDefs.d.ts +0 -72
  63. package/dist/sharedDefs.js +0 -13
  64. package/dist/squish.d.ts +0 -8
  65. package/dist/squish.js +0 -336
  66. package/dist/terrain/defs.js +0 -5
  67. package/dist/terrain/index.d.ts +0 -2
  68. package/dist/terrain/index.js +0 -151
  69. package/dist/terrain/terrainFunctions.d.ts +0 -6
  70. package/dist/terrain/terrainFunctions.js +0 -152
  71. package/dist/util/index.d.ts +0 -2
  72. package/dist/util/index.js +0 -11
  73. package/dist/util/listenable.d.ts +0 -3
  74. package/dist/util/listenable.js +0 -21
  75. package/dist/util/shapes.d.ts +0 -2
  76. package/dist/util/shapes.js +0 -23
  77. package/license.txt +0 -674
package/src/GameNode.js CHANGED
@@ -1,110 +1,113 @@
1
- const listenable = require("./util/listenable");
2
- const InternalGameNode = require('./InternalGameNode');
3
1
  const Shapes = require('./Shapes');
2
+ const SUBTYPES = require('./subtypes');
3
+ const { gameNode, BaseNode } = require('./BaseNode');
4
4
 
5
- const gameNode = (color, onClick, coordinates2d, border, fill, text, asset, playerIds, effects, input) => {
6
- const node = new InternalGameNode(color, onClick, coordinates2d, border, fill, text, asset, playerIds, effects, input);
7
- return listenable(node, node.onStateChange.bind(node));
5
+ const shapeTypeToSubtype = {
6
+ [Shapes.CIRCLE]: SUBTYPES.SHAPE_2D_CIRCLE,
7
+ [Shapes.POLYGON]: SUBTYPES.SHAPE_2D_POLYGON,
8
+ [Shapes.LINE]: SUBTYPES.SHAPE_2D_LINE
8
9
  };
9
10
 
10
- class Shape {
11
- constructor({ color, onClick, shapeType, coordinates2d, border, fill, playerIds, effects, input }) {
12
- if (!coordinates2d || !shapeType) {
13
- throw new Error("Shape requires coordinates2d and shapeType");
14
- }
15
-
16
- this.node = gameNode(color, onClick, coordinates2d, border, fill, null, null, playerIds, effects, input);
17
- this.id = this.node.id;
18
- }
19
-
20
- addChild(child) {
21
- this.node.addChild(child);
22
- }
11
+ const subtypeToShapeType = {
12
+ [SUBTYPES.SHAPE_2D_CIRCLE]: Shapes.CIRCLE,
13
+ [SUBTYPES.SHAPE_2D_POLYGON]: Shapes.POLYGON,
14
+ [SUBTYPES.SHAPE_2D_LINE]: Shapes.LINE
15
+ };
23
16
 
24
- addChildren(...nodes) {
25
- for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
26
- this.addChild(nodes[nodeIndex]);
17
+ class Shape extends BaseNode {
18
+ constructor({ color, onClick, shapeType, coordinates2d, border, fill, playerIds, effects, input, node, id }) {
19
+ if ((!coordinates2d || !shapeType) && !(node)) {
20
+ throw new Error("Shape requires coordinates2d and shapeType");
27
21
  }
28
- }
29
-
30
- removeChild(nodeId) {
31
- this.node.removeChild(nodeId);
32
- }
33
22
 
34
- addListener(listener) {
35
- this.node.addListener(listener);
23
+ super({
24
+ color,
25
+ onClick,
26
+ coordinates2d,
27
+ border,
28
+ fill,
29
+ playerIds,
30
+ effects,
31
+ input,
32
+ node,
33
+ subtype: shapeTypeToSubtype[shapeType],
34
+ id
35
+ });
36
+ }
37
+
38
+ clone({ handleClick, input, id }) {
39
+ const _id = id || null;
40
+ return new Shape({
41
+ color: this.node.color,
42
+ onClick: handleClick,
43
+ shapeType: subtypeToShapeType[this.node.subType],
44
+ coordinates2d: this.node.coordinates2d,
45
+ border: this.node.border,
46
+ fill: this.node.fill,
47
+ playerIds: this.node.playerIds,
48
+ effects: this.node.effects,
49
+ input,
50
+ id: _id
51
+ });
36
52
  }
37
53
 
38
- clearChildren(excludedNodeIds) {
39
- this.node.clearChildren(excludedNodeIds);
40
- }
41
54
  }
42
55
 
43
- class Text {
44
- constructor({ textInfo, playerIds, input }) {
45
- if (!textInfo) {
56
+ class Text extends BaseNode {
57
+ constructor({ textInfo, playerIds, input, node, id }) {
58
+ if (!textInfo && !node) {
46
59
  throw new Error("Text node requires textInfo");
47
60
  }
48
61
 
49
- this.node = gameNode(null, null, null, null, null, textInfo, null, playerIds, null, input);
50
- this.id = this.node.id;
51
- }
52
-
53
- addChild(child) {
54
- this.node.addChild(child);
55
- }
56
-
57
- addChildren(...nodes) {
58
- for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
59
- this.addChild(nodes[nodeIndex]);
60
- }
61
- }
62
-
63
- removeChild(nodeId) {
64
- this.node.removeChild(nodeId);
62
+ super({
63
+ textInfo,
64
+ playerIds,
65
+ input,
66
+ node,
67
+ subtype: SUBTYPES.TEXT,
68
+ id
69
+ });
65
70
  }
66
71
 
67
- addListener(listener) {
68
- this.node.addListener(listener);
69
- }
72
+ clone({ handleClick, input, id }) {
73
+ const _id = id || null;
70
74
 
71
- clearChildren(excludedNodeIds) {
72
- this.node.clearChildren(excludedNodeIds);
75
+ return new Text({
76
+ textInfo: Object.assign({}, this.node.text),
77
+ playerIds: this.playerIds?.slice(),
78
+ id: _id
79
+ });
73
80
  }
74
81
  }
75
82
 
76
- class Asset {
77
- constructor({ assetInfo, onClick, coordinates2d, playerIds, effects }) {
78
- if (!assetInfo) {
83
+ class Asset extends BaseNode {
84
+ constructor({ assetInfo, onClick, coordinates2d, playerIds, effects, node, id }) {
85
+ if (!assetInfo && !node) {
79
86
  throw new Error("Asset node requires assetInfo");
80
87
  }
81
- this.node = gameNode(null, onClick, coordinates2d, null, null, null, assetInfo, playerIds, effects);
82
- this.id = this.node.id;
83
- }
84
-
85
- addChild(child) {
86
- this.node.addChild(child);
87
- }
88
-
89
- addChildren(...nodes) {
90
- for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) {
91
- this.addChild(nodes[nodeIndex]);
92
- }
93
- }
94
88
 
95
- removeChild(nodeId) {
96
- this.node.removeChild(nodeId);
89
+ super({
90
+ assetInfo,
91
+ onClick,
92
+ coordinates2d,
93
+ playerIds,
94
+ effects,
95
+ node,
96
+ subtype: SUBTYPES.ASSET,
97
+ id
98
+ });
97
99
  }
98
100
 
99
- addListener(listener) {
100
- this.node.addListener(listener);
101
- }
101
+ clone({ handleClick, input, id }) {
102
+ const _id = id || null;
102
103
 
103
- clearChildren(excludedNodeIds) {
104
- this.node.clearChildren(excludedNodeIds);
104
+ return new Asset({
105
+ assetInfo: Object.assign({}, this.node.asset),
106
+ playerIds: this.playerIds?.slice(),
107
+ id: _id
108
+ });
105
109
  }
106
110
 
107
-
108
111
  }
109
112
 
110
113
  const GameNode = {
@@ -1,8 +1,8 @@
1
- let id = 0;
1
+ let _id = 0;
2
2
 
3
3
  class InternalGameNode {
4
- constructor(color, onClick, coordinates2d, border, fill, text, asset, playerIds = [], effects = null, input = null) {
5
- this.id = id++;
4
+ constructor(color, onClick, coordinates2d, border, fill, text, asset, playerIds = [], effects = null, input = null, subType = null, id = null) {
5
+ this.id = id ? Number(id) : _id++;
6
6
  this.children = new Array();
7
7
  this.color = color;
8
8
  this.handleClick = onClick;
@@ -18,6 +18,7 @@ class InternalGameNode {
18
18
  playerIds = [playerIds];
19
19
  }
20
20
  this.playerIds = playerIds || [];
21
+ this.subType = subType;
21
22
  }
22
23
 
23
24
  addChild(node) {
@@ -0,0 +1,224 @@
1
+ const ASSET_TYPE = 1;
2
+
3
+ const { squish, unsquish } = require('./squish');
4
+
5
+ const INVISIBLE_PLAYER_ID = 0;
6
+ const DEFAULT_TICK_RATE = 60;
7
+
8
+ class Squisher {
9
+ constructor({ game, scale, customBottomLayer, customTopLayer }) {
10
+ this.ids = new Set();
11
+
12
+ this.game = game;
13
+
14
+ this.customBottomLayer = customBottomLayer;
15
+ this.customTopLayer = customTopLayer;
16
+
17
+ this.listeners = new Set();
18
+ this.scale = scale || {x: 1, y: 1};
19
+ this.state = this.squish(this.game.getLayers());
20
+ this.spectatorState = this.game.getSpectatorLayers ? this.squish(this.game.getSpectatorLayers()) : [];
21
+ this.assets = {};
22
+ if (this.game.tick) {
23
+ const tickRate = this.gameMetadata && this.gameMetadata.tickRate ? this.gameMetadata.tickRate : DEFAULT_TICK_RATE;
24
+ setInterval(this.game.tick.bind(this.game), 1000 / tickRate);
25
+ }
26
+ }
27
+
28
+ addListener(onEvent) {
29
+ const listener = {
30
+ onEvent,
31
+ remove: () => this.removeListener(listener)
32
+ };
33
+
34
+ this.listeners.add(listener);
35
+ }
36
+
37
+ removeListener(listener) {
38
+ this.listeners.remove(listener);
39
+ }
40
+
41
+ unsquish(node) {
42
+ return unsquish(node);
43
+ }
44
+
45
+ squish(layers, scale = null) {
46
+
47
+ if (!layers) {
48
+ return [];
49
+ }
50
+
51
+ let layerLength = layers.length;
52
+
53
+ let toSquish = [];
54
+
55
+ const playerMap = {};
56
+ Object.keys(this.game.players).forEach(playerId => {
57
+ playerMap[Number(playerId)] = [];
58
+ });
59
+
60
+ if (this.customBottomLayer) {
61
+ const squishedLayer = [];
62
+ this.squishHelper(this.customBottomLayer.root, squishedLayer, this.customBottomLayer.scale, playerMap);
63
+ toSquish.push(squishedLayer);
64
+ }
65
+
66
+ for (const layerIndex in layers) {
67
+ const squishedLayer = [];
68
+ const layerInfo = layers[layerIndex];
69
+
70
+ const layerScale = layerInfo.scale ? {
71
+ x: this.scale.x * layerInfo.scale.x,
72
+ y: this.scale.y * layerInfo.scale.y
73
+ } : this.scale;
74
+
75
+ this.squishHelper(layerInfo.root, squishedLayer, scale || layerScale, playerMap);
76
+ toSquish.push(squishedLayer);
77
+ }
78
+
79
+
80
+ if (this.customTopLayer) {
81
+ const squishedLayer = [];
82
+ this.squishHelper(this.customTopLayer.root, squishedLayer, this.customTopLayer.scale, playerMap);
83
+ toSquish.push(squishedLayer);
84
+ }
85
+
86
+ // todo: remove/move this side effect
87
+ this.playerStates = playerMap;
88
+
89
+ return toSquish.flat();
90
+ }
91
+
92
+ squishHelper(node, squishedNodes, scale = {x: 1, y: 1}, playerMap = {}) {
93
+ if (!node.node.listeners.has(this)) {
94
+ node.addListener(this);
95
+ }
96
+
97
+ const squished = squish(node, scale);
98
+ squishedNodes.push(squished);
99
+
100
+ if (playerMap) {
101
+ if (node.node.playerIds && node.node.playerIds.length) {
102
+ node.node.playerIds.forEach(playerId => {
103
+ if (playerMap[playerId]) {
104
+ playerMap[playerId].push(squished);
105
+ } else {
106
+ console.warn(`Node references unknown player ID: ${playerId}`);
107
+ }
108
+ });
109
+ } else {
110
+ Object.keys(playerMap).forEach(playerId => {
111
+ playerMap[playerId].push(squished);
112
+ })
113
+ }
114
+ }
115
+
116
+ for (let i = 0; i < node.node.children.length; i++) {
117
+ this.squishHelper(node.node.children[i], squishedNodes, scale, playerMap);
118
+ }
119
+
120
+ }
121
+
122
+ getJson() {
123
+ const layers = this.game.layers;
124
+ const jsonLayers = new Array(layers.length);
125
+ for (const layerIndex in layers) {
126
+ const layerInfo = layers[layerIndex];
127
+ const jsonLayer = layerInfo.root;
128
+ jsonLayers[layerIndex] = jsonLayer;
129
+ }
130
+
131
+ return JSON.stringify(jsonLayers);
132
+ }
133
+
134
+ initialize() {
135
+ return new Promise((resolve, reject) => {
136
+ const gameMetadata = this.game.constructor.metadata && this.game.constructor.metadata();
137
+
138
+ const gameAssets = gameMetadata && gameMetadata.assets ? gameMetadata.assets : {};
139
+
140
+ if (this.customBottomLayer && this.customBottomLayer.assets) {
141
+ Object.assign(gameAssets, this.customBottomLayer.assets);
142
+ }
143
+
144
+ if (this.customTopLayer && this.customTopLayer.assets) {
145
+ Object.assign(gameAssets, this.customTopLayer.assets);
146
+ }
147
+
148
+ if (this.game.getAssets && this.game.getAssets()) {
149
+ Object.assign(gameAssets, this.game.getAssets());
150
+ }
151
+
152
+ const initializeAssetBundle = () => new Promise((resolve, reject) => {
153
+ let assetBundleSize = 0;
154
+ let finishedCount = 0;
155
+ const totalCount = Object.keys(gameAssets).length;
156
+
157
+ for (const key in gameAssets) {
158
+ gameAssets[key].getData().then(buf => {
159
+ const assetKeyLength = 32;
160
+ let keyIndex = 0;
161
+ const assetKeyArray = new Array(32);
162
+ while (keyIndex < assetKeyLength && keyIndex < key.length) {
163
+ assetKeyArray[keyIndex] = key.charCodeAt(keyIndex);
164
+ keyIndex++;
165
+ }
166
+
167
+ const encodedLength = (buf.length + assetKeyLength).toString(36);
168
+
169
+ const assetType = gameAssets[key].info.type === 'image' ? 1 : 2;
170
+
171
+ const encodedMaxLength = 10;
172
+ let encodedLengthString = '';
173
+ for (let i = 0; i < (encodedMaxLength - encodedLength.length); i++) {
174
+ encodedLengthString += '0';
175
+ }
176
+ for (let j = encodedLength.length; j < encodedMaxLength; j++) {
177
+ encodedLengthString += encodedLength.charAt(j - encodedLength.length);
178
+ }
179
+ const encodedLengthArray = new Array(encodedMaxLength);
180
+ for (let i = 0; i < encodedMaxLength; i++) {
181
+ encodedLengthArray[i] = encodedLength.charCodeAt(i);
182
+ }
183
+
184
+ this.assets[key] = [ASSET_TYPE, assetType, ...encodedLengthArray, ...assetKeyArray, ...buf];
185
+ assetBundleSize += this.assets[key].length;
186
+ finishedCount += 1;
187
+
188
+ if (finishedCount == totalCount) {
189
+ const newAssetBundle = new Array(assetBundleSize);
190
+ for (let index = 0; index < assetBundleSize; index++) {
191
+ for (const key in this.assets) {
192
+ for (let y = 0; y < this.assets[key].length; y++) {
193
+ newAssetBundle[index++] = this.assets[key][y];
194
+ }
195
+ }
196
+ }
197
+ resolve(newAssetBundle);
198
+ }
199
+ });
200
+ }
201
+ });
202
+
203
+ initializeAssetBundle().then((newAssetBundle) => {
204
+ this.assetBundle = newAssetBundle;
205
+ resolve();
206
+ });
207
+
208
+ });
209
+ }
210
+
211
+ handleStateChange(node, layerName) {
212
+ this.state = this.squish(this.game.getLayers());
213
+ this.broadcast();
214
+ }
215
+
216
+ broadcast() {
217
+ for (const listener of this.listeners) {
218
+ listener.onEvent(this.state);
219
+ }
220
+ }
221
+
222
+ }
223
+
224
+ module.exports = Squisher;
@@ -0,0 +1,60 @@
1
+ const { GameNode } = require('./GameNode');
2
+ const Shapes = require('./Shapes');
3
+ const ShapeUtils = require('./util/shapes');
4
+ const Colors = require('./Colors');
5
+ const Game = require('./Game');
6
+
7
+ const makePlane = (s) => {
8
+ return new GameNode.Shape({
9
+ shapeType: Shapes.POLYGON,
10
+ coordinates2d: ShapeUtils.rectangle(0, 0, s, s)
11
+ });
12
+ };
13
+
14
+ class ViewableGame extends Game {
15
+ #plane;
16
+ #fakeRoot;
17
+ #planeSize;
18
+ constructor(planeSize) {
19
+ super();
20
+ this.#planeSize = planeSize;
21
+ this.#plane = makePlane(this.planeSize);
22
+
23
+ this.#fakeRoot = new GameNode.Shape({
24
+ shapeType: Shapes.POLYGON,
25
+ coordinates2d: ShapeUtils.rectangle(0, 0, 0, 0),
26
+ fill: Colors.COLORS.BLACK,
27
+ });
28
+
29
+ this.layers = [
30
+ {
31
+ root: this.#fakeRoot
32
+ }
33
+ ];
34
+ }
35
+
36
+ updatePlaneSize(s) {
37
+ this.#planeSize = s;
38
+ this.#plane.node.coordinates2d = ShapeUtils.rectangle(0, 0, s, s);
39
+ }
40
+
41
+ getLayers() {
42
+ return this.layers;
43
+ }
44
+
45
+ getPlane() {
46
+ return this.#plane;
47
+ }
48
+
49
+ getPlaneSize() {
50
+ return this.#planeSize;
51
+ }
52
+
53
+ getViewRoot() {
54
+ return this.#fakeRoot;
55
+ }
56
+
57
+ }
58
+
59
+ module.exports = ViewableGame;
60
+
@@ -0,0 +1,15 @@
1
+ const { GameNode } = require('./GameNode');
2
+
3
+ module.exports = {
4
+ CONSTRUCTOR_TO_TYPE: {
5
+ 'Asset': 1,
6
+ 'Shape': 2,
7
+ 'Text': 3
8
+ },
9
+
10
+ TYPE_TO_CONSTRUCTOR: {
11
+ 1: GameNode.Asset,
12
+ 2: GameNode.Shape,
13
+ 3: GameNode.Text
14
+ }
15
+ };