Skip to content

Commit

Permalink
feat: show canvas (#3)
Browse files Browse the repository at this point in the history
* feat: show canvas

* feat: improve typecheck
  • Loading branch information
rei1024 authored Oct 16, 2024
1 parent a50437f commit 290479e
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 99 deletions.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ <h1>Oscilloscope</h1>
></textarea>
<button id="analyze">Analyze</button>
<div id="message"></div>
<canvas id="canvas"></canvas>
<div>
<table id="output-table" style="display: none">
<tr>
Expand Down
125 changes: 42 additions & 83 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,93 +1,52 @@
import { BitGrid } from "@ca-ts/algo/bit";
import type { WorkerRequestMessage, WorkerResponseMessage } from "./worker";
import MyWorker from "./worker?worker";

const worker = new MyWorker();

import type { AnalyzeResult } from "./lib/analyzeOscillator";
const cellSize = 10;
export class App {
constructor() {}
}

function post(req: WorkerRequestMessage) {
worker.postMessage(req);
}
histories: BitGrid[] | null = null;
private ctx: CanvasRenderingContext2D;
private gen = 0;
constructor(private $canvas: HTMLCanvasElement) {
const ctx = this.$canvas.getContext("2d");
if (ctx == null) {
throw Error("Context");
}
this.ctx = ctx;

const update = () => {
this.render();
requestAnimationFrame(update);
};

update();
}

const $message = document.querySelector("#message") as HTMLElement;
const $outputTable = document.querySelector("#output-table") as HTMLElement;
worker.addEventListener("message", (e) => {
const message = e.data as WorkerResponseMessage;
$message.textContent = "";
$outputTable.style.display = "none";
render() {
if (this.histories == null) {
return;
}

const ctx = this.ctx;
ctx.reset && ctx.reset();
ctx.beginPath();
this.histories[this.gen].forEachAlive((x, y) => {
ctx.rect(x * cellSize, y * cellSize, cellSize, cellSize);
});
ctx.fill();
this.gen = (this.gen + 1) % this.histories.length;
}

$analyzeButton.disabled = false;
if (message.kind === "response-error") {
$message.textContent = "Error: " + message.message;
} else {
$outputTable.style.display = "block";
const data = message.data;
setup(data: AnalyzeResult) {
const $canvas = this.$canvas;
const bitGridData = data.bitGridData;
$canvas.width = (bitGridData.width ?? 0) * cellSize;
$canvas.height = (bitGridData.height ?? 0) * cellSize;
$canvas.style.width = "100%";
const width32 = Math.ceil((bitGridData.width ?? 0) / 32);
const height = bitGridData.height ?? 0;
const hisotories = bitGridData.histories.map(

this.histories = bitGridData.histories.map(
(h) => new BitGrid(width32, height, h)
);

const $outputPeriod = document.querySelector(
"#output-period"
) as HTMLElement;
$outputPeriod.textContent = data.period.toString();
const $outputCellsMin = document.querySelector(
"#output-cells-min"
) as HTMLElement;
$outputCellsMin.textContent = data.population.min.toString();

const $outputCellsMax = document.querySelector(
"#output-cells-max"
) as HTMLElement;
$outputCellsMax.textContent = data.population.max.toString();

const $outputCellsAvg = document.querySelector(
"#output-cells-avg"
) as HTMLElement;
$outputCellsAvg.textContent = data.population.avg.toString();

const $outputCellsMedian = document.querySelector(
"#output-cells-median"
) as HTMLElement;
$outputCellsMedian.textContent = data.population.median.toString();

const $outputWidth = document.querySelector("#output-width") as HTMLElement;
$outputWidth.textContent = data.boundingBox.sizeX.toString();

const $outputHeight = document.querySelector(
"#output-height"
) as HTMLElement;
$outputHeight.textContent = data.boundingBox.sizeY.toString();

const $outputArea = document.querySelector("#output-area") as HTMLElement;
$outputArea.textContent = (
data.boundingBox.sizeX * data.boundingBox.sizeY
).toString();

const $outputStator = document.querySelector(
"#output-stator"
) as HTMLElement;
$outputStator.textContent = data.stator.toString();

const $outputRotor = document.querySelector("#output-rotor") as HTMLElement;
$outputRotor.textContent = data.rotor.toString();

const $outputVolatility = document.querySelector(
"#output-volatility"
) as HTMLElement;
$outputVolatility.textContent = data.volatility;
this.gen = 0;
}
});

const $input = document.querySelector("#input") as HTMLTextAreaElement;
const $analyzeButton = document.querySelector("#analyze") as HTMLButtonElement;

$analyzeButton.addEventListener("click", () => {
$analyzeButton.disabled = true;
post({ kind: "request-analyze", rle: $input.value });
});
}
52 changes: 52 additions & 0 deletions src/bind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const $message = document.querySelector("#message") as HTMLElement;
export const $outputTable = document.querySelector(
"#output-table"
) as HTMLElement;

export const $outputPeriod = document.querySelector(
"#output-period"
) as HTMLElement;
export const $outputCellsMin = document.querySelector(
"#output-cells-min"
) as HTMLElement;
export const $outputCellsMax = document.querySelector(
"#output-cells-max"
) as HTMLElement;
export const $outputCellsAvg = document.querySelector(
"#output-cells-avg"
) as HTMLElement;

export const $outputCellsMedian = document.querySelector(
"#output-cells-median"
) as HTMLElement;

export const $outputWidth = document.querySelector(
"#output-width"
) as HTMLElement;

export const $outputHeight = document.querySelector(
"#output-height"
) as HTMLElement;

export const $outputArea = document.querySelector(
"#output-area"
) as HTMLElement;

export const $outputStator = document.querySelector(
"#output-stator"
) as HTMLElement;

export const $outputRotor = document.querySelector(
"#output-rotor"
) as HTMLElement;

export const $outputVolatility = document.querySelector(
"#output-volatility"
) as HTMLElement;

export const $input = document.querySelector("#input") as HTMLTextAreaElement;
export const $analyzeButton = document.querySelector(
"#analyze"
) as HTMLButtonElement;

export const $canvas = document.querySelector("#canvas") as HTMLCanvasElement;
36 changes: 36 additions & 0 deletions src/lib/findPeriod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Find repeating pattern minimum periid.
*
* ```txt
* 000000 -> 1
* 010101 -> 2
* 110110 -> 3
* 111110 -> 6
* ```
*
* KMP
*/
export function findPeriod(binary: (0 | 1)[]): number {
const n = binary.length;
const lps = new Array(n).fill(0); // Longest Prefix Suffix table

// Build the LPS array
let length = 0;
for (let i = 1; i < n; i++) {
while (length > 0 && binary[i] !== binary[length]) {
length = lps[length - 1];
}
if (binary[i] === binary[length]) {
length++;
}
lps[i] = length;
}

const smallestPeriod = n - lps[n - 1];

// If the string is composed of a repeating pattern, the smallest period divides the total length perfectly
if (n % smallestPeriod === 0) {
return smallestPeriod;
}
return n; // Otherwise, the period is the entire string
}
45 changes: 44 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,47 @@
import "./style.css";
import { BitGrid } from "@ca-ts/algo/bit";
import type { WorkerRequestMessage, WorkerResponseMessage } from "./worker";
import MyWorker from "./worker?worker";
import {
$analyzeButton,
$canvas,
$input,
$message,
$outputTable,
} from "./bind";
import { setTable } from "./ui/table";

import { App } from "./app";

const app = new App();
const worker = new MyWorker();

const app = new App($canvas);

function post(req: WorkerRequestMessage) {
worker.postMessage(req);
}

worker.addEventListener("message", (e) => {
const message = e.data as WorkerResponseMessage;
$message.textContent = "";
$outputTable.style.display = "none";

$analyzeButton.disabled = false;
if (message.kind === "response-error") {
$message.textContent = "Error: " + message.message;
} else {
$outputTable.style.display = "block";
const data = message.data;
setTable(data);
app.setup(data);
const ctx = $canvas.getContext("2d");
if (ctx == null) {
throw new Error("canvas");
}
}
});

$analyzeButton.addEventListener("click", () => {
$analyzeButton.disabled = true;
post({ kind: "request-analyze", rle: $input.value });
});
43 changes: 43 additions & 0 deletions src/ui/table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { AnalyzeResult } from "../lib/analyzeOscillator";
import {
$analyzeButton,
$canvas,
$input,
$message,
$outputArea,
$outputCellsAvg,
$outputCellsMax,
$outputCellsMedian,
$outputCellsMin,
$outputHeight,
$outputPeriod,
$outputRotor,
$outputStator,
$outputVolatility,
$outputWidth,
} from "../bind";
export function setTable(data: AnalyzeResult) {
$outputPeriod.textContent = data.period.toString();

$outputCellsMin.textContent = data.population.min.toString();

$outputCellsMax.textContent = data.population.max.toString();

$outputCellsAvg.textContent = data.population.avg.toString();

$outputCellsMedian.textContent = data.population.median.toString();

$outputWidth.textContent = data.boundingBox.sizeX.toString();

$outputHeight.textContent = data.boundingBox.sizeY.toString();

$outputArea.textContent = (
data.boundingBox.sizeX * data.boundingBox.sizeY
).toString();

$outputStator.textContent = data.stator.toString();

$outputRotor.textContent = data.rotor.toString();

$outputVolatility.textContent = data.volatility;
}
40 changes: 25 additions & 15 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { analyzeOscillator, AnalyzeResult } from "./lib/analyzeOscillator";
import { analyzeOscillator, type AnalyzeResult } from "./lib/analyzeOscillator";
import { parseRLE } from "@ca-ts/rle";
import { parseRule } from "@ca-ts/rule";

Expand All @@ -17,42 +17,52 @@ export type WorkerResponseMessage =
message: string;
};

onmessage = (e) => {
const data = e.data as WorkerRequestMessage;
function post(res: WorkerResponseMessage) {
postMessage(res);
}
function handleRequest(data: WorkerRequestMessage): WorkerResponseMessage {
let rle;
let rule;
if (data.rle.trim() === "") {
return {
kind: "response-error",
message: "RLE is empty",
};
}
try {
rle = parseRLE(data.rle);
rule = parseRule(rle.ruleString);
} catch (error) {
post({
console.error(error);
return {
kind: "response-error",
message: "Unsupported rule or rle error",
});
return;
};
}

if (rule.type !== "outer-totalistic") {
post({
return {
kind: "response-error",
message: "Unsupported rule",
});
return;
};
}
try {
const result = analyzeOscillator(
rle.cells.filter((x) => x.state === 1).map((x) => x.position),
rule.transition,
{ maxGeneration: 100_000 }
);
post({ kind: "response-analyzed", data: result });
return { kind: "response-analyzed", data: result };
} catch (error) {
post({
console.error(error);
return {
kind: "response-error",
message: "Analyzation Error",
});
};
}
}

onmessage = (e) => {
const data = e.data as WorkerRequestMessage;
function post(res: WorkerResponseMessage) {
postMessage(res);
}
post(handleRequest(data));
};
Loading

0 comments on commit 290479e

Please sign in to comment.