```html
Snake
Score: 0
```
```typescript
const TILE_SIZE = 20;
const GRID_WIDTH = 20;
const GRID_HEIGHT = 20;
const MOVE_INTERVAL = 100;
const canvas = document.getElementById("game") as HTMLCanvasElement;
const ctx = canvas.getContext("2d");
if (!ctx) {
throw new Error("Unable to get 2D context");
}
const scoreEl = document.getElementById("score") as HTMLSpanElement;
type Point = { x: number; y: number };
let snake: Point[] = [];
let food: Point = { x: 0, y: 0 };
let dx = 1;
let dy = 0;
let nextDx = 1;
let nextDy = 0;
let score = 0;
let gameOver = false;
let lastMoveTime = 0;
function placeFood(): void {
let x: number;
let y: number;
do {
x = Math.floor(Math.random() * GRID_WIDTH);
y = Math.floor(Math.random() * GRID_HEIGHT);
} while (snake.some((seg) => seg.x === x && seg.y === y));
food = { x, y };
}
function reset(): void {
snake = [{ x: Math.floor(GRID_WIDTH / 2), y: Math.floor(GRID_HEIGHT / 2) }];
dx = 1;
dy = 0;
nextDx = 1;
nextDy = 0;
score = 0;
gameOver = false;
lastMoveTime = 0;
placeFood();
}
function move(): void {
dx = nextDx;
dy = nextDy;
const head = snake[0];
const newHead: Point = { x: head.x + dx, y: head.y + dy };
if (
newHead.x < 0 ||
newHead.x >= GRID_WIDTH ||
newHead.y < 0 ||
newHead.y >= GRID_HEIGHT
) {
gameOver = true;
return;
}
if (snake.some((seg) => seg.x === newHead.x && seg.y === newHead.y)) {
gameOver = true;
return;
}
snake.unshift(newHead);
if (newHead.x === food.x && newHead.y === food.y) {
score += 1;
placeFood();
} else {
snake.pop();
}
}
function draw(): void {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#e74c3c";
ctx.fillRect(
food.x * TILE_SIZE,
food.y * TILE_SIZE,
TILE_SIZE - 2,
TILE_SIZE - 2
);
ctx.fillStyle = "#2ecc71";
for (const seg of snake) {
ctx.fillRect(
seg.x * TILE_SIZE,
seg.y * TILE_SIZE,
TILE_SIZE - 2,
TILE_SIZE - 2
);
}
if (gameOver) {
ctx.fillStyle = "#fff";
ctx.font = "30px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Game Over", canvas.width / 2, canvas.height / 2);
}
scoreEl.textContent = String(score);
}
function gameLoop(timestamp: number): void {
if (!lastMoveTime) {
lastMoveTime = timestamp;
}
if (timestamp - lastMoveTime >= MOVE_INTERVAL) {
if (!gameOver) {
move();
}
lastMoveTime = timestamp;
}
draw();
requestAnimationFrame(gameLoop);
}
window.addEventListener("keydown", (e: KeyboardEvent) => {
if (gameOver) {
if (e.key === " ") {
reset();
}
return;
}
switch (e.key) {
case "ArrowUp":
if (dy === 0) {
nextDx = 0;
nextDy = -1;
}
break;
case "ArrowDown":
if (dy === 0) {
nextDx = 0;
nextDy = 1;
}
break;
case "ArrowLeft":
if (dx === 0) {
nextDx = -1;
nextDy = 0;
}
break;
case "ArrowRight":
if (dx === 0) {
nextDx = 1;
nextDy = 0;
}
break;
}
});
reset();
requestAnimationFrame(gameLoop);
```