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

沙与流体元胞自动机模拟:水单元格闪烁及方向控制异常求助

沙与流体元胞自动机SFML实现的问题分析与修复建议

问题概述

基于SFML实现的沙与流体元胞自动机存在两个核心问题:

  • 水单元格(type=2)触达窗口边缘或与其他水单元格碰撞时,单元格及相邻区域出现明显闪烁
  • 水单元格的方向切换功能未达到预期效果

问题根源分析

  1. 闪烁问题

    • 直接在原状态数组上修改单元格类型和状态,同一帧内后续的更新逻辑会读取到已修改的状态,导致单元格状态来回切换
    • 方向碰撞时的dir修改和moved标记逻辑不同步,渲染时出现状态不一致
    • moved状态的重置时机在绘制之后,部分状态残留到下一帧引发逻辑错误
  2. 方向切换失效

    • 水单元格切换方向后未立即执行新方向的移动逻辑,导致方向切换后无动作
    • 实时修改原数组状态,导致当前帧内的状态判断逻辑混乱

修复方案

核心改进:双缓冲状态更新

使用两个数组分别存储当前帧状态和下一帧状态,所有更新逻辑基于当前帧状态计算,结果写入下一帧状态数组,最后将下一帧状态同步到当前帧,彻底避免同一帧内的状态干扰。

其他细节调整

  • 调整moved状态的重置时机,在每一帧更新开始前统一重置
  • 水单元格切换方向后,立即尝试向新方向执行移动逻辑
  • 优化边界判断逻辑,避免越界访问

修正后的完整代码

#include <SFML/Graphics.hpp>
#include <SFML/Main.hpp>
#include <SFML/Window.hpp>
#include <iostream>

using namespace std;

struct Cell {
    int x, y;
    int size;
    int type;
    bool moved = false;
    int dir = -1;
    sf::RectangleShape rect;

    Cell(int cxPos, int cyPos, int cSize, int cType, int cDir = -1) {
        x = cxPos;
        y = cyPos;
        size = cSize;
        type = cType;
        dir = cDir;
        sf::Color color = sf::Color::Black;
        if (type == 1) color = sf::Color(255, 255, 0);
        if (type == 2) color = sf::Color(0, 0, 255);

        rect.setSize(sf::Vector2f(static_cast<float>(size), static_cast<float>(size)));
        rect.setPosition(sf::Vector2f(static_cast<float>(x), static_cast<float>(y)));
        setColor(color);
    }

    void setColor(sf::Color color) {
        rect.setFillColor(color);
    }
};

void updateCell(const Cell& currentCell, vector<vector<Cell>>& nextArray, const vector<vector<Cell>>& currentArray) {
    int px = currentCell.x / currentCell.size;
    int py = currentCell.y / currentCell.size;
    int gridWidth = currentArray.size();
    int gridHeight = currentArray[0].size();

    if (currentCell.type == 1 && py + 1 < gridHeight && !currentCell.moved) {
        int below = currentArray[px][py + 1].type;
        int bottomLeft = (px > 0) ? currentArray[px - 1][py + 1].type : -1;
        int bottomRight = (px + 1 < gridWidth) ? currentArray[px + 1][py + 1].type : -1;

        if (below == 0) {
            nextArray[px][py + 1].type = 1;
            nextArray[px][py].type = 0;
            nextArray[px][py + 1].moved = true;
        } else if (below == 1) {
            if (bottomLeft == 0) {
                nextArray[px][py].type = 0;
                nextArray[px - 1][py + 1].type = 1;
                nextArray[px - 1][py + 1].moved = true;
            } else if (bottomRight == 0) {
                nextArray[px][py].type = 0;
                nextArray[px + 1][py + 1].type = 1;
                nextArray[px + 1][py + 1].moved = true;
            }
        }
    }

    if (currentCell.type == 2 && py + 1 < gridHeight && !currentCell.moved) {
        int below = currentArray[px][py + 1].type;

        if (below == 0) {
            nextArray[px][py + 1].type = 2;
            nextArray[px][py].type = 0;
            nextArray[px][py + 1].moved = true;
            nextArray[px][py + 1].dir = currentCell.dir;
        } else {
            int newDir = currentCell.dir;
            bool canMove = false;

            // 检查当前方向是否合法
            bool dirValid = (newDir > 0) ? (px + newDir < gridWidth) : (px + newDir >= 0);
            if (!dirValid) {
                newDir *= -1;
                dirValid = (newDir > 0) ? (px + newDir < gridWidth) : (px + newDir >= 0);
            }

            if (dirValid) {
                if (currentArray[px + newDir][py].type == 2) {
                    // 与其他水单元格碰撞,交换方向
                    nextArray[px + newDir][py].dir *= -1;
                    nextArray[px][py].dir *= -1;
                    nextArray[px + newDir][py].moved = true;
                    nextArray[px][py].moved = true;
                } else if (currentArray[px + newDir][py].type == 0) {
                    // 向新方向移动
                    nextArray[px + newDir][py].type = 2;
                    nextArray[px][py].type = 0;
                    nextArray[px + newDir][py].moved = true;
                    nextArray[px + newDir][py].dir = newDir;
                    canMove = true;
                }
            }

            // 如果当前方向无法移动,尝试反向
            if (!canMove && dirValid) {
                int reverseDir = newDir * -1;
                bool reverseValid = (reverseDir > 0) ? (px + reverseDir < gridWidth) : (px + reverseDir >= 0);
                if (reverseValid && currentArray[px + reverseDir][py].type == 0) {
                    nextArray[px + reverseDir][py].type = 2;
                    nextArray[px][py].type = 0;
                    nextArray[px + reverseDir][py].moved = true;
                    nextArray[px + reverseDir][py].dir = reverseDir;
                }
            }
        }
    }
}

