feat: implement canvas-based rendering with geometric spaceship

- Replace DOM grid with HTML5 canvas (800x600)
- Draw spaceship as geometric shapes (triangle + window + flames)
- Green body, yellow window, orange flame trails
- Smooth pixel-based rendering instead of DOM elements
This commit is contained in:
2025-11-03 21:53:42 +00:00
parent fb79aa0229
commit dce96b8453
+61 -48
View File
@@ -1,64 +1,77 @@
import { useRef } from "react";
import { useRef, useEffect } from "react";
import type { PlayerPosition } from "../types";
const BOARD_WIDTH = 10;
const BOARD_HEIGHT = 20;
const CELL_SIZE = 40; // pixels
const CANVAS_WIDTH = 800;
const CANVAS_HEIGHT = 600;
const SPACESHIP_Y = CANVAS_HEIGHT - 40;
export function useBoard() {
const containerRef = useRef<HTMLDivElement | null>(null);
const canvasRef = useRef<HTMLCanvasElement | null>(null);
// Setup canvas on mount
useEffect(() => {
if (!containerRef.current) return;
const canvas = document.createElement("canvas");
canvas.width = CANVAS_WIDTH;
canvas.height = CANVAS_HEIGHT;
canvas.style.border = "1px solid #0f3460";
canvas.style.backgroundColor = "#000";
canvas.style.display = "block";
containerRef.current.innerHTML = "";
containerRef.current.appendChild(canvas);
canvasRef.current = canvas;
}, []);
const renderBoard = (playerPosition: PlayerPosition) => {
const container = containerRef.current;
if (!container) return;
const canvas = canvasRef.current;
if (!canvas) return;
container.innerHTML = "";
const ctx = canvas.getContext("2d");
if (!ctx) return;
// Create board grid
const board = document.createElement("div");
board.className = "typing-board";
board.style.display = "grid";
board.style.gridTemplateColumns = `repeat(${BOARD_WIDTH}, ${CELL_SIZE}px)`;
board.style.gap = "1px";
board.style.backgroundColor = "#000";
board.style.padding = "8px";
board.style.borderRadius = "4px";
board.style.width = "fit-content";
// Clear canvas
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);
// Create cells
for (let row = 0; row < BOARD_HEIGHT; row++) {
for (let col = 0; col < BOARD_WIDTH; col++) {
const cell = document.createElement("div");
cell.className = "typing-cell";
cell.setAttribute("data-row", String(row));
cell.setAttribute("data-col", String(col));
cell.style.width = `${CELL_SIZE}px`;
cell.style.height = `${CELL_SIZE}px`;
cell.style.backgroundColor = "#1a1a2e";
cell.style.border = "1px solid #0f3460";
cell.style.display = "flex";
cell.style.alignItems = "center";
cell.style.justifyContent = "center";
cell.style.fontSize = "12px";
cell.style.fontFamily = "monospace";
cell.style.color = "#00ff00";
// Draw spaceship at player position
const spaceshipX = (playerPosition.x / 10) * CANVAS_WIDTH + CANVAS_WIDTH / 20;
const spaceshipY = SPACESHIP_Y;
// Highlight player row (bottom)
if (row === BOARD_HEIGHT - 1) {
cell.style.backgroundColor = "#0f3460";
// Render spaceship in player column
if (col === playerPosition.x) {
cell.innerHTML = "🚀";
cell.style.fontSize = "24px";
}
}
// Spaceship body (triangle pointing up)
ctx.fillStyle = "#00ff00";
ctx.beginPath();
ctx.moveTo(spaceshipX, spaceshipY - 15); // top point
ctx.lineTo(spaceshipX - 10, spaceshipY + 10); // left bottom
ctx.lineTo(spaceshipX + 10, spaceshipY + 10); // right bottom
ctx.closePath();
ctx.fill();
board.appendChild(cell);
}
}
// Spaceship window (circle in center)
ctx.fillStyle = "#ffff00";
ctx.beginPath();
ctx.arc(spaceshipX, spaceshipY - 5, 3, 0, Math.PI * 2);
ctx.fill();
container.appendChild(board);
// Spaceship flames (small triangles at bottom)
ctx.fillStyle = "#ff6600";
// Left flame
ctx.beginPath();
ctx.moveTo(spaceshipX - 5, spaceshipY + 10);
ctx.lineTo(spaceshipX - 3, spaceshipY + 20);
ctx.lineTo(spaceshipX - 8, spaceshipY + 15);
ctx.closePath();
ctx.fill();
// Right flame
ctx.beginPath();
ctx.moveTo(spaceshipX + 5, spaceshipY + 10);
ctx.lineTo(spaceshipX + 3, spaceshipY + 20);
ctx.lineTo(spaceshipX + 8, spaceshipY + 15);
ctx.closePath();
ctx.fill();
};
return {