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

基于ncurses的贪吃蛇持续移动功能故障排查求助

贪吃蛇游戏移动功能故障排查与修复

问题概述

实现贪吃蛇游戏时,将控制变量从var替换为dir后出现以下异常:

  • 蛇无法按按下的方向(a/w/s/d)自动持续移动
  • 按住按键时蛇能移动,但后续不再响应键盘输入
  • 存在段错误
  • 青蛙需触发按键才会生成

完整故障代码

#include <cctype>
#include <cstdlib>
#include <curses.h>
#include <stdio.h>
#include <wchar.h>
#include <locale.h>
#include <stdlib.h>
#include <ncurses.h>
#include <ctime>
#include <time.h>
#include <errno.h>
#define N 20
#define M 40
int i,j,Field[N][M],x,y,gy,Head,Tail,Game,Frogs,a ,b,var,dir;
void snakeInitialization(){
  for(i=0;i<N;i++){
    for(j=0;j<M;j++){
      Field [i][j]=0;
    }
  }
  x=N/2;
  y=M/2;
  Head=5;
  Tail=1;
  gy=y;
  Game = 0;
  Frogs = 0;
  a = 0;
  b = 0;
  var = 0;
  for(i=0;i<Head;i++)
  {
    gy++;
    Field[x][gy-Head]=i+1;
  }
}
int kbhit(void)    /* comment */
{
    int ch, r;

    // turn off getch() blocking and echo
    nodelay(stdscr, TRUE);
    noecho();

    // check for input
    ch = getch();
    if( ch == ERR)      // no input
            r = FALSE;
    else                // input
    {
            r = TRUE;
            ungetch(ch);
    }

    // restore block and echo
    nodelay(stdscr, FALSE);
    return(r);
}
int msleep(long msec)
{
    struct timespec ts;
    int res;

    if (msec < 0)
    {
        errno = EINVAL;
        return -1;
    }

    ts.tv_sec = msec / 1000;
    ts.tv_nsec = (msec % 1000) * 1000000;

    do {
        res = nanosleep(&ts, &ts);
    } while (res && errno == EINTR);

    return res;
}

void print()
{
  wchar_t RDC = 0x255D;//right down corner simbol
  wchar_t LDC = 0x255A;//left down corner unicode simbol
  wchar_t VW = 0x2551;//vertical wall simbol
  wchar_t HW = 0x2550;//Horizontal wall simbol
  wchar_t RUC = 0x2557;//right up corner simbol
  wchar_t LUC = 0x2554;//left up corner simbol
  wchar_t HS = 0x2593;
  wchar_t BS = 0x2591;
  wchar_t Frg = 0x1318F;//frog
  for (i=0;i<=M+1;i++)
  {
    if(i==0)
    {
      printw("%lc",LUC);
    }
    else if (i==M+1){
      printw("%lc",RUC);
    }
    else{
      printw("%lc",HW);
    }
  }
  printw("\n");
  for(i=0;i<N;i++)
  {
    printw("%lc",VW);
    for (j=0;j<M;j++)
    {
      if(Field[i][j]==0) printw(" ");
      if(Field[i][j]>0&&Field[i][j]!=Head) printw("%lc",BS);
      if(Field[i][j]==Head) printw("%lc",HS);
      if(Field[i][j]==-1) printw("%lc",Frg);
      if(j==M-1) printw("%lc\n",VW);
    }
  }
  for (i=0;i<=M+1;i++)
  {
    if(i==0)
    {
      printw("%lc",LDC);
    }
    else if (i==M+1){

      printw("%lc",RDC);
    }
    else{
      printw("%lc",HW);
    }
  }
}
void ResetScreenPosition(){
  move(0, 0);
}
void Random (){
  srand(time(0));
  a = 1 + rand() % 18;
  b = 1 + rand() % 38;
  if (Frogs == 0 && Field[a][b]==0) {
    Field[a][b] = -1;
    Frogs = 1;
  }
}
long int getch_noblock(){
  if(!kbhit())
    return getch();
  else
    return -1;
}
void movement(){
  var = getch_noblock();
  var = tolower(var);
  if (var == 'd' || var == 'a' || var == 'w' || var == 's') {
    dir=var; 
  }
  if (dir == 'd'){
    y++;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 'a'){
    y--;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 'w'){
    x--;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 's'){
    x++;
    Head++;
    Field[x][y]=Head;
  }
}
int main (int argc, char *argv[]) {
  setlocale(LC_CTYPE, "");
  snakeInitialization();  // moving cursor, x = 20, y = 10
  initscr();
  while (Game == 0) {
    print();
    ResetScreenPosition();
    Random();
    movement();
    msleep(99);
    refresh();

  }
  endwin();
  return 0;
}

