110 lines
3.3 KiB
TypeScript
110 lines
3.3 KiB
TypeScript
|
|
import { createCanvas, getPixelInt, setPixel } from '@common/display/canvas';
|
|
import { gameLoop } from '@common/game';
|
|
import Input from '@common/input';
|
|
|
|
const sandWidth = 128;
|
|
|
|
const setup = () => {
|
|
const canvas = createCanvas(window.innerWidth >> 2, window.innerHeight >> 2);
|
|
const ctx = canvas.getContext('2d');
|
|
if (!ctx) {
|
|
throw new Error('Failed to get canvas context');
|
|
}
|
|
document.body.style.backgroundColor = '#eee';
|
|
|
|
const imageData = initData();
|
|
|
|
return {
|
|
canvas,
|
|
ctx,
|
|
imageData,
|
|
};
|
|
};
|
|
|
|
const initData = () => {
|
|
const imageData = new ImageData(sandWidth, sandWidth);
|
|
|
|
for (let x = 0; x < imageData.width; x++) {
|
|
for (let y = 0; y < imageData.height; y++) {
|
|
const index = (y * imageData.width + x) * 4;
|
|
imageData.data[index + 0] = Math.random() < 0.3 ? 255 : 0;
|
|
imageData.data[index + 1] = Math.random() < 0.3 ? 255 : 0;
|
|
imageData.data[index + 2] = Math.random() < 0.3 ? 255 : 0;
|
|
imageData.data[index + 3] = 255;
|
|
}
|
|
}
|
|
|
|
return imageData;
|
|
}
|
|
|
|
const emptyColor = 0x000000FF;
|
|
const isEmpty = (pixel: number) => (pixel >> 8) === 0;
|
|
|
|
type State = ReturnType<typeof setup>;
|
|
|
|
const frame = async (dt: number, state: State): Promise<State> => {
|
|
const { canvas, ctx } = state;
|
|
|
|
if (Input.isPressed(Input.KeyCode.SPACE)) {
|
|
state.imageData = initData();
|
|
}
|
|
|
|
const imageData = state.imageData;
|
|
|
|
for (let y = imageData.height - 2; y >= 0; y--) {
|
|
const direction = Math.random() < 0.5 ? 1 : -1;
|
|
let startX, endX;
|
|
if (direction > 0) {
|
|
startX = 0;
|
|
endX = imageData.width;
|
|
} else {
|
|
startX = imageData.width - 1;
|
|
endX = -1;
|
|
}
|
|
for (let x = startX; x != endX; x += direction) {
|
|
const pixel = getPixelInt(imageData, x, y);
|
|
if (isEmpty(pixel)) continue;
|
|
|
|
const pixelBelow = getPixelInt(imageData, x, y + 1);
|
|
if (isEmpty(pixelBelow)) {
|
|
setPixel(imageData, x, y, emptyColor);
|
|
setPixel(imageData, x, y + 1, pixel);
|
|
continue;
|
|
}
|
|
|
|
let leftEmpty = false;
|
|
let rightEmpty = false;
|
|
|
|
if (x > 0) {
|
|
const pixelLeft = getPixelInt(imageData, x - 1, y + 1);
|
|
leftEmpty = isEmpty(pixelLeft);
|
|
}
|
|
if (x < imageData.width - 1) {
|
|
const pixelRight = getPixelInt(imageData, x + 1, y + 1);
|
|
rightEmpty = isEmpty(pixelRight);
|
|
}
|
|
|
|
const selector = Math.random() < 0.5;
|
|
const selectedLeft = (leftEmpty && rightEmpty && selector) || (leftEmpty && !rightEmpty);
|
|
const selectedRight = (leftEmpty && rightEmpty && !selector) || (!leftEmpty && rightEmpty);
|
|
|
|
if (selectedLeft) {
|
|
setPixel(imageData, x, y, emptyColor);
|
|
setPixel(imageData, x - 1, y + 1, pixel);
|
|
} else if (selectedRight) {
|
|
setPixel(imageData, x, y, emptyColor);
|
|
setPixel(imageData, x + 1, y + 1, pixel);
|
|
}
|
|
}
|
|
}
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
ctx.putImageData(imageData, (canvas.width - sandWidth) / 2, (canvas.height - sandWidth) / 2);
|
|
|
|
return {
|
|
...state,
|
|
};
|
|
};
|
|
|
|
export default gameLoop(setup, frame); |