You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular 17贪吃蛇游戏问题:蛇与水果无法在棋盘显示

Angular 17 贪吃蛇游戏:蛇与水果无法显示问题解决

问题描述

我正在开发贪吃蛇游戏的初始阶段,需完成以下步骤:

  • 创建游戏棋盘
  • 创建蛇和水果
  • 在棋盘上显示蛇和水果

当前问题:蛇和水果生成后,棋盘数据已更新,但它们并未显示在游戏棋盘上。

说明:棋盘单元格中0代表空,1代表蛇身,2代表水果。

相关代码

HTML(gameboard组件)

<div class="gameB">
    <div *ngFor="let i of board" class="row">
        <div *ngFor="let j of i" class="cell"
        [ngClass]="{'snake' : board[board.indexOf(i)][j] === 1, 'fruit': board[board.indexOf(i)][j] === 2}">
            {{ board[board.indexOf(i)][j] }}
        </div>
    </div>
</div>

TypeScript(gameboard组件)

import { CommonModule } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { Snake } from '../snake-iterface';

@Component({
  selector: 'app-game-board',
  standalone: true,
  imports: [CommonModule],
  templateUrl: './game-board.component.html',
  styleUrl: './game-board.component.css'
})
export class GameBoardComponent implements OnInit{
  boardWidth:number = 20;
  boardHeight:number = 20;
  board:number[][] = [];
  snake:Snake = {} as Snake;
  fruit:number[] = [1,1];
  
  createBoard(){
    let temp:number[];
    for(let i = 0; i < this.boardHeight;i++){
      temp =[]
      for(let j = 0; j < this.boardWidth;j++){
        temp.push(0)
      }
      this.board.push(temp)
    }
  }
  
  spawnSnake():void{
    this.board[this.snake.headPositionY][this.snake.headPositionX] = 1;
  }
  
  updateBoard():void{
    for(let i = 0; i < this.boardHeight; i++){
      for(let j = 0; j < this.boardWidth;j++){
        if(this.snake.bodyPositions.includes([j, i]) ){
          this.board[i][j] = 1;
        }else if((j === this.fruit[0] && i == this.fruit[1])){
          this.board[i][j] = 2;
        }
      }
    }
  }

  ngOnInit(): void {
    this.createBoard()
    console.log("Before Snake creation", this.board)
    this.snake = {
      headPositionX: Math.floor(this.boardWidth / 2),
      headPositionY: Math.floor(this.boardHeight / 2),
      bodyPositions: [[Math.floor(this.boardWidth / 2), Math.floor(this.boardHeight / 2)]],
      length: 1,
      direction: 'right'
    };

    console.log("Before Spawn", this.board)
    this.spawnSnake()
    
    this.updateBoard()
    console.log("After Update 1", this.board)
    setTimeout(this.updateBoard, 1000)
    console.log("After Update 2", this.board)
  }
}

CSS(gameboard组件)

.gameB {
    margin: auto;
    padding:10px;
    display: flex;
    flex-direction: column;
    gap: 3px;
    background-color: black;
    width:fit-content;
}
.row {
    display: flex;
    flex-direction: row;
    gap: 3px;
}
.cell {
    width:20px;
    height:20px;
    background-color: grey;
}
.snake {
    background-color:red;
}
.fruit {
    background-color:purple;
}

其他文件

HTML(app组件)

<app-game-board></app-game-board>

HTML(index.html)

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>SnakeGame</title>
  <base href="/">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root></app-root>
</body>
</html>

问题分析

  1. HTML模板逻辑错误:*ngFor遍历中,j是数组元素而非索引,board[board.indexOf(i)][j]的取值逻辑完全错误,无法正确获取单元格的数值。
  2. 数组引用判断失效:this.snake.bodyPositions.includes([j, i])中,数组是引用类型,两个内容相同的数组引用不同,includes会返回false,导致蛇身无法被正确标记。
  3. setTimeout的this指向问题:直接传递this.updateBoard给setTimeout,会导致方法内的this指向全局对象而非组件实例,无法访问snake、board等属性。
  4. 控制台日志的误导:数组是引用类型,console.log存储的是数组引用,会显示后续修改后的状态,而非打印时的真实状态,所以看起来蛇和水果提前出现在棋盘中。

解决方案

1. 修正HTML模板

使用*ngFor的索引变量来正确访问棋盘数据:

<div class="gameB">
    <div *ngFor="let row of board; let i = index" class="row">
        <div *ngFor="let cell of row; let j = index" class="cell"
        [ngClass]="{'snake' : board[i][j] === 1, 'fruit': board[i][j] === 2}">
            {{ board[i][j] }}
        </div>
    </div>
</div>

2. 修复updateBoard方法

重写方法逻辑,先清空棋盘再重新设置蛇和水果,避免引用判断错误:

updateBoard():void{
    // 先清空棋盘,清除旧数据
    for(let i = 0; i < this.boardHeight; i++){
        for(let j = 0; j < this.boardWidth;j++){
            this.board[i][j] = 0;
        }
    }
    // 设置蛇身
    this.snake.bodyPositions.forEach(pos => {
        const [x, y] = pos;
        this.board[y][x] = 1;
    });
    // 设置水果
    const [fruitX, fruitY] = this.fruit;
    this.board[fruitY][fruitX] = 2;
}

3. 修正setTimeout的this指向

使用箭头函数绑定组件实例的this:

// 替换原来的setTimeout
setTimeout(() => this.updateBoard(), 1000)

4. 优化控制台日志

使用深拷贝打印数组的实时状态,避免引用误导:

ngOnInit(): void {
    this.createBoard()
    // 用深拷贝打印当前状态
    console.log("Before Snake creation", JSON.parse(JSON.stringify(this.board)))
    this.snake = {
        headPositionX: Math.floor(this.boardWidth / 2),
        headPositionY: Math.floor(this.boardHeight / 2),
        bodyPositions: [[Math.floor(this.boardWidth / 2), Math.floor(this.boardHeight / 2)]],
        length: 1,
        direction: 'right'
    };

    console.log("Before Update", JSON.parse(JSON.stringify(this.board)))
    // 移除spawnSnake,因为updateBoard已经会设置蛇身
    this.updateBoard()
    console.log("After Update 1", JSON.parse(JSON.stringify(this.board)))
    setTimeout(() => this.updateBoard(), 1000)
}

可选:移除冗余的spawnSnake方法

由于updateBoard已经负责设置蛇身,spawnSnake方法重复操作,可以直接删除。

补充说明

  • 控制台日志的“提前显示蛇和水果”是因为数组引用特性,console.log会动态更新显示数组的最新状态,用JSON.parse(JSON.stringify())深拷贝后可以打印出日志执行时的真实状态。
  • 所有修改完成后,蛇和水果会正确渲染到棋盘上,后续可以继续开发移动、碰撞检测等功能。

内容的提问来源于stack exchange,提问作者UnPatched Main

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 17:54:51