big commit
This commit is contained in:
parent
aff885cf17
commit
b506f5a219
22 changed files with 692 additions and 556 deletions
|
@ -12,10 +12,8 @@ function getLiterateProgram(name) {
|
|||
outputCount: 0,
|
||||
|
||||
nextOutputIndex() {
|
||||
let index = this.outputCount;
|
||||
++this.outputCount;
|
||||
return index;
|
||||
}
|
||||
return this.outputCount++;
|
||||
},
|
||||
});
|
||||
}
|
||||
return literatePrograms.get(name);
|
||||
|
@ -28,7 +26,7 @@ function getLiterateProgramWorkerCommands(name) {
|
|||
if (frame.mode == "input") {
|
||||
commands.push({ kind: "module", source: frame.textContent });
|
||||
} else if (frame.mode == "output") {
|
||||
commands.push({ kind: "output", expected: frame.textContent });
|
||||
commands.push({ kind: "output" });
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
|
@ -51,7 +49,7 @@ class InputMode {
|
|||
{ regex: /"(\\"|[^"])*"/, as: "string" },
|
||||
{ regex: /`(\\`|[^"])*`/, as: "string" },
|
||||
// TODO: RegExp literals?
|
||||
{ regex: /[+=/*^%<>!~|&\.-]+/, as: "operator" },
|
||||
{ regex: /[+=/*^%<>!~|&\.?:-]+/, as: "operator" },
|
||||
{ regex: /[,;]/, as: "punct" },
|
||||
],
|
||||
keywords: new Map([
|
||||
|
@ -132,54 +130,84 @@ class InputMode {
|
|||
}
|
||||
}
|
||||
|
||||
function messageOutputArrayToString(output) {
|
||||
return output
|
||||
.map(x => {
|
||||
if (typeof x === "object") return JSON.stringify(x);
|
||||
else return x + "";
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
class OutputMode {
|
||||
constructor(frame) {
|
||||
this.clearResultsOnNextOutput = false;
|
||||
|
||||
this.frame = frame;
|
||||
|
||||
this.frame.program.onChanged.push(_ => this.evaluate());
|
||||
this.outputIndex = this.frame.program.nextOutputIndex();
|
||||
|
||||
this.evaluate();
|
||||
}
|
||||
this.console = document.createElement("pre");
|
||||
this.console.classList.add("console");
|
||||
this.frame.appendChild(this.console);
|
||||
this.clearConsoleOnNextOutput = false;
|
||||
|
||||
evaluate() {
|
||||
// This is a small bit of debouncing. If we cleared the output right away, the page would
|
||||
// jitter around irritatingly.
|
||||
this.clearResultsOnNextOutput = true;
|
||||
this.error = document.createElement("pre");
|
||||
this.error.classList.add("error");
|
||||
this.frame.appendChild(this.error);
|
||||
|
||||
if (this.worker != null) {
|
||||
this.worker.terminate();
|
||||
}
|
||||
this.worker = new Worker(import.meta.resolve("./literate-programming/worker.js"), {
|
||||
type: "module",
|
||||
name: `evaluate LiterateOutput ${this.frame.programName}`
|
||||
});
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.classList.add("hidden");
|
||||
this.iframe.src = `${TREEHOUSE_SITE}/sandbox`;
|
||||
this.frame.appendChild(this.iframe);
|
||||
|
||||
this.worker.addEventListener("message", event => {
|
||||
this.iframe.contentWindow.treehouseSandboxInternals = { outputIndex: this.outputIndex };
|
||||
|
||||
this.iframe.contentWindow.addEventListener("message", event => {
|
||||
let message = event.data;
|
||||
if (message.kind == "evalComplete") {
|
||||
this.worker.terminate();
|
||||
if (message.kind == "ready") {
|
||||
this.evaluate();
|
||||
} else if (message.kind == "resize" && message.outputIndex == this.outputIndex) {
|
||||
this.resize();
|
||||
} else if (message.kind == "output" && message.outputIndex == this.outputIndex) {
|
||||
this.addOutput(message.output);
|
||||
if (message.output.kind == "error") {
|
||||
this.error.textContent = messageOutputArrayToString(message.output.message);
|
||||
this.iframe.classList.add("hidden");
|
||||
} else {
|
||||
this.addOutput(message.output);
|
||||
}
|
||||
} else if (message.kind == "evalComplete") {
|
||||
this.error.textContent = "";
|
||||
this.flushConsoleClear();
|
||||
}
|
||||
});
|
||||
|
||||
this.worker.postMessage({
|
||||
this.frame.program.onChanged.push(_ => this.evaluate());
|
||||
}
|
||||
|
||||
evaluate() {
|
||||
this.requestConsoleClear();
|
||||
this.iframe.contentWindow.postMessage({
|
||||
action: "eval",
|
||||
input: getLiterateProgramWorkerCommands(this.frame.programName),
|
||||
});
|
||||
}
|
||||
|
||||
addOutput(output) {
|
||||
if (this.clearResultsOnNextOutput) {
|
||||
this.clearResultsOnNextOutput = false;
|
||||
this.clearResults();
|
||||
}
|
||||
clearConsole() {
|
||||
this.console.replaceChildren();
|
||||
}
|
||||
|
||||
// Don't show anything if the function didn't return a value.
|
||||
if (output.kind == "result" && output.message[0] === undefined) return;
|
||||
requestConsoleClear() {
|
||||
this.clearConsoleOnNextOutput = true;
|
||||
}
|
||||
|
||||
flushConsoleClear() {
|
||||
if (this.clearConsoleOnNextOutput) {
|
||||
this.clearConsole();
|
||||
this.clearConsoleOnNextOutput = false;
|
||||
}
|
||||
}
|
||||
|
||||
addOutput(output) {
|
||||
this.flushConsoleClear();
|
||||
|
||||
let line = document.createElement("code");
|
||||
|
||||
|
@ -194,65 +222,22 @@ class OutputMode {
|
|||
})
|
||||
.join(" ");
|
||||
|
||||
this.frame.appendChild(line);
|
||||
this.console.appendChild(line);
|
||||
}
|
||||
|
||||
clearResults() {
|
||||
this.frame.replaceChildren();
|
||||
}
|
||||
|
||||
static messageOutputArrayToString(output) {
|
||||
return output
|
||||
.map(x => {
|
||||
if (typeof x === "object") return JSON.stringify(x);
|
||||
else return x + "";
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
}
|
||||
|
||||
class GraphicsMode {
|
||||
constructor(frame) {
|
||||
this.frame = frame;
|
||||
|
||||
this.error = document.createElement("pre");
|
||||
this.error.classList.add("error");
|
||||
this.frame.appendChild(this.error);
|
||||
|
||||
this.iframe = document.createElement("iframe");
|
||||
this.iframe.classList.add("hidden");
|
||||
this.iframe.src = import.meta.resolve("../../html/sandbox.html");
|
||||
this.frame.appendChild(this.iframe);
|
||||
|
||||
this.iframe.contentWindow.addEventListener("message", event => {
|
||||
let message = event.data;
|
||||
if (message.kind == "ready") {
|
||||
this.evaluate();
|
||||
}
|
||||
else if (message.kind == "resize") {
|
||||
this.resize(message);
|
||||
} else if (message.kind == "output" && message.output.kind == "error") {
|
||||
this.error.textContent = OutputMode.messageOutputArrayToString(message.output.message);
|
||||
this.iframe.classList.add("hidden");
|
||||
} else if (message.kind == "evalComplete") {
|
||||
this.error.textContent = "";
|
||||
}
|
||||
});
|
||||
|
||||
this.frame.program.onChanged.push(_ => this.evaluate());
|
||||
}
|
||||
|
||||
evaluate() {
|
||||
this.iframe.contentWindow.postMessage({
|
||||
action: "eval",
|
||||
input: getLiterateProgramWorkerCommands(this.frame.programName),
|
||||
});
|
||||
}
|
||||
|
||||
resize(message) {
|
||||
this.iframe.width = message.width;
|
||||
this.iframe.height = message.height;
|
||||
resize() {
|
||||
// iframe cannot be `display: none` to get its scrollWidth/scrollHeight.
|
||||
this.iframe.classList.remove("hidden");
|
||||
|
||||
let width = this.iframe.contentDocument.body.scrollWidth;
|
||||
let height = this.iframe.contentDocument.body.scrollHeight;
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
this.iframe.classList.add("hidden");
|
||||
} else {
|
||||
this.iframe.width = width;
|
||||
this.iframe.height = height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,8 +251,6 @@ class LiterateProgram extends HTMLElement {
|
|||
this.modeImpl = new InputMode(this);
|
||||
} else if (this.mode == "output") {
|
||||
this.modeImpl = new OutputMode(this);
|
||||
} else if (this.mode == "graphics") {
|
||||
this.modeImpl = new GraphicsMode(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,21 @@
|
|||
let outputIndex = 0;
|
||||
|
||||
export function getOutputIndex() {
|
||||
return outputIndex;
|
||||
}
|
||||
export const jsConsole = console;
|
||||
|
||||
// Overwrite globalThis.console with domConsole to redirect output to the DOM console.
|
||||
// To always output to the JavaScript console regardless, use jsConsole.
|
||||
export const domConsole = {
|
||||
log(...message) {
|
||||
postMessage({
|
||||
kind: "output",
|
||||
output: {
|
||||
kind: "console.log",
|
||||
message: [...message],
|
||||
},
|
||||
outputIndex,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
async function withTemporaryGlobalScope(callback) {
|
||||
let state = {
|
||||
|
@ -13,6 +26,7 @@ async function withTemporaryGlobalScope(callback) {
|
|||
}
|
||||
};
|
||||
await callback(state);
|
||||
jsConsole.trace(state.oldValues, "bringing back old state");
|
||||
for (let key in state.oldValues) {
|
||||
globalThis[key] = state.oldValues[key];
|
||||
}
|
||||
|
@ -20,15 +34,11 @@ async function withTemporaryGlobalScope(callback) {
|
|||
|
||||
let evaluationComplete = null;
|
||||
|
||||
export async function evaluate(commands, { start, success, error }) {
|
||||
export async function evaluate(commands, { error, newOutput }) {
|
||||
if (evaluationComplete != null) {
|
||||
await evaluationComplete;
|
||||
}
|
||||
|
||||
if (start != null) {
|
||||
start();
|
||||
}
|
||||
|
||||
let signalEvaluationComplete;
|
||||
evaluationComplete = new Promise((resolve, _reject) => {
|
||||
signalEvaluationComplete = resolve;
|
||||
|
@ -36,21 +46,19 @@ export async function evaluate(commands, { start, success, error }) {
|
|||
|
||||
outputIndex = 0;
|
||||
try {
|
||||
await withTemporaryGlobalScope(async scope => {
|
||||
for (let command of commands) {
|
||||
if (command.kind == "module") {
|
||||
let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
|
||||
let module = await import(blobUrl);
|
||||
for (let exportedKey in module) {
|
||||
scope.set(exportedKey, module[exportedKey]);
|
||||
}
|
||||
} else if (command.kind == "output") {
|
||||
++outputIndex;
|
||||
for (let command of commands) {
|
||||
if (command.kind == "module") {
|
||||
let blobUrl = URL.createObjectURL(new Blob([command.source], { type: "text/javascript" }));
|
||||
let module = await import(blobUrl);
|
||||
for (let exportedKey in module) {
|
||||
globalThis[exportedKey] = module[exportedKey];
|
||||
}
|
||||
} else if (command.kind == "output") {
|
||||
if (newOutput != null) {
|
||||
newOutput(outputIndex);
|
||||
}
|
||||
++outputIndex;
|
||||
}
|
||||
});
|
||||
if (success != null) {
|
||||
success();
|
||||
}
|
||||
postMessage({
|
||||
kind: "evalComplete",
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import { evaluate, getOutputIndex } from "./eval.js";
|
||||
|
||||
let debugLog = console.log;
|
||||
|
||||
globalThis.console = {
|
||||
log(...message) {
|
||||
postMessage({
|
||||
kind: "output",
|
||||
output: {
|
||||
kind: "log",
|
||||
message: [...message],
|
||||
},
|
||||
outputIndex: getOutputIndex(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
addEventListener("message", async event => {
|
||||
let message = event.data;
|
||||
if (message.action == "eval") {
|
||||
evaluate(message.input, {});
|
||||
}
|
||||
});
|
0
static/js/components/tairu/cardinal-directions.js
Normal file
0
static/js/components/tairu/cardinal-directions.js
Normal file
134
static/js/components/tairu/editor.js
Normal file
134
static/js/components/tairu/editor.js
Normal file
|
@ -0,0 +1,134 @@
|
|||
import { Sketch } from "treehouse/sandbox.js";
|
||||
|
||||
export class TileEditor extends Sketch {
|
||||
constructor({ tilemap, tileSize }) {
|
||||
super(tilemap.width * tileSize, tilemap.height * tileSize);
|
||||
|
||||
this.colorScheme = {
|
||||
background: "#F7F7F7",
|
||||
grid: "#00000011",
|
||||
tileCursor: "#222222",
|
||||
tiles: [
|
||||
"transparent", // never actually drawn to the screen with the default renderer!
|
||||
"#eb134a",
|
||||
],
|
||||
};
|
||||
|
||||
this.tilemap = tilemap;
|
||||
this.tileSize = tileSize;
|
||||
|
||||
this.hasFocus = false;
|
||||
this.paintingTile = null;
|
||||
this.tileCursor = { x: 0, y: 0 };
|
||||
|
||||
this.canvas.addEventListener("mousemove", event => this.mouseMoved(event));
|
||||
this.canvas.addEventListener("mousedown", event => this.mousePressed(event));
|
||||
this.canvas.addEventListener("mouseup", event => this.mouseReleased(event));
|
||||
|
||||
this.canvas.addEventListener("mouseenter", _ => this.mouseEnter());
|
||||
this.canvas.addEventListener("mouseleave", _ => this.mouseLeave());
|
||||
|
||||
this.canvas.addEventListener("contextmenu", event => event.preventDefault());
|
||||
|
||||
// Only draw first frame after the constructor already runs.
|
||||
// That way we can modify the color scheme however much we want without causing additional
|
||||
// redraws.
|
||||
requestAnimationFrame(() => this.draw());
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.drawBackground();
|
||||
this.drawTilemap();
|
||||
this.drawGrid();
|
||||
if (this.hasFocus) {
|
||||
this.drawTileCursor();
|
||||
}
|
||||
}
|
||||
|
||||
drawBackground() {
|
||||
this.ctx.fillStyle = this.colorScheme.background;
|
||||
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
drawTilemap() {
|
||||
for (let y = 0; y < this.tilemap.height; ++y) {
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
let tile = this.tilemap.at(x, y);
|
||||
if (tile != 0) {
|
||||
this.ctx.fillStyle = this.colorScheme.tiles[tile];
|
||||
this.ctx.fillRect(x * this.tileSize, y * this.tileSize, this.tileSize, this.tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawGrid() {
|
||||
this.ctx.beginPath();
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
this.ctx.moveTo(x * this.tileSize, 0);
|
||||
this.ctx.lineTo(x * this.tileSize, this.canvas.height);
|
||||
}
|
||||
for (let y = 0; y < this.tilemap.width; ++y) {
|
||||
this.ctx.moveTo(0, y * this.tileSize);
|
||||
this.ctx.lineTo(this.canvas.width, y * this.tileSize);
|
||||
}
|
||||
this.ctx.strokeStyle = this.colorScheme.grid;
|
||||
this.ctx.lineWidth = 1;
|
||||
this.ctx.stroke();
|
||||
}
|
||||
|
||||
drawTileCursor() {
|
||||
this.ctx.strokeStyle = this.colorScheme.tileCursor;
|
||||
this.ctx.lineWidth = 5;
|
||||
this.ctx.strokeRect(this.tileCursor.x * this.tileSize, this.tileCursor.y * this.tileSize, this.tileSize, this.tileSize);
|
||||
}
|
||||
|
||||
|
||||
mouseMoved(event) {
|
||||
this.tileCursor.x = Math.floor(event.offsetX / this.tileSize);
|
||||
this.tileCursor.y = Math.floor(event.offsetY / this.tileSize);
|
||||
this.paintTileUnderCursor();
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
mousePressed(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (event.button == 0) {
|
||||
this.paintingTile = 1;
|
||||
} else if (event.button == 2) {
|
||||
this.paintingTile = 0;
|
||||
}
|
||||
|
||||
this.paintTileUnderCursor();
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
mouseReleased(_event) {
|
||||
this.stopPainting();
|
||||
this.draw();
|
||||
}
|
||||
|
||||
mouseEnter() {
|
||||
this.hasFocus = true;
|
||||
this.draw();
|
||||
}
|
||||
|
||||
mouseLeave() {
|
||||
this.hasFocus = false;
|
||||
this.stopPainting();
|
||||
this.draw();
|
||||
}
|
||||
|
||||
paintTileUnderCursor() {
|
||||
if (this.paintingTile != null) {
|
||||
this.tilemap.setAt(this.tileCursor.x, this.tileCursor.y, this.paintingTile);
|
||||
}
|
||||
}
|
||||
|
||||
stopPainting() {
|
||||
this.paintingTile = null;
|
||||
}
|
||||
}
|
87
static/js/components/tairu/framework.js
Normal file
87
static/js/components/tairu/framework.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
// A frameworking class assigning some CSS classes to the canvas to make it integrate nicer with CSS.
|
||||
export class Frame extends HTMLCanvasElement {
|
||||
static fontFace = "RecVar";
|
||||
static monoFontFace = "RecVarMono";
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
this.style.cssText = `
|
||||
margin-top: 8px;
|
||||
margin-bottom: 4px;
|
||||
border-radius: 4px;
|
||||
max-width: 100%;
|
||||
`;
|
||||
|
||||
this.ctx = this.getContext("2d");
|
||||
|
||||
requestAnimationFrame(this.#drawLoop.bind(this));
|
||||
}
|
||||
|
||||
#drawLoop() {
|
||||
this.ctx.font = "14px RecVar";
|
||||
this.draw();
|
||||
requestAnimationFrame(this.#drawLoop.bind(this));
|
||||
}
|
||||
|
||||
// Override this!
|
||||
draw() {
|
||||
throw new ReferenceError("draw() must be overridden");
|
||||
}
|
||||
|
||||
getTextPositionInBox(text, x, y, width, height, hAlign, vAlign) {
|
||||
let measurements = this.ctx.measureText(text);
|
||||
|
||||
let leftX;
|
||||
switch (hAlign) {
|
||||
case "left":
|
||||
leftX = x;
|
||||
break;
|
||||
case "center":
|
||||
leftX = x + width / 2 - measurements.width / 2;
|
||||
break;
|
||||
case "right":
|
||||
leftX = x + width - measurements.width;
|
||||
break;
|
||||
}
|
||||
|
||||
let textHeight = measurements.fontBoundingBoxAscent;
|
||||
let baselineY;
|
||||
switch (vAlign) {
|
||||
case "top":
|
||||
baselineY = y + textHeight;
|
||||
break;
|
||||
case "center":
|
||||
baselineY = y + height / 2 + textHeight / 2;
|
||||
break;
|
||||
case "bottom":
|
||||
baselineY = y + height;
|
||||
break;
|
||||
}
|
||||
|
||||
return { leftX, baselineY };
|
||||
}
|
||||
|
||||
get scaleInViewportX() {
|
||||
return this.clientWidth / this.width;
|
||||
}
|
||||
|
||||
get scaleInViewportY() {
|
||||
return this.clientHeight / this.height;
|
||||
}
|
||||
|
||||
getMousePositionFromEvent(event) {
|
||||
return {
|
||||
x: event.offsetX / this.scaleInViewportX,
|
||||
y: event.offsetY / this.scaleInViewportY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function defineFrame(elementName, claß) { // because `class` is a keyword.
|
||||
customElements.define(elementName, claß, { extends: "canvas" });
|
||||
}
|
||||
|
||||
defineFrame("tairu--frame", Frame);
|
76
static/js/components/tairu/proto.js
Normal file
76
static/js/components/tairu/proto.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { TileEditor } from 'tairu/editor.js';
|
||||
|
||||
export function alignTextInRectangle(ctx, text, x, y, width, height, hAlign, vAlign) {
|
||||
let measurements = ctx.measureText(text);
|
||||
|
||||
let leftX;
|
||||
switch (hAlign) {
|
||||
case "left":
|
||||
leftX = x;
|
||||
break;
|
||||
case "center":
|
||||
leftX = x + width / 2 - measurements.width / 2;
|
||||
break;
|
||||
case "right":
|
||||
leftX = x + width - measurements.width;
|
||||
break;
|
||||
}
|
||||
|
||||
let textHeight = measurements.fontBoundingBoxAscent;
|
||||
let baselineY;
|
||||
switch (vAlign) {
|
||||
case "top":
|
||||
baselineY = y + textHeight;
|
||||
break;
|
||||
case "center":
|
||||
baselineY = y + height / 2 + textHeight / 2;
|
||||
break;
|
||||
case "bottom":
|
||||
baselineY = y + height;
|
||||
break;
|
||||
}
|
||||
|
||||
return { leftX, baselineY };
|
||||
}
|
||||
|
||||
export function shouldConnect(a, b) {
|
||||
return a == b;
|
||||
}
|
||||
|
||||
export class TileEditorWithCardinalDirections extends TileEditor {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.colorScheme.tiles[1] = "#f96565";
|
||||
}
|
||||
|
||||
drawConnectionText(text, enabled, tileX, tileY, hAlign, vAlign) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.fillStyle = enabled ? "#6c023e" : "#d84161";
|
||||
this.ctx.font = `800 14px ${Frame.monoFontFace}`;
|
||||
const padding = 2;
|
||||
let topLeftX = tileX * this.tileSize + padding;
|
||||
let topLeftY = tileY * this.tileSize + padding;
|
||||
let rectSize = this.tileSize - padding * 2;
|
||||
let { leftX, baselineY } = this.getTextPositionInBox(text, topLeftX, topLeftY, rectSize, rectSize, hAlign, vAlign);
|
||||
this.ctx.fillText(text, leftX, baselineY);
|
||||
}
|
||||
|
||||
drawTiles() {
|
||||
super.drawTiles();
|
||||
for (let y = 0; y < this.tilemap.height; ++y) {
|
||||
for (let x = 0; x < this.tilemap.width; ++x) {
|
||||
let tile = this.tilemap.at(x, y);
|
||||
if (canConnect(tile)) {
|
||||
let connectedWithEast = shouldConnect(tile, this.tilemap.at(x + 1, y));
|
||||
let connectedWithSouth = shouldConnect(tile, this.tilemap.at(x, y + 1));
|
||||
let connectedWithNorth = shouldConnect(tile, this.tilemap.at(x, y - 1));
|
||||
let connectedWithWest = shouldConnect(tile, this.tilemap.at(x - 1, y));
|
||||
this.drawConnectionText("E", connectedWithEast, x, y, "right", "center");
|
||||
this.drawConnectionText("S", connectedWithSouth, x, y, "center", "bottom");
|
||||
this.drawConnectionText("N", connectedWithNorth, x, y, "center", "top");
|
||||
this.drawConnectionText("W", connectedWithWest, x, y, "left", "center");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
47
static/js/components/tairu/tilemap-registry.js
Normal file
47
static/js/components/tairu/tilemap-registry.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { Tilemap } from './tilemap.js';
|
||||
|
||||
const alphabet = " x";
|
||||
|
||||
function parseTilemap(lineArray) {
|
||||
let tilemap = new Tilemap(lineArray[0].length, lineArray.length);
|
||||
for (let y in lineArray) {
|
||||
let line = lineArray[y];
|
||||
for (let x = 0; x < line.length; ++x) {
|
||||
let char = line.charAt(x);
|
||||
tilemap.setAt(x, y, alphabet.indexOf(char));
|
||||
}
|
||||
}
|
||||
return tilemap;
|
||||
}
|
||||
|
||||
export default {
|
||||
bitwiseAutotiling: parseTilemap([
|
||||
" ",
|
||||
" xxx ",
|
||||
" xxx ",
|
||||
" xxx ",
|
||||
" ",
|
||||
]),
|
||||
bitwiseAutotilingChapter2: parseTilemap([
|
||||
" ",
|
||||
" x ",
|
||||
" x ",
|
||||
" xxx ",
|
||||
" ",
|
||||
]),
|
||||
bitwiseAutotilingCorners: parseTilemap([
|
||||
" ",
|
||||
" x x ",
|
||||
" x ",
|
||||
" x x ",
|
||||
" ",
|
||||
]),
|
||||
bitwiseAutotiling47: parseTilemap([
|
||||
" x ",
|
||||
" x ",
|
||||
" xx xx ",
|
||||
" xxxx ",
|
||||
" x ",
|
||||
]),
|
||||
};
|
||||
|
42
static/js/components/tairu/tilemap.js
Normal file
42
static/js/components/tairu/tilemap.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
export class Tilemap {
|
||||
constructor(width, height) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.tiles = new Uint8Array(width * height);
|
||||
this.default = 0;
|
||||
}
|
||||
|
||||
tileIndex(x, y) {
|
||||
return x + y * this.width;
|
||||
}
|
||||
|
||||
inBounds(x, y) {
|
||||
return x >= 0 && y >= 0 && x < this.width && y < this.height;
|
||||
}
|
||||
|
||||
at(x, y) {
|
||||
if (this.inBounds(x, y)) {
|
||||
return this.tiles[this.tileIndex(x, y)];
|
||||
} else {
|
||||
return this.default;
|
||||
}
|
||||
}
|
||||
|
||||
setAt(x, y, tile) {
|
||||
if (this.inBounds(x, y)) {
|
||||
this.tiles[this.tileIndex(x, y)] = tile;
|
||||
}
|
||||
}
|
||||
|
||||
static parse(alphabet, lineArray) {
|
||||
let tilemap = new Tilemap(lineArray[0].length, lineArray.length);
|
||||
for (let y in lineArray) {
|
||||
let line = lineArray[y];
|
||||
for (let x = 0; x < line.length; ++x) {
|
||||
let char = line.charAt(x);
|
||||
tilemap.setAt(x, y, alphabet.indexOf(char));
|
||||
}
|
||||
}
|
||||
return tilemap;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue