Comparing version 0.6.43 to 0.7.1
12
index.js
@@ -6,5 +6,10 @@ const { squish, unsquish } = require('./src/squish'); | ||
const Game = require('./src/Game'); | ||
const ViewableGame = require('./src/ViewableGame'); | ||
const Shapes = require('./src/Shapes'); | ||
const shapeUtils = require('./src/util/shapes'); | ||
const viewUtils = require('./src/util/views'); | ||
const { terrainGenerator } = require('./src/terrain'); | ||
const subtypes = require('./src/subtypes'); | ||
const Squisher = require('./src/Squisher'); | ||
const GeometryUtils = require('./src/util/geometry'); | ||
@@ -15,8 +20,13 @@ module.exports = { | ||
gameNode, | ||
subtypes, | ||
Game, | ||
ViewableGame, | ||
GameNode, | ||
Colors, | ||
Shapes, | ||
Squisher, | ||
ShapeUtils: shapeUtils, | ||
TerrainGenerator: terrainGenerator | ||
ViewUtils: viewUtils, | ||
TerrainGenerator: terrainGenerator, | ||
GeometryUtils | ||
}; |
{ | ||
"name": "squishjs", | ||
"version": "0.6.43", | ||
"version": "0.7.1", | ||
"description": "squish & unsquish stuff", | ||
@@ -5,0 +5,0 @@ "scripts": { |
158
README.md
@@ -0,45 +1,119 @@ | ||
Squish is a way to utility package for [Homegames-core](https://github.com/homegamesio/homegames-core) and [Homegames-web](https://github.com/homegamesio/homegames-web). It contains several utilities and the class definitions for the entities most important to running Homegames. | ||
Below the term `First Class` is used to describe several functions and variables. This means those values should be treated as immutable, and in the case of functions are called automatically. | ||
## Exported Features | ||
* squish | ||
* unsquish | ||
* gameNode | ||
* GameNode | ||
* Colors | ||
* Game | ||
* Shapes | ||
* ShapeUtils | ||
* terrainGenerator | ||
## squish | ||
squish is a first class function that is used to compress the information of the game down to a set of integers. This compresses the data required to rendering down. | ||
## unsquish | ||
unsquish is a first class function that is used to uncompress squished entities. This uncompresses the data that was squished down for more easy manipulation and usage. | ||
## gameNode | ||
gameNode is the most foundational component to Homegames. The contents of a game is made up of a set of gameNodes that define entities, and display logic. Wrapped in a listener it contains the following: | ||
* id :- a first calss numeric identification number. It is unique in a Game. | ||
* color :- Defines the display color of the gameNode. See **Colors** | ||
* handleClick :- a function that defines what happens if a user clicks on the gameNode. The function passed in will automatically be called, and will pass the player who clicked as the argument. | ||
* coordinates2d :- an array of coordinates in a two dimensional plane that defines the vertices of the gameNode. It is recommened that **ShapeUtils** is used | ||
* border :- a number that defines the thickness of the border on the gameNode | ||
* fill :- Defines the color that fills the gameNode. See **Colors** | ||
* text :- Defines the text (if any) that will be displayed by the gameNode. It has the form | ||
* text :- The text to be displayed; | ||
* x :- The x-position of the top-left corner of the text. This is relative to the entire screen | ||
* y :- The y-position of the top-left corner of the text. This is relative to the entire screen | ||
* size :- The font size of the text | ||
* align :- 'left' | 'center' | 'right' | ||
* color :- The Color of the text, see **Colors** | ||
* asset :- Defines an asset (image, video, etc) to be displayed by the gameNode. Takes the form: | ||
* assetInfo :- The information for the display of the asset. | ||
* onClick :- a function that defines what happens if a user clicks on the asset. The function passed in will automatically be called, and will pass the player who clicked as the argument. | ||
* coordinates2d :- an array of coordinates in a two dimensional plane that defines the vertices of the gameNode. It is recommened that **ShapeUtils** is used | ||
* playerIds :- An array of player identification numbers. Defining who can see this asset. | ||
* effects :- Defines any additional effects applied to the gameNode. | ||
* input :- Defines whether the gameNode should accept an input or not. Takes the form: | ||
* type :- 'text' | 'file' | ||
* oninput :- A function that will be called with the inputting player's identification number and the data of the input. (player: number, data: any) => void; | ||
* playerIds :- An array of player identification numbers, that defines which players can see and interact with the gameNode. | ||
* listeners :- The set of listeners attached to the gameNode. | ||
* _animation :- An interval or timeout that can be used to modify the gameNode, such as lightening the color over time to give it a fade in, or darkening the color over time to give it a fade out. | ||
* onStateChange :- A first class function that will notify listeners of a state change. | ||
* addListener :- A first class function that will attach an additional listener to the gameNode. | ||
## GameNode | ||
A helpful wrapper and the recommended way of generatin a gameNode. It gives you the following: | ||
* Asset :- Generates a gameNode with an asset attached to it | ||
* Example Call: `Asset(onClick: (player) => console.log(player), coordinates2d: [[0, 1], [2, 1], [2, 3], [0, 3], [0, 1]], assetInfo: { myAsset: { pos: { x: 0, y: 1 }, size: { x: 2, y: 3 }}}, playerIds: [2])` | ||
* This will generate an asset based on `myAsset` and on clicking on the asset the asset will log out the clicking player. | ||
* Shape :- Generates a generic two dimenstional gameNode | ||
* Example Call: `Shape({ color: COLORS.CANDY_RED, onClick: (player) => console.log(player), shapeType: Shapes.POLYGON, coordinates2d: [[0, 1], [2, 1], [2, 3], [0, 3], [0, 1]], border: 2, fill: COLORS.CANDY_RED, playerIds: [2], null, null })` | ||
* This will generate a rectangle of color COLORS.CANDY_RED, visible to player of identification number 2, and onClick will log out the player clicking. | ||
* Text :- Generates a gameNode with text attached to it. | ||
* Example Call: `Text({ textInfo: { text: "My Text", color: COLORS.CANDY_RED, align: 'center', x: 2, y: 3, size: 1 }, playerIds: [2] })` | ||
* This will generate a gameNode with texting starting at the (2, 3) position, visible to player of identification number 2. The Text will be centered and CANDY_RED, and will be "My Text". | ||
## Colors | ||
Colors is a utility for using shared homegames RGBA values. Colors is exported as an object with two keys: COLORS & randomColor. | ||
**COLORS** is an object that takes the form of: `string -> RGBA Array`. For example we can GET the RBGA value for Homegames CANDY_RED (RGBA of [246, 84, 106, 255]) by doing the following `COLORS.CANDY_RED`. Thus COLORS serves as a map between strings and in-built colors. | ||
**randomColor** is a function that takes the form: `string[] => RGBA array`. It is a function to get a random RGBA from the set of RGBA that Homegames provides. It can take in array of strings that limits the options, allowing for the exclusion of certain RGBA values from returning. For example we can call this function like `randomColor(["CANDY_RED"])` which would return an RGBA array from the set of COLORS while preventing CANDY_RED from being a possible return value. | ||
## Game | ||
Game is the base class definition for a game in Homegames. Most games will extend the definition to Game, and leverage most or all of its internal logic. Game holds the following Member Variables: | ||
* players :- Object of the form `player id -> player object` | ||
* timeouts :- Array of NodeJS.Timeout that holds the timeout triggered logic for the Game | ||
* intervals :- Array of NodeJS.Timeout that holds the interval triggered logic for the Game | ||
* listeners :- the list of all listeners on this Game. | ||
* root :- The base GameNode of this game, for which all other GameNodes are children | ||
Game also holds the following Instance Functions | ||
* _hgAddPlayer :- This is a first class function, and is used to add a player to the Game's `players` value. | ||
* _hgRemovePlayer :- This is a first class function, and is used to remmove a player from the Game's `players` value. | ||
* addStateListener :- This is a first class function, and is used to add a listener to the Game's `listeners` value. | ||
* removeStateListener :- This is a first class function, and is used to remove a listener from the Game's `listeners` value. | ||
* getRoot :- This is a function and returns the Game's `root` value. An example call would be: `getRoot()`. It is expected that this function is overwritten in the extending class's definition. | ||
* setTimeout :- This is a function, and is used to add a timeout to the Game's `timeouts` value. It takes in two arguments a function to be called at the end of the timer, and how long the timer should be. An example call would be: `setTimeout( () => console.log("Welcome to Homegames"), 200)` | ||
* setInterval :- This is a function, and is used to add an interval to the Game's `intervals` value. It takes in two arguments a function to be called at the end of the timer, and how long the timer should be. An example call would be: `intervals(() => console.log("Welcome to Homegames"), 200)` | ||
* close :- This is a first class function, and is used to clear all `timeouts` and `intervals` from the Game, as the Game instance is spun down. | ||
## Shapes | ||
Shapes is functionally an enum of the types of shapes Homegames supports. | ||
## ShapeUtils | ||
ShapeUtils is a utility for building shapes for use in GameNodes. ShapeUtils is exported as an object with two keys rectangle & triangle. | ||
**rectangle** is a function that takes the form `(startX, startY, width, height) => the rectangle's coordinate set`. Thus rectangle creates an array from the starting x position, the starting y position, the width, and the height that defines the vertices of the rectangle. An example call would be: `rectangle(0, 1, 2, 3)` which would create a rectangle with the top-left corner at (0,1), and bottom-right corner at (2, 4). | ||
**triangle** is a function that takes the form `(x1, y1, x2, y2, x3, y3) => the triangle's coordinate set`. Thus triangle creates an array from provided vertex pairings. An example call would be `triangle(0, 1, 2, 3, 4, 5)` which would create a triangle with vertices at (0, 1), (2, 3), (4, 5). | ||
## terrainGenerator | ||
terrainGenerator is a utility function that generates a "board" from the provided information. This "board" functions as a map, defining regions of the board that are randomly marked to be "filled". This can be used to procedurally generate fields of play with parts of the map unaccessible to players. The "board" is guaranteed that all un-filled points are contiguous. An example call would be: `terrainGenerator({ x: 50, y: 100 }, { x: 2, y: 3 }, 5)`. This would generate board of area 50 * 100. The "filled" regions would be of area 2 * 3, and a maximum of 5 such "filled" regions will be placed. The outcome of this would be two-dimensionl area, where each element has the form | ||
``` | ||
> const { GameNode, Shapes, Colors, ShapeUtils, squish, unsquish } = require('squishjs'); | ||
> | ||
> const redSquare = new GameNode.Shape({ | ||
... fill: Colors.COLORS.RED, | ||
... coordinates2d: ShapeUtils.rectangle(20, 20, 60, 60), | ||
... shapeType: Shapes.POLYGON | ||
... }); | ||
> | ||
> const squished = squish(redSquare.node); | ||
> const unsquished = unsquish(squished); | ||
> | ||
> squished | ||
[ | ||
3, 35, 43, 3, 0, | ||
44, 2, 52, 22, 20, | ||
0, 20, 0, 80, 0, | ||
20, 0, 80, 0, 80, | ||
0, 20, 0, 80, 0, | ||
20, 0, 20, 0, 53, | ||
6, 255, 0, 0, 255 | ||
] | ||
> | ||
> unsquished | ||
InternalGameNode { | ||
id: 0, | ||
children: [], | ||
color: undefined, | ||
handleClick: undefined, | ||
coordinates2d: [ | ||
20, 20, 80, | ||
20, 80, 80, | ||
20, 80, 20, | ||
20 | ||
], | ||
border: undefined, | ||
fill: [ 255, 0, 0, 255 ], | ||
text: undefined, | ||
asset: undefined, | ||
effects: null, | ||
input: null, | ||
listeners: Set {}, | ||
playerIds: [] | ||
{ | ||
filled: boolean | ||
north: boolean | ||
south: boolean | ||
east: boolean | ||
west: boolean | ||
} | ||
``` | ||
**filled** of true means this place can be marked as inaccessible to players | ||
**north** of true means from this place a player can move "north". | ||
**south** of true means from this place a player can move "south". | ||
**east** of true means from this place a player can move "east". | ||
**west** of true means from this place a player can move "west". |
class Game { | ||
constructor() { | ||
this.players = {}; | ||
this.spectators = {}; | ||
this.listeners = new Set(); | ||
this.root = null; | ||
this.intervals = []; | ||
@@ -26,4 +26,16 @@ this.timeouts = []; | ||
getRoot() { | ||
return this.root; | ||
findNode(id) { | ||
let found = null; | ||
if (this.getLayers) { | ||
const layers = this.getLayers(); | ||
for (let layerIndex in layers) { | ||
found = layers[layerIndex].root.findChild(id); | ||
if (found) { | ||
break; | ||
} | ||
} | ||
} | ||
return found; | ||
} | ||
@@ -30,0 +42,0 @@ |
@@ -1,108 +0,111 @@ | ||
const listenable = require("./util/listenable"); | ||
const InternalGameNode = require('./InternalGameNode'); | ||
const Shapes = require('./Shapes'); | ||
const SUBTYPES = require('./subtypes'); | ||
const { gameNode, BaseNode } = require('./BaseNode'); | ||
const gameNode = (color, onClick, coordinates2d, border, fill, text, asset, playerIds, effects, input) => { | ||
const node = new InternalGameNode(color, onClick, coordinates2d, border, fill, text, asset, playerIds, effects, input); | ||
return listenable(node, node.onStateChange.bind(node)); | ||
const shapeTypeToSubtype = { | ||
[Shapes.CIRCLE]: SUBTYPES.SHAPE_2D_CIRCLE, | ||
[Shapes.POLYGON]: SUBTYPES.SHAPE_2D_POLYGON, | ||
[Shapes.LINE]: SUBTYPES.SHAPE_2D_LINE | ||
}; | ||
class Shape { | ||
constructor({ color, onClick, shapeType, coordinates2d, border, fill, playerIds, effects, input }) { | ||
if (!coordinates2d || !shapeType) { | ||
const subtypeToShapeType = { | ||
[SUBTYPES.SHAPE_2D_CIRCLE]: Shapes.CIRCLE, | ||
[SUBTYPES.SHAPE_2D_POLYGON]: Shapes.POLYGON, | ||
[SUBTYPES.SHAPE_2D_LINE]: Shapes.LINE | ||
}; | ||
class Shape extends BaseNode { | ||
constructor({ color, onClick, shapeType, coordinates2d, border, fill, playerIds, effects, input, node, id }) { | ||
if ((!coordinates2d || !shapeType) && !(node)) { | ||
throw new Error("Shape requires coordinates2d and shapeType"); | ||
} | ||
this.node = gameNode(color, onClick, coordinates2d, border, fill, null, null, playerIds, effects, input); | ||
this.id = this.node.id; | ||
super({ | ||
color, | ||
onClick, | ||
coordinates2d, | ||
border, | ||
fill, | ||
playerIds, | ||
effects, | ||
input, | ||
node, | ||
subtype: shapeTypeToSubtype[shapeType], | ||
id | ||
}); | ||
} | ||
addChild(child) { | ||
this.node.addChild(child); | ||
clone({ handleClick, input, id }) { | ||
const _id = id || null; | ||
return new Shape({ | ||
color: this.node.color, | ||
onClick: handleClick, | ||
shapeType: subtypeToShapeType[this.node.subType], | ||
coordinates2d: this.node.coordinates2d, | ||
border: this.node.border, | ||
fill: this.node.fill, | ||
playerIds: this.node.playerIds, | ||
effects: this.node.effects, | ||
input, | ||
id: _id | ||
}); | ||
} | ||
addChildren(...nodes) { | ||
for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { | ||
this.addChild(nodes[nodeIndex]); | ||
} | ||
} | ||
removeChild(nodeId) { | ||
this.node.removeChild(nodeId); | ||
} | ||
addListener(listener) { | ||
this.node.addListener(listener); | ||
} | ||
clearChildren(excludedNodeIds) { | ||
this.node.clearChildren(excludedNodeIds); | ||
} | ||
} | ||
class Text { | ||
constructor({ textInfo, playerIds, input }) { | ||
if (!textInfo) { | ||
class Text extends BaseNode { | ||
constructor({ textInfo, playerIds, input, node, id }) { | ||
if (!textInfo && !node) { | ||
throw new Error("Text node requires textInfo"); | ||
} | ||
this.node = gameNode(null, null, null, null, null, textInfo, null, playerIds, null, input); | ||
this.id = this.node.id; | ||
super({ | ||
textInfo, | ||
playerIds, | ||
input, | ||
node, | ||
subtype: SUBTYPES.TEXT, | ||
id | ||
}); | ||
} | ||
addChild(child) { | ||
this.node.addChild(child); | ||
} | ||
clone({ handleClick, input, id }) { | ||
const _id = id || null; | ||
addChildren(...nodes) { | ||
for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { | ||
this.addChild(nodes[nodeIndex]); | ||
} | ||
return new Text({ | ||
textInfo: Object.assign({}, this.node.text), | ||
playerIds: this.playerIds?.slice(), | ||
id: _id | ||
}); | ||
} | ||
removeChild(nodeId) { | ||
this.node.removeChild(nodeId); | ||
} | ||
addListener(listener) { | ||
this.node.addListener(listener); | ||
} | ||
clearChildren(excludedNodeIds) { | ||
this.node.clearChildren(excludedNodeIds); | ||
} | ||
} | ||
class Asset { | ||
constructor({ assetInfo, onClick, coordinates2d, playerIds, effects }) { | ||
if (!assetInfo) { | ||
class Asset extends BaseNode { | ||
constructor({ assetInfo, onClick, coordinates2d, playerIds, effects, node, id }) { | ||
if (!assetInfo && !node) { | ||
throw new Error("Asset node requires assetInfo"); | ||
} | ||
this.node = gameNode(null, onClick, coordinates2d, null, null, null, assetInfo, playerIds, effects); | ||
this.id = this.node.id; | ||
} | ||
addChild(child) { | ||
this.node.addChild(child); | ||
super({ | ||
assetInfo, | ||
onClick, | ||
coordinates2d, | ||
playerIds, | ||
effects, | ||
node, | ||
subtype: SUBTYPES.ASSET, | ||
id | ||
}); | ||
} | ||
addChildren(...nodes) { | ||
for (let nodeIndex = 0; nodeIndex < nodes.length; nodeIndex++) { | ||
this.addChild(nodes[nodeIndex]); | ||
} | ||
} | ||
clone({ handleClick, input, id }) { | ||
const _id = id || null; | ||
removeChild(nodeId) { | ||
this.node.removeChild(nodeId); | ||
return new Asset({ | ||
assetInfo: Object.assign({}, this.node.asset), | ||
playerIds: this.playerIds?.slice(), | ||
id: _id | ||
}); | ||
} | ||
addListener(listener) { | ||
this.node.addListener(listener); | ||
} | ||
clearChildren(excludedNodeIds) { | ||
this.node.clearChildren(excludedNodeIds); | ||
} | ||
} | ||
@@ -109,0 +112,0 @@ |
@@ -1,6 +0,6 @@ | ||
let id = 0; | ||
let _id = 0; | ||
class InternalGameNode { | ||
constructor(color, onClick, coordinates2d, border, fill, text, asset, playerIds = [], effects = null, input = null) { | ||
this.id = id++; | ||
constructor(color, onClick, coordinates2d, border, fill, text, asset, playerIds = [], effects = null, input = null, subType = null, id = null) { | ||
this.id = id ? Number(id) : _id++; | ||
this.children = new Array(); | ||
@@ -21,2 +21,3 @@ this.color = color; | ||
this.playerIds = playerIds || []; | ||
this.subType = subType; | ||
} | ||
@@ -23,0 +24,0 @@ |
@@ -1,382 +0,123 @@ | ||
const InternalGameNode = require("./InternalGameNode"); | ||
const Colors = require('./Colors'); | ||
const assert = require('assert'); | ||
const ASSET_TYPE = 1; | ||
const InternalGameNode = require("./InternalGameNode"); | ||
const { CONSTRUCTOR_TO_TYPE, TYPE_TO_CONSTRUCTOR } = require('./node-types'); | ||
const SUBTYPE_MAPPINGS = require('./subtype-mappings'); | ||
const COLOR_SUBTYPE = 42; | ||
const ID_SUBTYPE = 43; | ||
const PLAYER_ID_SUBTYPE = 44; | ||
const POS_SUBTYPE = 45; | ||
const SIZE_SUBTYPE = 46; | ||
const TEXT_SUBTYPE = 47; | ||
const ASSET_SUBTYPE = 48; | ||
const EFFECTS_SUBTYPE = 49; | ||
const ONCLICK_SUBTYPE = 50; | ||
const INPUT_SUBTYPE = 51; | ||
const COORDINATES_2D_SUBTYPE = 52; | ||
const FILL_SUBTYPE = 53; | ||
const BORDER_SUBTYPE = 54; | ||
const { squishId } = require('./squishHelpers/id'); | ||
const { squishColor } = require('./squishHelpers/color'); | ||
const { squishPlayerIds } = require('./squishHelpers/playerIds'); | ||
const { squishPos } = require('./squishHelpers/pos'); | ||
const { squishFill } = require('./squishHelpers/fill'); | ||
const { squishSize } = require('./squishHelpers/size'); | ||
const { squishHandleClick } = require('./squishHelpers/handleClick'); | ||
const { squishBorder } = require('./squishHelpers/border'); | ||
const { squishSubType } = require('./squishHelpers/subType'); | ||
const { squishInput } = require('./squishHelpers/input'); | ||
const { squishCoordinates2d } = require('./squishHelpers/coordinates2d'); | ||
const { squishEffect } = require('./squishHelpers/effect'); | ||
const { squishText } = require('./squishHelpers/text'); | ||
const { squishAsset } = require('./squishHelpers/asset'); | ||
const { getFractional, hypLength } = require('./util/'); | ||
const squishSpec = { | ||
id: { | ||
type: ID_SUBTYPE, | ||
squish: (i) => { | ||
return [i]; | ||
}, | ||
unsquish: (arr) => { | ||
return arr[0]; | ||
} | ||
}, | ||
color: { | ||
type: COLOR_SUBTYPE, | ||
squish: (c) => { | ||
return [c[0], c[1], c[2], c[3]]; | ||
}, | ||
unsquish: (squished) => { | ||
return [squished[0], squished[1], squished[2], squished[3]]; | ||
} | ||
}, | ||
playerIds: { | ||
type: PLAYER_ID_SUBTYPE, | ||
squish: (i) => i, | ||
unsquish: (squished) => squished | ||
}, | ||
pos: { | ||
type: POS_SUBTYPE, | ||
squish: (p) => { | ||
return [Math.floor(p.x), Math.round(100 * (p.x - Math.floor(p.x))), Math.floor(p.y), Math.round(100 * (p.y - Math.floor(p.y)))] | ||
}, | ||
unsquish: (squished) => { | ||
return { | ||
x: squished[0] + squished[1] / 100, | ||
y: squished[2] + squished[3] / 100 | ||
} | ||
} | ||
}, | ||
coordinates2d: { | ||
type: COORDINATES_2D_SUBTYPE, | ||
squish: (p, scale) => { | ||
const originalCoords = p.flat(); | ||
const squished = new Array(originalCoords.length * 2); | ||
for (const i in originalCoords) { | ||
if (scale) { | ||
const isX = i % 2 == 0; | ||
const scaleValue = isX ? scale.x : scale.y; | ||
const scaled = scaleValue * originalCoords[i]; | ||
id: squishId, | ||
color: squishColor, | ||
playerIds: squishPlayerIds, | ||
pos: squishPos, | ||
coordinates2d: squishCoordinates2d, | ||
fill: squishFill, | ||
size: squishSize, | ||
text: squishText, | ||
asset: squishAsset, | ||
effects: squishEffect, | ||
handleClick:squishHandleClick, | ||
border: squishBorder, | ||
subType: squishSubType, | ||
input: squishInput | ||
}; | ||
const removedSpace = Math.round(100 * (1 - scaleValue)); | ||
const typeToSquishMap = {}; | ||
const shifted = scaled + (removedSpace / 2); | ||
for (const key in squishSpec) { | ||
typeToSquishMap[Number(squishSpec[key]['type'])] = key; | ||
} | ||
squished[2 * i] = shifted; | ||
squished[(2 * i) + 1] = getFractional(shifted); | ||
const unsquish = (squished) => { | ||
assert(squished[0] == 3); | ||
} else { | ||
squished[2 * i] = Math.floor(originalCoords[i]); | ||
squished[(2 * i) + 1] = Math.round(100 * (originalCoords[i] - Math.floor(originalCoords[i]))); | ||
} | ||
} | ||
assert(squished.length === squished[1]); | ||
return squished; | ||
}, | ||
unsquish: (squished) => { | ||
const unsquished = new Array(squished.length / 2); | ||
for (let i = 0; i < squished.length; i += 2) { | ||
const value = squished[i] + (squished[i + 1] / 100); | ||
unsquished[i / 2] = value; | ||
} | ||
return unsquished; | ||
} | ||
}, | ||
fill: { | ||
type: FILL_SUBTYPE, | ||
squish: (c) => { | ||
return [c[0], c[1], c[2], c[3]]; | ||
}, | ||
unsquish: (squished) => { | ||
return [squished[0], squished[1], squished[2], squished[3]]; | ||
} | ||
}, | ||
size: { | ||
type: SIZE_SUBTYPE, | ||
squish: (s) => { | ||
return [Math.floor(s.x), Math.round(100 * (s.x - Math.floor(s.x))), Math.floor(s.y), Math.round(100 * (s.y - Math.floor(s.y)))] | ||
}, | ||
unsquish: (squished) => { | ||
return { | ||
x: squished[0] + squished[1] / 100, | ||
y: squished[2] + squished[3] / 100 | ||
} | ||
} | ||
}, | ||
text: { | ||
type: TEXT_SUBTYPE, | ||
squish: (t, scale) => { | ||
const textX = scale ? (t.x * scale.x) + Math.round(100 * (1 - scale.x)) / 2 : t.x; | ||
const textY = scale ? (t.y * scale.y) + Math.round(100 * (1 - scale.y)) / 2 : t.y; | ||
let squishedIndex = 3; | ||
const align = t.align || 'left'; | ||
const squishedText = new Array(t.text.length + 10 + align.length); | ||
squishedText[0] = Math.floor(textX); | ||
squishedText[1] = Math.round(100 * (textX - Math.floor(textX))); | ||
let constructedInternalNode = new InternalGameNode(); | ||
squishedText[2] = Math.floor(textY); | ||
squishedText[3] = Math.round(100 * (textY - Math.floor(textY))); | ||
const textSize = t.size || 1; | ||
const scaledTextSize = scale ? textSize * hypLength(scale.x, scale.y) : textSize; | ||
while(squishedIndex < squished.length) { | ||
squishedText[4] = Math.floor(scaledTextSize); | ||
squishedText[5] = Math.round(100 * (scaledTextSize - Math.floor(scaledTextSize))); | ||
const subFrameType = squished[squishedIndex]; | ||
const subFrameLength = squished[squishedIndex + 1]; | ||
const subFrame = squished.slice(squishedIndex + 2, squishedIndex + subFrameLength); | ||
const textColor = t.color || Colors.BLACK; | ||
const squishedTextColor = squishSpec.color.squish(textColor); | ||
for (let i = 0; i < squishedTextColor.length; i++) { | ||
squishedText[6 + i] = squishedTextColor[i]; | ||
} | ||
squishedText[6 + squishedTextColor.length] = align.length; | ||
for (let i = 0; i < align.length; i++) { | ||
squishedText[6 + squishedTextColor.length + 1 + i] = align.codePointAt(i); | ||
} | ||
for (let i = 0; i < t.text.length; i++) { | ||
squishedText[6 + squishedTextColor.length + align.length + 1 + i] = t.text.codePointAt(i); | ||
} | ||
return squishedText; | ||
}, | ||
unsquish: (squished) => { | ||
const textPosX = squished[0] + squished[1] / 100; | ||
const textPosY = squished[2] + squished[3] / 100; | ||
const textSize = squished[4] + squished[5] / 100; | ||
const textColor = squished.slice(6, 10); | ||
const textAlignLength = squished[10]; | ||
const align = String.fromCodePoint.apply(null, squished.slice(11, 11 + textAlignLength)); | ||
const text = String.fromCodePoint.apply(null, squished.slice(11 + textAlignLength)); | ||
return { | ||
x: textPosX, | ||
y: textPosY, | ||
text: text, | ||
size: textSize, | ||
color: textColor, | ||
align | ||
}; | ||
if (!typeToSquishMap[subFrameType]) { | ||
console.warn("Unknown sub frame type " + subFrameType); | ||
break; | ||
} else { | ||
const objField = typeToSquishMap[subFrameType]; | ||
const unsquishFun = squishSpec[objField]['unsquish']; | ||
// anything that was declared as a dependency of this property will be available when | ||
// calling this property's unsquish function | ||
const dependencyReference = Object.assign({}, constructedInternalNode); | ||
const unsquishedVal = unsquishFun(subFrame, dependencyReference); | ||
constructedInternalNode[objField] = unsquishedVal; | ||
} | ||
}, | ||
asset: { | ||
type: ASSET_SUBTYPE, | ||
squish: (a, scale) => { | ||
const assetKey = Object.keys(a)[0]; | ||
const squishedAssets = new Array(8 + assetKey.length); | ||
squishedIndex += subFrameLength; | ||
} | ||
const asset = a[assetKey]; | ||
const constructor = TYPE_TO_CONSTRUCTOR[squished[2]]; | ||
return new constructor({ node: constructedInternalNode }); | ||
} | ||
const posX = scale ? ((scale.x * asset.pos.x) + Math.round(100 * (1 - scale.x)) / 2) : asset.pos.x; | ||
const posY = scale ? ((scale.y * asset.pos.y) + Math.round(100 * (1 - scale.y)) / 2) : asset.pos.y; | ||
// When squishing, we need to make sure that properties that other properties depend on are inserted first. | ||
// This is because when unsquishing, we need to guarantee that the dependee is available to the function responsible | ||
// for creating the dependant | ||
const sortSpecKeys = () => { | ||
const keysWithDeps = Object.keys(squishSpec).filter(key => { | ||
return squishSpec[key].dependsOn && squishSpec[key].dependsOn.length > 0; | ||
}); | ||
const sizeX = scale ? scale.x * asset.size.x : asset.size.x; | ||
const sizeY = scale ? scale.y * asset.size.y : asset.size.y; | ||
const keysWithoutDeps = Object.keys(squishSpec).filter(key => { | ||
return !squishSpec[key].dependsOn || squishSpec[key].dependsOn.length === 0; | ||
}); | ||
squishedAssets[0] = Math.floor(posX); | ||
squishedAssets[1] = getFractional(posX); | ||
// todo: recursively find circular deps | ||
squishedAssets[2] = Math.floor(posY); | ||
squishedAssets[3] = getFractional(posY); | ||
squishedAssets[4] = Math.floor(sizeX); | ||
squishedAssets[5] = getFractional(sizeX); | ||
squishedAssets[6] = Math.floor(sizeY); | ||
squishedAssets[7] = getFractional(sizeY); | ||
for (let i = 0; i < assetKey.length; i++) { | ||
squishedAssets[8 + i] = assetKey.codePointAt(i); | ||
} | ||
return squishedAssets; | ||
}, | ||
unsquish: (squished) => { | ||
const assetPosX = squished[0] + squished[1] / 100; | ||
const assetPosY = squished[2] + squished[3] / 100; | ||
const assetSizeX = squished[4] + squished[5] / 100; | ||
const assetSizeY = squished[6] + squished[7] / 100; | ||
const assetKey = String.fromCodePoint.apply(null, squished.slice(8)); | ||
return { | ||
[assetKey]: { | ||
pos: { | ||
x: assetPosX, | ||
y: assetPosY | ||
}, | ||
size: { | ||
x: assetSizeX, | ||
y: assetSizeY | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
effects: { | ||
type: EFFECTS_SUBTYPE, | ||
squish: (a) => { | ||
if (a['shadow']) { | ||
const assetKey = 'shadow'; | ||
let squishedLength = assetKey.length + 4; // + 4 for color | ||
if (a['shadow'].blur) { | ||
squishedLength += 2; | ||
} | ||
const squishedEffects = new Array(squishedLength); | ||
for (let i = 0; i < assetKey.length; i++) { | ||
squishedEffects[i] = assetKey.codePointAt(i); | ||
} | ||
squishedEffects[assetKey.length] = a.shadow.color[0]; | ||
squishedEffects[assetKey.length + 1] = a.shadow.color[1]; | ||
squishedEffects[assetKey.length + 2] = a.shadow.color[2]; | ||
squishedEffects[assetKey.length + 3] = a.shadow.color[3]; | ||
if (a.shadow.blur) { | ||
squishedEffects[assetKey.length + 4] = Math.floor(a.shadow.blur / 10) | ||
squishedEffects[assetKey.length + 5] = a.shadow.blur % 10 | ||
} | ||
return squishedEffects; | ||
} | ||
}, | ||
unsquish: (squished) => { | ||
// 'shadow' is all (for now) | ||
const assetKey = String.fromCodePoint.apply(null, squished.slice(0, 6)); | ||
const color = squished.slice(6, 10); | ||
let blur; | ||
if (squished.length > 10) { | ||
blur = squished[10] * 10 + squished[11]; | ||
} | ||
const unsquished = { | ||
[assetKey]: { | ||
color | ||
} | ||
}; | ||
if (blur) { | ||
unsquished[assetKey].blur = blur; | ||
} | ||
return unsquished; | ||
} | ||
}, | ||
handleClick: { | ||
type: ONCLICK_SUBTYPE, | ||
squish: (a) => { | ||
return a ? [1] : [0]; | ||
}, | ||
unsquish: (a) => { | ||
return a[0] === 1; | ||
} | ||
}, | ||
border: { | ||
type: BORDER_SUBTYPE, | ||
squish: (a) => { | ||
return [a]; | ||
}, | ||
unsquish: (s) => { | ||
return s[0]; | ||
} | ||
}, | ||
input: { | ||
type: INPUT_SUBTYPE, | ||
squish: (a) => { | ||
const squished = new Array(a.type.length); | ||
for (let i = 0; i < a.type.length; i++) { | ||
squished[i] = a.type.codePointAt(i); | ||
} | ||
return squished; | ||
}, | ||
unsquish: (squished) => { | ||
return { | ||
type: String.fromCodePoint.apply(null, squished) | ||
} | ||
} | ||
} | ||
}; | ||
const squishSpecKeys = [ | ||
'id', | ||
'color', | ||
'playerIds', | ||
'coordinates2d', | ||
'fill', | ||
'pos', | ||
'size', | ||
'text', | ||
'asset', | ||
'effects', | ||
'border', | ||
'handleClick', | ||
'input' | ||
]; | ||
const typeToSquishMap = {}; | ||
for (const key in squishSpec) { | ||
typeToSquishMap[Number(squishSpec[key]['type'])] = key; | ||
return [keysWithoutDeps, keysWithDeps].flat(); | ||
} | ||
const unsquish = (squished) => { | ||
assert(squished[0] == 3); | ||
assert(squished.length === squished[1]); | ||
const squish = (entity, scale = null) => { | ||
let squishedPieces = []; | ||
let squishedIndex = 2; | ||
const internalNode = entity.node; | ||
let constructedGameNode = new InternalGameNode(); | ||
const sortedSpecKeys = sortSpecKeys(); | ||
while(squishedIndex < squished.length) { | ||
const subFrameType = squished[squishedIndex]; | ||
const subFrameLength = squished[squishedIndex + 1]; | ||
const subFrame = squished.slice(squishedIndex + 2, squishedIndex + subFrameLength); | ||
if (!typeToSquishMap[subFrameType]) { | ||
console.warn("Unknown sub frame type " + subFrameType); | ||
break; | ||
} else { | ||
const objField = typeToSquishMap[subFrameType]; | ||
const unsquishFun = squishSpec[objField]['unsquish']; | ||
const unsquishedVal = unsquishFun(subFrame); | ||
constructedGameNode[objField] = unsquishedVal; | ||
for (const keyIndex in sortedSpecKeys) { | ||
const key = sortedSpecKeys[keyIndex]; | ||
if (key in internalNode) { | ||
const attr = internalNode[key]; | ||
if (attr !== undefined && attr !== null) { | ||
const squished = squishSpec[key].squish(attr, scale, internalNode); | ||
squishedPieces.push([squishSpec[key]['type'], squished.length + 2, ...squished]); | ||
} | ||
squishedIndex += subFrameLength; | ||
} | ||
return constructedGameNode; | ||
} | ||
const squish = (entity, scale = null) => { | ||
let squishedPieces = []; | ||
let nodeClassCode = CONSTRUCTOR_TO_TYPE[entity.constructor.name]; | ||
for (const keyIndex in squishSpecKeys) { | ||
const key = squishSpecKeys[keyIndex]; | ||
if (key in entity) { | ||
const attr = entity[key]; | ||
if (attr !== undefined && attr !== null) { | ||
const squished = squishSpec[key].squish(attr, scale); | ||
squishedPieces.push([squishSpec[key]['type'], squished.length + 2, ...squished]); | ||
} | ||
} | ||
// implemented for json support (infer from subtype instead of custom json property) | ||
if (!nodeClassCode) { | ||
nodeClassCode = CONSTRUCTOR_TO_TYPE[SUBTYPE_MAPPINGS[internalNode.subType]]; | ||
} | ||
const squished = squishedPieces.flat(); | ||
return [3, squished.length + 2, ...squished]; | ||
return [3, squished.length + 3, nodeClassCode, ...squished]; | ||
@@ -383,0 +124,0 @@ } |
const { squish, unsquish } = require('../src/squish'); | ||
const { GameNode } = require('../src/GameNode'); | ||
const { COLORS, randomColor } = require("../src/Colors"); | ||
const { COLORS } = require("../src/Colors"); | ||
const Shapes = require('../src/Shapes'); | ||
const ShapeUtils = require('../src/util/shapes'); | ||
const { verifyArrayEquality } = require('./utils'); | ||
@@ -24,6 +25,3 @@ const assert = require('assert'); | ||
} else { | ||
const preSquishFlat = preSquish.coordinates2d.flat(); | ||
for (let i = 0; i < unsquished.coordinates2d.length; i++) { | ||
assert(preSquishFlat[i] === unsquished.coordinates2d[i]); | ||
} | ||
verifyArrayEquality(preSquish.coordinates2d, unsquished.coordinates2d); | ||
} | ||
@@ -71,4 +69,4 @@ } else if (key === 'input' || key == 'text') { | ||
}); | ||
const squishedGameNode = squish(gameNode.node); | ||
const unsquishedGameNode = unsquish(squishedGameNode); | ||
const squishedGameNode = squish(gameNode); | ||
const unsquishedGameNode = unsquish(squishedGameNode).node; | ||
compareSquished(gameNode.node, unsquishedGameNode); | ||
@@ -85,4 +83,4 @@ }); | ||
const squishedGameNode = squish(gameNode.node); | ||
const unsquishedGameNode = unsquish(squishedGameNode); | ||
const squishedGameNode = squish(gameNode); | ||
const unsquishedGameNode = unsquish(squishedGameNode).node; | ||
compareSquished(gameNode.node, unsquishedGameNode); | ||
@@ -104,8 +102,8 @@ assert(unsquishedGameNode.playerIds.length == 2); | ||
color: COLORS.RED | ||
}, | ||
}, | ||
playerIds | ||
}); | ||
const squishedGameNode = squish(gameNode.node); | ||
const unsquishedGameNode = unsquish(squishedGameNode); | ||
const squishedGameNode = squish(gameNode); | ||
const unsquishedGameNode = unsquish(squishedGameNode).node; | ||
compareSquished(gameNode.node, unsquishedGameNode); | ||
@@ -118,2 +116,24 @@ assert(unsquishedGameNode.playerIds.length == 255); | ||
test("Text node with unicode", () => { | ||
const gameNode = new GameNode.Text({ | ||
textInfo: { | ||
text: '💯😂💯', | ||
x: 40, | ||
y: 40, | ||
size: 1, | ||
align: '😂😂😂😂😂',//center', | ||
color: COLORS.BLACK | ||
} | ||
}); | ||
console.log("123:") | ||
const squishedNode = new Uint8ClampedArray(squish(gameNode.node)); | ||
const unsquishedNode = unsquish(squishedNode); | ||
compareSquished(squishedNode.node, unsquishedNode); | ||
assert(unsquishedNode.text.text.length === 6); | ||
assert([...unsquishedNode.text.text].length === 3); | ||
assert(unsquishedNode.text.text.codePointAt(0) === '💯'.codePointAt(0)); | ||
assert(unsquishedNode.text.text.codePointAt(4) === '💯'.codePointAt(0)); | ||
}); | ||
test("Text node", () => { | ||
@@ -123,3 +143,3 @@ const gameNode = new GameNode.Text({ | ||
text: 'ayy lmao', | ||
x: 40, | ||
x: 40, | ||
y: 40, | ||
@@ -132,4 +152,4 @@ size: 1, | ||
const squishedNode = squish(gameNode.node); | ||
const unsquishedNode = unsquish(squishedNode); | ||
const squishedNode = squish(gameNode); | ||
const unsquishedNode = unsquish(squishedNode).node; | ||
compareSquished(squishedNode.node, unsquishedNode); | ||
@@ -154,4 +174,4 @@ }); | ||
}); | ||
const squishedNode = squish(gameNode.node); | ||
const unsquishedNode = unsquish(squishedNode); | ||
const squishedNode = squish(gameNode); | ||
const unsquishedNode = unsquish(squishedNode).node; | ||
compareSquished(squishedNode.node, unsquishedNode); | ||
@@ -174,4 +194,4 @@ }); | ||
const squishedNode = squish(gameNode.node); | ||
const unsquishedNode = unsquish(squishedNode); | ||
const squishedNode = squish(gameNode); | ||
const unsquishedNode = unsquish(squishedNode).node; | ||
compareSquished(squishedNode.node, unsquishedNode); | ||
@@ -191,4 +211,4 @@ | ||
const squished = squish(gameNode.node); | ||
const unsquished = unsquish(squished); | ||
const squished = squish(gameNode); | ||
const unsquished = unsquish(squished).node; | ||
@@ -202,3 +222,3 @@ compareSquished(gameNode.node, unsquished); | ||
text: 'ayy lmao', | ||
x: 40, | ||
x: 40, | ||
y: 40, | ||
@@ -217,4 +237,4 @@ size: 1, | ||
const squishedNode = squish(gameNode.node); | ||
const unsquishedNode = unsquish(squishedNode); | ||
const squishedNode = squish(gameNode); | ||
const unsquishedNode = unsquish(squishedNode).node; | ||
compareSquished(gameNode.node, unsquishedNode); | ||
@@ -234,3 +254,3 @@ }); | ||
// .8 (y scale factor) * 100 (height) = 80 | ||
// This gets you the correct size. | ||
// This gets you the correct size. | ||
// To scale the position to re-center the data, we need to shift x and y by 1/2 of the amount of space we just removed in each direction. | ||
@@ -241,12 +261,12 @@ // So we removed 20 units from the width, and now we need to shift everything right 10 units to keep it horizontally centered. | ||
const squishedScaledNode = squish(gameNode.node, {x: .8, y: .8}); | ||
const unsquishedNode = unsquish(squishedScaledNode); | ||
const squishedScaledNode = squish(gameNode, {x: .8, y: .8}); | ||
const unsquishedNode = unsquish(squishedScaledNode).node; | ||
// top left | ||
assert(unsquishedNode.coordinates2d[0] == 10); | ||
assert(unsquishedNode.coordinates2d[1] == 10); | ||
assert(unsquishedNode.coordinates2d[0][0] == 10); | ||
assert(unsquishedNode.coordinates2d[0][1] == 10); | ||
// bottom right | ||
assert(unsquishedNode.coordinates2d[4] == 90); | ||
assert(unsquishedNode.coordinates2d[5] == 90); | ||
assert(unsquishedNode.coordinates2d[2][0] == 90); | ||
assert(unsquishedNode.coordinates2d[2][1] == 90); | ||
}); | ||
@@ -271,4 +291,4 @@ | ||
const squishedScaledNode = squish(gameNode.node, {x: xScale, y: yScale}); | ||
const unsquishedNode = unsquish(squishedScaledNode); | ||
const squishedScaledNode = squish(gameNode, {x: xScale, y: yScale}); | ||
const unsquishedNode = unsquish(squishedScaledNode).node; | ||
@@ -294,4 +314,4 @@ assert(unsquishedNode.text.x.toFixed(2) === (4 * xScale + Math.round((1 - xScale) * 100) / 2).toFixed(2)); | ||
const squishedNode = squish(gameNode.node, {x: .85, y: .85}); | ||
const unsquishedNode = unsquish(squishedNode); | ||
const squishedNode = squish(gameNode, {x: .85, y: .85}); | ||
const unsquishedNode = unsquish(squishedNode).node; | ||
@@ -298,0 +318,0 @@ const asset = unsquishedNode.asset['some-asset-ref']; |
@@ -16,3 +16,2 @@ const { terrainGenerator } = require("../src/terrain"); | ||
}); | ||
console.log(rtnString); | ||
}; | ||
@@ -19,0 +18,0 @@ |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Copyleft License
License(Experimental) Copyleft license information was found.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Non-permissive License
License(Experimental) A license not known to be considered permissive was found.
Found 1 instance in 1 package
58
1
100
3830
120
159002
3
1