2020-05-09 17:14:11 +00:00
|
|
|
/*
|
|
|
|
* Lots of code here is copied 1:1 from actual game files
|
|
|
|
*
|
|
|
|
*/
|
2020-05-09 14:45:23 +00:00
|
|
|
|
|
|
|
/** @enum {string} */
|
|
|
|
const enumSubShape = {
|
|
|
|
rect: "rect",
|
|
|
|
circle: "circle",
|
|
|
|
star: "star",
|
|
|
|
windmill: "windmill",
|
|
|
|
};
|
|
|
|
|
|
|
|
/** @enum {string} */
|
|
|
|
const enumSubShapeToShortcode = {
|
|
|
|
[enumSubShape.rect]: "R",
|
|
|
|
[enumSubShape.circle]: "C",
|
|
|
|
[enumSubShape.star]: "S",
|
|
|
|
[enumSubShape.windmill]: "W",
|
|
|
|
};
|
|
|
|
|
|
|
|
/** @enum {enumSubShape} */
|
|
|
|
const enumShortcodeToSubShape = {};
|
|
|
|
for (const key in enumSubShapeToShortcode) {
|
|
|
|
enumShortcodeToSubShape[enumSubShapeToShortcode[key]] = key;
|
|
|
|
}
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
const arrayQuadrantIndexToOffset = [
|
|
|
|
{ x: 1, y: -1 }, // tr
|
|
|
|
{ x: 1, y: 1 }, // br
|
|
|
|
{ x: -1, y: 1 }, // bl
|
|
|
|
{ x: -1, y: -1 }, // tl
|
|
|
|
];
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
// From colors.js
|
|
|
|
/** @enum {string} */
|
|
|
|
const enumColors = {
|
|
|
|
red: "red",
|
|
|
|
green: "green",
|
|
|
|
blue: "blue",
|
|
|
|
|
|
|
|
yellow: "yellow",
|
|
|
|
purple: "purple",
|
|
|
|
cyan: "cyan",
|
|
|
|
|
|
|
|
white: "white",
|
|
|
|
uncolored: "uncolored",
|
|
|
|
};
|
|
|
|
|
|
|
|
/** @enum {string} */
|
|
|
|
const enumColorToShortcode = {
|
|
|
|
[enumColors.red]: "r",
|
|
|
|
[enumColors.green]: "g",
|
|
|
|
[enumColors.blue]: "b",
|
|
|
|
|
|
|
|
[enumColors.yellow]: "y",
|
|
|
|
[enumColors.purple]: "p",
|
|
|
|
[enumColors.cyan]: "c",
|
|
|
|
|
|
|
|
[enumColors.white]: "w",
|
|
|
|
[enumColors.uncolored]: "u",
|
|
|
|
};
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
/** @enum {string} */
|
|
|
|
const enumColorsToHexCode = {
|
|
|
|
[enumColors.red]: "#ff666a",
|
|
|
|
[enumColors.green]: "#78ff66",
|
|
|
|
[enumColors.blue]: "#66a7ff",
|
|
|
|
|
|
|
|
// red + green
|
|
|
|
[enumColors.yellow]: "#fcf52a",
|
|
|
|
|
|
|
|
// red + blue
|
|
|
|
[enumColors.purple]: "#dd66ff",
|
|
|
|
|
|
|
|
// blue + green
|
|
|
|
[enumColors.cyan]: "#87fff5",
|
|
|
|
|
|
|
|
// blue + green + red
|
|
|
|
[enumColors.white]: "#ffffff",
|
|
|
|
|
|
|
|
[enumColors.uncolored]: "#aaaaaa",
|
|
|
|
};
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/** @enum {enumColors} */
|
|
|
|
const enumShortcodeToColor = {};
|
|
|
|
for (const key in enumColorToShortcode) {
|
|
|
|
enumShortcodeToColor[enumColorToShortcode[key]] = key;
|
|
|
|
}
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
CanvasRenderingContext2D.prototype.beginCircle = function (x, y, r) {
|
|
|
|
if (r < 0.05) {
|
|
|
|
this.beginPath();
|
|
|
|
this.rect(x, y, 1, 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.beginPath();
|
|
|
|
this.arc(x, y, r, 0, 2.0 * Math.PI);
|
|
|
|
};
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
function radians(degrees) {
|
|
|
|
return (degrees * Math.PI) / 180.0;
|
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/**
|
|
|
|
* Generates the definition from the given short key
|
|
|
|
*/
|
|
|
|
function fromShortKey(key) {
|
|
|
|
const sourceLayers = key.split(":");
|
|
|
|
let layers = [];
|
|
|
|
for (let i = 0; i < sourceLayers.length; ++i) {
|
|
|
|
const text = sourceLayers[i];
|
|
|
|
if (text.length !== 8) {
|
|
|
|
throw new Error("Invalid layer: '" + text + "' -> must be 8 characters");
|
|
|
|
}
|
|
|
|
|
|
|
|
const quads = [null, null, null, null];
|
|
|
|
for (let quad = 0; quad < 4; ++quad) {
|
|
|
|
const shapeText = text[quad * 2 + 0];
|
|
|
|
const subShape = enumShortcodeToSubShape[shapeText];
|
|
|
|
const color = enumShortcodeToColor[text[quad * 2 + 1]];
|
|
|
|
if (subShape) {
|
|
|
|
if (!color) {
|
|
|
|
throw new Error("Invalid shape color key: " + key);
|
|
|
|
}
|
|
|
|
quads[quad] = {
|
|
|
|
subShape,
|
|
|
|
color,
|
|
|
|
};
|
|
|
|
} else if (shapeText !== "-") {
|
|
|
|
throw new Error("Invalid shape key: " + shapeText);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
layers.push(quads);
|
|
|
|
}
|
|
|
|
|
|
|
|
return layers;
|
|
|
|
}
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
function renderShape(layers) {
|
|
|
|
const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById("result"));
|
|
|
|
const context = canvas.getContext("2d");
|
|
|
|
|
|
|
|
context.save();
|
|
|
|
context.fillStyle = "#fff";
|
|
|
|
context.fillRect(0, 0, 1000, 1000);
|
|
|
|
|
|
|
|
const w = 256;
|
|
|
|
const h = 256;
|
|
|
|
const dpi = 1;
|
|
|
|
|
|
|
|
context.translate((w * dpi) / 2, (h * dpi) / 2);
|
|
|
|
context.scale((dpi * w) / 23, (dpi * h) / 23);
|
|
|
|
|
|
|
|
context.fillStyle = "#e9ecf7";
|
|
|
|
|
|
|
|
const quadrantSize = 10;
|
|
|
|
const quadrantHalfSize = quadrantSize / 2;
|
|
|
|
|
|
|
|
context.fillStyle = "rgba(40, 50, 65, 0.1)";
|
|
|
|
context.beginCircle(0, 0, quadrantSize * 1.15);
|
|
|
|
context.fill();
|
|
|
|
|
|
|
|
for (let layerIndex = 0; layerIndex < layers.length; ++layerIndex) {
|
|
|
|
const quadrants = layers[layerIndex];
|
|
|
|
|
|
|
|
const layerScale = Math.max(0.1, 0.9 - layerIndex * 0.22);
|
|
|
|
|
|
|
|
for (let quadrantIndex = 0; quadrantIndex < 4; ++quadrantIndex) {
|
|
|
|
if (!quadrants[quadrantIndex]) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const { subShape, color } = quadrants[quadrantIndex];
|
|
|
|
|
|
|
|
const quadrantPos = arrayQuadrantIndexToOffset[quadrantIndex];
|
|
|
|
const centerQuadrantX = quadrantPos.x * quadrantHalfSize;
|
|
|
|
const centerQuadrantY = quadrantPos.y * quadrantHalfSize;
|
|
|
|
|
|
|
|
const rotation = radians(quadrantIndex * 90);
|
|
|
|
|
|
|
|
context.translate(centerQuadrantX, centerQuadrantY);
|
|
|
|
context.rotate(rotation);
|
|
|
|
|
|
|
|
context.fillStyle = enumColorsToHexCode[color];
|
|
|
|
context.strokeStyle = "#555";
|
|
|
|
context.lineWidth = 1;
|
|
|
|
|
|
|
|
const insetPadding = 0.0;
|
|
|
|
|
|
|
|
switch (subShape) {
|
|
|
|
case enumSubShape.rect: {
|
|
|
|
context.beginPath();
|
|
|
|
const dims = quadrantSize * layerScale;
|
|
|
|
context.rect(
|
|
|
|
insetPadding + -quadrantHalfSize,
|
|
|
|
-insetPadding + quadrantHalfSize - dims,
|
|
|
|
dims,
|
|
|
|
dims
|
|
|
|
);
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case enumSubShape.star: {
|
|
|
|
context.beginPath();
|
|
|
|
const dims = quadrantSize * layerScale;
|
|
|
|
|
|
|
|
let originX = insetPadding - quadrantHalfSize;
|
|
|
|
let originY = -insetPadding + quadrantHalfSize - dims;
|
|
|
|
|
|
|
|
const moveInwards = dims * 0.4;
|
|
|
|
context.moveTo(originX, originY + moveInwards);
|
|
|
|
context.lineTo(originX + dims, originY);
|
|
|
|
context.lineTo(originX + dims - moveInwards, originY + dims);
|
|
|
|
context.lineTo(originX, originY + dims);
|
|
|
|
context.closePath();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case enumSubShape.windmill: {
|
|
|
|
context.beginPath();
|
|
|
|
const dims = quadrantSize * layerScale;
|
|
|
|
|
|
|
|
let originX = insetPadding - quadrantHalfSize;
|
|
|
|
let originY = -insetPadding + quadrantHalfSize - dims;
|
|
|
|
const moveInwards = dims * 0.4;
|
|
|
|
context.moveTo(originX, originY + moveInwards);
|
|
|
|
context.lineTo(originX + dims, originY);
|
|
|
|
context.lineTo(originX + dims, originY + dims);
|
|
|
|
context.lineTo(originX, originY + dims);
|
|
|
|
context.closePath();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case enumSubShape.circle: {
|
|
|
|
context.beginPath();
|
|
|
|
context.moveTo(insetPadding + -quadrantHalfSize, -insetPadding + quadrantHalfSize);
|
|
|
|
context.arc(
|
|
|
|
insetPadding + -quadrantHalfSize,
|
|
|
|
-insetPadding + quadrantHalfSize,
|
|
|
|
quadrantSize * layerScale,
|
|
|
|
-Math.PI * 0.5,
|
|
|
|
0
|
|
|
|
);
|
|
|
|
context.closePath();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
default: {
|
|
|
|
assertAlways(false, "Unkown sub shape: " + subShape);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context.fill();
|
|
|
|
context.stroke();
|
|
|
|
|
|
|
|
context.rotate(-rotation);
|
|
|
|
context.translate(-centerQuadrantX, -centerQuadrantY);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
context.restore();
|
|
|
|
}
|
|
|
|
|
2020-05-09 14:45:23 +00:00
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
function showError(msg) {
|
|
|
|
const errorDiv = document.getElementById("error");
|
|
|
|
errorDiv.classList.toggle("hasError", !!msg);
|
|
|
|
if (msg) {
|
|
|
|
errorDiv.innerText = msg;
|
|
|
|
} else {
|
|
|
|
errorDiv.innerText = "Shape generated";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
// @ts-ignore
|
2020-05-09 14:45:23 +00:00
|
|
|
window.generate = () => {
|
|
|
|
showError(null);
|
2020-05-09 17:14:11 +00:00
|
|
|
// @ts-ignore
|
2020-05-09 14:45:23 +00:00
|
|
|
const code = document.getElementById("code").value.trim();
|
|
|
|
|
|
|
|
let parsed = null;
|
|
|
|
try {
|
|
|
|
parsed = fromShortKey(code);
|
|
|
|
} catch (ex) {
|
|
|
|
showError(ex);
|
2020-05-09 17:14:11 +00:00
|
|
|
return;
|
2020-05-09 14:45:23 +00:00
|
|
|
}
|
2020-05-09 17:14:11 +00:00
|
|
|
|
|
|
|
renderShape(parsed);
|
2020-05-09 14:45:23 +00:00
|
|
|
};
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
// @ts-ignore
|
2020-05-09 14:45:23 +00:00
|
|
|
window.debounce = fn => {
|
|
|
|
setTimeout(fn, 0);
|
|
|
|
};
|
|
|
|
|
2020-05-09 17:14:11 +00:00
|
|
|
// @ts-ignore
|
|
|
|
window.addEventListener("load", window.generate);
|