int main() {
    const int width = 600;
    const int height = 600;
    const int size = 30;
    const int blockSize = width / size;

    sf::RenderWindow window(sf::VideoMode(width, height), "Sand & Fluid CA");
    window.setFramerateLimit(10);

    // 初始化双缓冲数组
    vector<vector<Cell>> currentArray(size, vector<Cell>(size, Cell(0, 0, 0, 0)));
    vector<vector<Cell>> nextArray(size, vector<Cell>(size, Cell(0, 0, 0, 0)));

    for (int x = 0; x < size; x++) {
        for (int y = 0; y < size; y++) {
            currentArray[x][y] = Cell(x * blockSize, y * blockSize, blockSize, 0);
            nextArray[x][y] = currentArray[x][y];
        }
    }

    currentArray[2][2].type = 2;
    nextArray[2][2].type = 2;

    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
            else if (event.type == sf::Event::MouseButtonPressed) {
                sf::Vector2i mousePos = sf::Mouse::getPosition(window);
                int cellType = (event.mouseButton.button == sf::Mouse::Left) ? 1 : 2;

                for (int x = 0; x < size; x++) {
                    for (int y = 0; y < size; y++) {
                        if (currentArray[x][y].rect.getGlobalBounds().contains(static_cast<float>(mousePos.x), static_cast<float>(mousePos.y))) {
                            currentArray[x][y].type = cellType;
                            nextArray[x][y].type = cellType;
                            if (cellType == 2) {
                                currentArray[x][y].dir = -1;
                                nextArray[x][y].dir = -1;
                            }
                        }
                    }
                }
            }
        }

        // 重置所有单元格的moved状态
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                nextArray[x][y].moved = false;
                nextArray[x][y].type = currentArray[x][y].type;
                nextArray[x][y].dir = currentArray[x][y].dir;
            }
        }

        // 更新所有单元格(从下到上处理,保证重力逻辑正确)
        for (int y = size - 1; y >= 0; --y) {
            for (int x = 0; x < size; ++x) {
                updateCell(currentArray[x][y], nextArray, currentArray);
            }
        }

        // 同步双缓冲状态,并更新颜色
        for (int x = 0; x < size; x++) {
            for (int y = 0; y < size; y++) {
                currentArray[x][y] = nextArray[x][y];
                sf::Color color = sf::Color::Black;
                if (currentArray[x][y].type == 1)
                    color = sf::Color(255, 255, 0);
                if (currentArray[x][y].type == 2)
                    color = sf::Color(0, 0, 255);
                currentArray[x][y].setColor(color);
            }
        }

        // 绘制
        window.clear();
        for (int y = 0; y < size; ++y) {
            for (int x = 0; x < size; ++x) {
                window.draw(currentArray[x][y].rect);
            }
        }
        window.display();
    }

    return 0;
}

修复效果说明

  • 双缓冲机制彻底解决了同一帧内状态冲突导致的闪烁问题
  • 优化后的方向切换逻辑,水单元格在碰撞边缘或同类单元格时能正确切换方向并继续移动
  • 调整后的更新顺序和状态重置时机,保证了重力逻辑和流体逻辑的稳定性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 19:17:03