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

ncurses+C++游戏星号偶现生成于障碍物内的问题排查与方案咨询

问题描述

我用ncurses库和C++开发了一款简易收集类游戏:玩家操控黄色方块在棋盘内移动,尽可能收集“*”,需避开障碍物与边界。初始生成100个星号,每收集一个便随机生成新的星号。

目前遇到的问题:生成星号前会手动检查障碍物坐标,多数情况有效,但偶尔仍会出现星号落在障碍物内的情况。想排查是否存在逻辑漏洞、操作错误,并寻求替代实现方案。

障碍物定义代码

mainwin = initscr();
obscatle=subwin(mainwin,7,2,1,6);
obscatle2=subwin(mainwin,10,2,8,20);
obscatle3=subwin(mainwin,2,2,17,50);
obscatle4=subwin(mainwin,6,2,25,70);
obscatle5=subwin(mainwin,12,2,10,90);
obscatle6=subwin(mainwin,20,2,8,110);
obscatle7=subwin(mainwin,22,2,4,130);
obscatle8=subwin(mainwin,17,2,18,150);

星号生成相关函数

// 游戏初始化时生成随机位置的星号
vector<pair<int,int>> generate_asterisk(int numberOfLines, int numberOfColumns) {
    vector<pair<int,int>> coordinates;
    srand(time(0)); 
    while(coordinates.size() < 500) {
        int x = (rand() % (COLS - 3)) + 1;
        int y = (rand() % (LINES - 5)) +1;

        // 检查坐标是否在障碍物区域,是则重新生成
        while((y >= 1 && y <= 7 && (x >= 5 && x <= 9)) ||
               (y >= 8 && y <= 17 && (x >= 19 && x <= 23)) ||
               (y >= 17 && y <= 18 && (x >= 49 && x <= 53)) ||
               (y >= 25 && y <= 30 && (x >= 69 && x <= 73)) ||
               (y >= 10 && y <= 21 && (x >= 89 && x <= 93)) ||
               (y >= 8 && y <= 27 && (x >= 109 && x <= 114)) ||
               (y >= 4 && y <= 25 && (x >= 129 && x <= 134)) ||
               (y >= 18 && y <= 34 && (x >= 149 && x <= 154))) {
            x = (rand() % (COLS - 3)) + 1;
            y = (rand() % (LINES - 5)) + 1;
        }

        pair<int,int> coord = make_pair(x,y);
        if(find(coordinates.begin(), coordinates.end(), coord) == coordinates.end()) {
            coordinates.push_back(coord);
        }
    }
    return coordinates;
}

// 收集星号后生成新的星号
void spawnAsterisk(WINDOW* win, int x_max, int y_max, std::vector<std::pair<int, int>>& asterisk_coords)
{
    // 生成新坐标
    int new_x = (rand() % (COLS - 3)) + 1;
    int new_y = (rand() % (LINES - 5)) +1;

    // 检查是否在障碍物区域,是则重新生成
    while((new_y >= 1 && new_y <= 7 && (new_x >= 5 && new_x <= 9)) ||
           (new_y >= 8 && new_y <= 17 && (new_x >= 19 && new_x <= 23)) ||
           (new_y >= 17 && new_y <= 18 && (new_x >= 49 && new_x <= 53)) ||
           (new_y >= 25 && new_y <= 30 && (new_x >= 69 && new_x <= 73)) ||
           (new_y >= 10 && new_y <= 21 && (new_x >= 89 && new_x <= 93)) ||
           (new_y >= 8 && new_y <= 27 && (new_x >= 109 && new_x <= 114)) ||
           (new_y >= 4 && new_y <= 25 && (new_x >= 129 && new_x <= 134)) ||
           (new_y >= 18 && new_y <= 34 && (new_x >= 149 && new_x <= 154))) {
        new_x = (rand() % (COLS - 3)) + 1;
        new_y = (rand() % (LINES - 5)) + 1;
    }

    // 检查是否和已有星号坐标冲突
    bool conflict = false;
    for (const auto& coord : asterisk_coords)
    {
        if (coord.first == new_x && coord.second == new_y)
        {
            conflict = true;
            break;
        }
    }

    // 冲突则重新生成,直到无冲突
    while (conflict)
    {
        new_x = rand() % x_max;
        new_y = rand() % y_max;

        conflict = false;
        for (const auto& coord : asterisk_coords)
        {
            if (coord.first == new_x && coord.second == new_y)
            {
                conflict = true;
                break;
            }
        }
    }

    asterisk_coords.push_back(std::make_pair(new_x, new_y));

    mvwprintw(win, new_y, new_x, "*");
    wrefresh(win);
}
问题分析与解决方案

