说明:我计划用angular实现一个贪吃蛇的程序,并且有方向键去控制蛇的上下左右的移动,并且有得分系统,当蛇撞到墙壁或者自身,游戏结束
效果图:
step1: C:\Users\Administrator\WebstormProjects\untitled4\src\app\snake\snake.component.ts
javascript">import { Component , OnInit, OnDestroy, HostListener } from '@angular/core';interface Position {x: number;y: number;
}@Component({selector: 'app-snake',imports: [],templateUrl: './snake.component.html',styleUrl: './snake.component.css'
})
export class SnakeComponent implements OnInit, OnDestroy{private ctx!: CanvasRenderingContext2D;private gameLoop!: any;private gridSize = 20;snake: Position[] = [{ x: 10, y: 10 }];food: Position = this.generateFood();direction: 'UP' | 'DOWN' | 'LEFT' | 'RIGHT' = 'RIGHT';score = 0;gameSpeed = 150;isPaused = false;@HostListener('window:keydown', ['$event'])handleKeydown(event: KeyboardEvent) {this.handleDirection(event.key.replace('Arrow', '').toUpperCase());}ngOnInit() {const canvas = document.getElementById('gameCanvas') as HTMLCanvasElement;this.ctx = canvas.getContext('2d')!;this.startGame();}ngOnDestroy() {clearInterval(this.gameLoop);}handleDirection(newDirection: string) {const validMoves: Record<string, boolean> = {'UP': this.direction !== 'DOWN','DOWN': this.direction !== 'UP','LEFT': this.direction !== 'RIGHT','RIGHT': this.direction !== 'LEFT'};if (validMoves[newDirection]) {this.direction = newDirection as any;}}private startGame() {this.gameLoop = setInterval(() => this.update(), this.gameSpeed);}private update() {if (this.isPaused) return;const head = { ...this.snake[0] };switch (this.direction) {case 'UP': head.y--; break;case 'DOWN': head.y++; break;case 'LEFT': head.x--; break;case 'RIGHT': head.x++; break;}if (this.checkCollision(head)) {clearInterval(this.gameLoop);alert(`游戏结束! 得分: ${this.score}`);return;}this.snake.unshift(head);if (head.x === this.food.x && head.y === this.food.y) {this.score++;this.food = this.generateFood();this.gameSpeed = Math.max(50, this.gameSpeed - 2);} else {this.snake.pop();}this.draw();}private draw() {// 游戏区域背景this.ctx.fillStyle = '#1a1b26';this.ctx.fillRect(0, 0, 400, 400);// 食物绘制this.ctx.fillStyle = '#f7768e';this.ctx.beginPath();this.ctx.roundRect(this.food.x * this.gridSize + 2,this.food.y * this.gridSize + 2,this.gridSize - 4,this.gridSize - 4,4);this.ctx.fill();// 蛇身绘制this.snake.forEach((segment, index) => {this.ctx.fillStyle = index === 0 ? '#9ece6a' : '#73daca';this.ctx.beginPath();this.ctx.roundRect(segment.x * this.gridSize + 2,segment.y * this.gridSize + 2,this.gridSize - 4,this.gridSize - 4,index === 0 ? 6 : 4);this.ctx.fill();});}private generateFood(): Position {return {x: Math.floor(Math.random() * 20),y: Math.floor(Math.random() * 20)};}private checkCollision(pos: Position): boolean {return pos.x < 0 || pos.x >= 20 || pos.y < 0 || pos.y >= 20 ||this.snake.some(segment => segment.x === pos.x && segment.y === pos.y);}togglePause() {this.isPaused = !this.isPaused;}resetGame() {this.snake = [{ x: 10, y: 10 }];this.direction = 'RIGHT';this.score = 0;this.gameSpeed = 150;this.food = this.generateFood();this.isPaused = false;clearInterval(this.gameLoop);this.startGame();}
}
step2: C:\Users\Administrator\WebstormProjects\untitled4\src\app\snake\snake.component.html
<!-- snake.component.html -->
<div class="game-container"><div class="game-header"><div class="score-box"><span>得分: {{ score }}</span></div><div class="controls"><button class="control-btn pause-btn" (click)="togglePause()">{{ isPaused ? '继续' : '暂停' }}</button><button class="control-btn reset-btn" (click)="resetGame()">重置</button></div></div><canvas id="gameCanvas" width="400" height="400"></canvas><div class="direction-controls"><div class="control-row"><button class="direction-btn up" (click)="handleDirection('UP')">↑</button></div><div class="control-row"><button class="direction-btn left" (click)="handleDirection('LEFT')">←</button><button class="direction-btn down" (click)="handleDirection('DOWN')">↓</button><button class="direction-btn right" (click)="handleDirection('RIGHT')">→</button></div></div>
</div>
step3: C:\Users\Administrator\WebstormProjects\untitled4\src\app\snake\snake.component.css
/* snake.component.css */
.game-container {display: flex;flex-direction: column;align-items: center;gap: 1.5rem;padding: 2rem;background: #24283b;border-radius: 1.5rem;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}.game-header {display: flex;justify-content: space-between;width: 100%;max-width: 400px;color: #c0caf5;
}.score-box {background: #414868;padding: 0.5rem 1rem;border-radius: 0.5rem;font-size: 1.1rem;
}.controls {display: flex;gap: 0.5rem;
}.control-btn {padding: 0.5rem 1rem;border: none;border-radius: 0.5rem;cursor: pointer;transition: transform 0.2s, opacity 0.2s;
}.pause-btn {background: linear-gradient(135deg, #7aa2f7, #2ac3de);color: white;
}.reset-btn {background: linear-gradient(135deg, #f7768e, #ff9e64);color: white;
}.control-btn:hover {opacity: 0.9;transform: translateY(-1px);
}.direction-controls {display: flex;flex-direction: column;gap: 0.5rem;
}.control-row {display: flex;justify-content: center;gap: 0.5rem;
}.direction-btn {width: 3.5rem;height: 3.5rem;border: none;border-radius: 1rem;background: linear-gradient(145deg, #414868, #565f89);color: #c0caf5;font-size: 1.5rem;cursor: pointer;transition: all 0.2s;display: flex;align-items: center;justify-content: center;
}.direction-btn:hover {background: linear-gradient(145deg, #565f89, #414868);transform: scale(1.05);box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}.direction-btn:active {transform: scale(0.95);
}/* 移动端优化 */
@media (max-width: 480px) {.game-container {padding: 1rem;width: 95vw;}canvas {width: 95vw;height: 95vw;}.direction-btn {width: 3rem;height: 3rem;font-size: 1.2rem;}
}
end