176 lines
4.0 KiB
TypeScript
176 lines
4.0 KiB
TypeScript
type BoardT<T = boolean> = T[][];
|
|
type InitBoardReturnT = [HTMLTableElement, BoardT];
|
|
|
|
const isAlive = <T>(val: T) => {
|
|
if (typeof val === "string") return val === "alive";
|
|
|
|
if (typeof val === "boolean") return val ? "alive" : "dead";
|
|
};
|
|
|
|
const createBoard = (root: HTMLElement, size = 16): InitBoardReturnT => {
|
|
const boardHTML = document.createElement("table");
|
|
const board: boolean[][] = [];
|
|
|
|
for (let i = 0; i < size; i++) {
|
|
const boardRowHTML = document.createElement("tr");
|
|
|
|
board.push([]);
|
|
|
|
for (let j = 0; j < size; j++) {
|
|
const boardCellHTML = document.createElement("td");
|
|
|
|
board[i].push(false);
|
|
boardCellHTML.className = "dead";
|
|
|
|
boardRowHTML.appendChild(boardCellHTML);
|
|
}
|
|
|
|
boardHTML.appendChild(boardRowHTML);
|
|
}
|
|
|
|
root.appendChild(boardHTML);
|
|
|
|
return [boardHTML, board];
|
|
};
|
|
|
|
type BoardOperationsT = {
|
|
setStatus: (x: number, y: number, val: boolean) => void;
|
|
syncStatus: () => void;
|
|
getSize: () => number;
|
|
};
|
|
|
|
const createOperators = (
|
|
boardHTML: HTMLTableElement,
|
|
board: BoardT
|
|
): BoardOperationsT => ({
|
|
setStatus: (x, y, val) => {
|
|
board[y][x] = val;
|
|
|
|
const boardRowHTML = boardHTML.children.item(y);
|
|
if (boardRowHTML) {
|
|
const cellHTML = boardRowHTML.children.item(x);
|
|
if (cellHTML) {
|
|
cellHTML.className = isAlive(val) as string;
|
|
}
|
|
}
|
|
},
|
|
syncStatus: () => {
|
|
for (let y = 0; y < board.length; y++) {
|
|
const boardRowHTML = boardHTML.children.item(y);
|
|
|
|
if (boardRowHTML)
|
|
for (let x = 0; x < board.length; x++) {
|
|
const boardCellHTML = boardRowHTML.children.item(x);
|
|
|
|
if (boardCellHTML)
|
|
board[y][x] = isAlive(boardCellHTML.className) as boolean;
|
|
}
|
|
}
|
|
},
|
|
getSize: () => board.length,
|
|
});
|
|
|
|
function* randomBit(amount: number) {
|
|
const bytes = new Uint8Array(amount);
|
|
const QUOTA = 65536;
|
|
|
|
for (let i = 0; i < amount; i += QUOTA) {
|
|
crypto.getRandomValues(bytes.subarray(i, i + Math.min(amount - i, QUOTA)));
|
|
}
|
|
|
|
let n = 0;
|
|
|
|
while (true) {
|
|
if (n >= amount) return false;
|
|
|
|
const byteN = Math.floor(n / 8);
|
|
const bitPos = n % 8;
|
|
|
|
yield Boolean((bytes[byteN] >> bitPos) & 1);
|
|
|
|
n++;
|
|
}
|
|
}
|
|
|
|
const initBoard = (op: BoardOperationsT) => {
|
|
const size = op.getSize();
|
|
const getRandomBit = randomBit(size * size);
|
|
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
const val = getRandomBit.next().value;
|
|
op.setStatus(x, y, val);
|
|
}
|
|
}
|
|
};
|
|
|
|
const countNeighbours = (x: number, y: number, board: BoardT) => {
|
|
let k = 0;
|
|
|
|
if (y > 0) {
|
|
if (board[y - 1][x]) k++;
|
|
if (x < board.length && board[y - 1][x + 1]) k++;
|
|
if (x > 0 && board[y - 1][x - 1]) k++;
|
|
}
|
|
|
|
if (y < board.length - 1) {
|
|
if (board[y + 1][x]) k++;
|
|
if (x < board.length && board[y + 1][x + 1]) k++;
|
|
if (x > 0 && board[y + 1][x - 1]) k++;
|
|
}
|
|
|
|
if (x > 0 && board[y][x - 1]) k++;
|
|
if (x < board.length && board[y][x + 1]) k++;
|
|
|
|
return k;
|
|
};
|
|
|
|
const doStep = (op: BoardOperationsT, board: BoardT) => {
|
|
const size = op.getSize();
|
|
|
|
for (let y = 0; y < size; y++) {
|
|
for (let x = 0; x < size; x++) {
|
|
const nb = countNeighbours(x, y, board);
|
|
if (board[y][x] && (nb < 2 || nb > 3)) op.setStatus(x, y, false);
|
|
else if (!board[y][x] && nb === 3) op.setStatus(x, y, true);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const initGame = (size: number) => {
|
|
const root = document.getElementById("board");
|
|
|
|
if (!root) {
|
|
console.error("Can not find root element");
|
|
} else {
|
|
root.innerHTML = "";
|
|
|
|
const [boardHTML, board] = createBoard(root, size);
|
|
|
|
const op = createOperators(boardHTML, board);
|
|
|
|
initBoard(op);
|
|
|
|
for (let t = 750; t < 100000; t += 750)
|
|
setTimeout(() => doStep(op, board), t);
|
|
}
|
|
};
|
|
|
|
const runButton = document.getElementById("run");
|
|
|
|
if (runButton) {
|
|
runButton.onclick = (e) => {
|
|
e.preventDefault();
|
|
|
|
const sizeInput = document.getElementById("size");
|
|
|
|
if (sizeInput) {
|
|
const { value } = sizeInput as HTMLInputElement;
|
|
|
|
const size = value ? parseInt(value) : 16;
|
|
|
|
initGame(size);
|
|
}
|
|
};
|
|
}
|