逻辑漏洞与操作错误

  1. spawnAsterisk函数的二次生成未检查障碍物
    这是星号偶尔出现在障碍物里的核心原因:第一次生成坐标时做了障碍物检查,但如果发现和已有星号冲突进入while(conflict)循环后,新生成的new_x和new_y只检查了星号冲突,完全没做障碍物过滤——如果此时生成的坐标刚好落在障碍物区域,就会直接被添加进去。

  2. 手动坐标范围与实际障碍物不匹配
    以第一个障碍物为例:subwin(mainwin,7,2,1,6)的参数是窗口, 行数, 列数, 起始y, 起始x,实际覆盖范围是y=1到7,x=6到7。但你手动检查的范围是y>=1 && y<=7 && x>=5 && x<=9,x的范围比实际障碍物宽了不少。虽然这不会导致星号进障碍物,但属于冗余检查,若其他障碍物的手动范围计算错误(比如起始/结束值偏差),就会出现漏检。

  3. rand()初始化重复调用
    generate_asterisk里调用了srand(time(0)),如果游戏中多次调用这个函数(比如重启游戏),可能因为时间戳相同导致随机序列重复。建议把srand(time(0))放在程序启动入口处,只调用一次。

替代实现方案

方案1:修复现有代码漏洞

把障碍物检查逻辑封装成独立函数,在所有生成坐标的环节都调用,包括冲突后的二次生成:

// 封装障碍物检查函数,修正坐标范围为实际障碍物覆盖区域
bool isInObstacle(int x, int y) {
    return (y >= 1 && y <= 7 && x >= 6 && x <= 7) ||
           (y >= 8 && y <= 17 && x >= 20 && x <= 21) ||
           (y >= 17 && y <= 18 && x >= 50 && x <= 51) ||
           (y >= 25 && y <= 30 && x >= 70 && x <= 71) ||
           (y >= 10 && y <= 21 && x >= 90 && x <= 91) ||
           (y >= 8 && y <= 27 && x >= 110 && x <= 111) ||
           (y >= 4 && y <= 25 && x >= 130 && x <= 131) ||
           (y >= 18 && y <= 34 && x >= 150 && x <= 151);
}

// 修改spawnAsterisk的冲突循环部分
while (conflict || isInObstacle(new_x, new_y))
{
    new_x = (rand() % (COLS - 3)) + 1;
    new_y = (rand() % (LINES - 5)) + 1;

    // 先确保不在障碍物内
    while (isInObstacle(new_x, new_y)) {
        new_x = (rand() % (COLS - 3)) + 1;
        new_y = (rand() % (LINES - 5)) + 1;
    }

    // 再检查星号冲突
    conflict = false;
    for (const auto& coord : asterisk_coords)
    {
        if (coord.first == new_x && coord.second == new_y)
        {
            conflict = true;
            break;
        }
    }
}

方案2:预先生成所有合法坐标,随机选取

  1. 程序启动时,遍历整个游戏区域,过滤掉障碍物和边界,把合法坐标存入vector<pair<int,int>> valid_coords。
  2. 生成星号时直接从valid_coords中随机选取,选完后移除该坐标避免重复;合法坐标用完时重新初始化。

这种方式完全避免手动检查的错误,逻辑更清晰:

vector<pair<int,int>> valid_coords;

// 初始化合法坐标
void initValidCoords() {
    valid_coords.clear();
    for (int y = 1; y <= LINES -5; y++) {
        for (int x = 1; x <= COLS -3; x++) {
            if (!isInObstacle(x, y)) { // 使用上面封装的检查函数
                valid_coords.emplace_back(x, y);
            }
        }
    }
    // 打乱顺序保证随机性
    random_shuffle(valid_coords.begin(), valid_coords.end());
}

// 修改初始化星号生成函数
vector<pair<int,int>> generate_asterisk(int count) {
    vector<pair<int,int>> coords;
    if (valid_coords.size() < count) {
        initValidCoords(); // 合法坐标不足时重新生成
    }
    for (int i = 0; i < count; i++) {
        coords.push_back(valid_coords.back());
        valid_coords.pop_back();
    }
    return coords;
}

// 修改收集后生成星号的函数
void spawnAsterisk(WINDOW* win, std::vector<std::pair<int, int>>& asterisk_coords)
{
    if (valid_coords.empty()) {
        initValidCoords();
    }
    auto new_coord = valid_coords.back();
    valid_coords.pop_back();

    // 极端情况冲突时重新初始化
    bool conflict = false;
    for (const auto& coord : asterisk_coords) {
        if (coord == new_coord) {
            conflict = true;
            break;
        }
    }
    if (conflict) {
        initValidCoords();
        new_coord = valid_coords.back();
        valid_coords.pop_back();
    }

    asterisk_coords.push_back(new_coord);
    mvwprintw(win, new_coord.second, new_coord.first, "*");
    wrefresh(win);
}

方案3:动态检查障碍物字符

如果障碍物是通过特定字符绘制的(比如'#'),可以用ncurses的mvwinch直接获取坐标处的字符,判断是否为障碍物,无需手动维护坐标范围:

bool isObstacleChar(int x, int y) {
    chtype c = mvwinch(mainwin, y, x);
    return (c == '#' || c == ACS_CKBOARD); // 根据实际障碍物字符修改
}

之后生成坐标时调用这个函数替代手动范围检查,障碍物位置或大小修改时无需同步更新检查逻辑,大幅降低出错概率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 05:47:02