```html
Snake Game
Score: 0
Game Over
```
```typescript
// snake.ts
const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;
const ctx = canvas.getContext('2d')!;
const scoreDiv = document.getElementById('score') as HTMLDivElement;
const gameOverDiv = document.getElementById('gameOver') as HTMLDivElement;
const cellSize = 20;
const gridSize = canvas.width / cellSize; // 20
interface Position {
x: number;
y: number;
}
enum Direction {
Up,
Down,
Left,
Right,
}
let snake: Position[] = [{ x: 9, y: 9 }];
let direction: Direction = Direction.Right;
let food: Position = generateFood();
let score: number = 0;
let gameRunning: boolean = true;
function generateFood(): Position {
let newFood: Position;
while (true) {
newFood = {
x: Math.floor(Math.random() * gridSize),
y: Math.floor(Math.random() * gridSize),
};
if (!snake.some(segment => segment.x === newFood.x && segment.y === newFood.y)) {
break;
}
}
return newFood;
}
function isOpposite(d1: Direction, d2: Direction): boolean {
return (d1 === Direction.Up && d2 === Direction.Down) ||
(d1 === Direction.Down && d2 === Direction.Up) ||
(d1 === Direction.Left && d2 === Direction.Right) ||
(d1 === Direction.Right && d2 === Direction.Left);
}
function moveSnake(): void {
const head = snake[0];
let newHead: Position;
switch (direction) {
case Direction.Up:
newHead = { x: head.x, y: head.y - 1 };
break;
case Direction.Down:
newHead = { x: head.x, y: head.y + 1 };
break;
case Direction.Left:
newHead = { x: head.x - 1, y: head.y };
break;
case Direction.Right:
newHead = { x: head.x + 1, y: head.y };
break;
}
if (checkCollision(newHead)) {
gameOver();
return;
}
snake.unshift(newHead);
if (newHead.x === food.x && newHead.y === food.y) {
score += 1;
scoreDiv.textContent = `Score: ${score}`;
food = generateFood();
} else {
snake.pop();
}
}
function checkCollision(pos: Position): boolean {
if (pos.x < 0 || pos.x >= gridSize || pos.y < 0 || pos.y >= gridSize) {
return true;
}
return snake.some(segment => segment.x === pos.x && segment.y === pos.y);
}
function draw(): void {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw food
ctx.fillStyle = 'red';
ctx.fillRect(food.x * cellSize, food.y * cellSize, cellSize, cellSize);
// Draw snake
ctx.fillStyle = 'green';
snake.forEach(segment => {
ctx.fillRect(segment.x * cellSize, segment.y * cellSize, cellSize, cellSize);
});
}
function gameLoop(): void {
if (!gameRunning) return;
moveSnake();
draw();
requestAnimationFrame(gameLoop);
}
function gameOver(): void {
gameRunning = false;
gameOverDiv.style.display = 'block';
}
window.addEventListener('keydown', (e: KeyboardEvent) => {
let newDirection: Direction | null = null;
switch (e.key) {
case 'ArrowUp':
newDirection = Direction.Up;
break;
case 'ArrowDown':
newDirection = Direction.Down;
break;
case 'ArrowLeft':
newDirection = Direction.Left;
break;
case 'ArrowRight':
newDirection = Direction.Right;
break;
}
if (newDirection !== null && !isOpposite(direction, newDirection)) {
direction = newDirection;
}
});
requestAnimationFrame(gameLoop);
```