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; const frame = async (dt: number, state: State): Promise => { 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);