核心故障函数(movement)

void movement(){
  var = getch_noblock();
  var = tolower(var);
  if (var == 'd' || var == 'a' || var == 'w' || var == 's') {
    dir=var; 
  }
  if (dir == 'd'){
    y++;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 'a'){
    y--;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 'w'){
    x--;
    Head++;
    Field[x][y]=Head;
  }
  if (dir == 's'){
    x++;
    Head++;
    Field[x][y]=Head;
  }
}

问题分析与修复方案

1. 输入读取逻辑完全反转

getch_noblock函数逻辑颠倒,导致有按键时返回-1,无按键时阻塞等待输入,这是蛇无法自动移动的核心原因:

// 修复后的getch_noblock
long int getch_noblock(){
  if(kbhit())
    return getch();
  else
    return -1;
}

2. dir变量未初始化

全局变量dir未初始化,初始值为随机垃圾值,导致蛇初始状态无移动或乱走。在snakeInitialization中添加默认方向:

void snakeInitialization(){
  // ... 原有代码 ...
  dir = 'd'; // 设置初始向右移动
}

3. 随机数种子重复初始化

Random函数每次调用都执行srand(time(0)),短时间内会生成相同随机坐标,且青蛙可能生成在蛇身上。将srand移到main开头仅初始化一次,并确保青蛙生成在空白区域:

// 修改后的Random函数
void Random (){
  if (Frogs == 0) {
    do {
      a = 1 + rand() % 18;
      b = 1 + rand() % 38;
    } while(Field[a][b] != 0);
    Field[a][b] = -1;
    Frogs = 1;
  }
}

// main函数中添加
int main (int argc, char *argv[]) {
  srand(time(0)); // 仅初始化一次随机种子
  setlocale(LC_CTYPE, "");
  // ... 原有代码 ...
}

4. 未处理蛇的尾巴逻辑

原代码仅添加头部,未清除尾巴,导致蛇无限变长最终越界触发段错误。修改movement函数,添加尾巴处理与碰撞检测:

// 修复后的movement函数
void movement(){
  var = getch_noblock();
  var = tolower(var);
  // 防止反向移动撞自己
  if ((var == 'd' && dir != 'a') || 
      (var == 'a' && dir != 'd') || 
      (var == 'w' && dir != 's') || 
      (var == 's' && dir != 'w')) {
    dir = var; 
  }

  // 移动头部坐标
  if (dir == 'd') y++;
  else if (dir == 'a') y--;
  else if (dir == 'w') x--;
  else if (dir == 's') x++;

  // 边界碰撞或撞自己则结束游戏
  if(x < 0 || x >= N || y < 0 || y >= M || Field[x][y] > 0){
    Game = 1;
    return;
  }

  Head++;
  Field[x][y] = Head;

  // 未吃到青蛙则缩短尾巴
  if(Field[x][y] != -1){
    for(int i=0; i<N; i++){
      for(int j=0; j<M; j++){
        if(Field[i][j] == Tail){
          Field[i][j] = 0;
          Tail++;
          break;
        }
      }
    }
  } else {
    Frogs = 0; // 吃到青蛙,允许生成新的
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:57:20