2

我正在开发一款游戏(并且已经问了几个问题),现在我还有一个问题要问你们。

此游戏中的关卡格式设置为 Uint16(我正在使用 SDL)的 tilemap,它们是 tilemapData 结构数组的索引。tilemapData 结构的位之一是 isConductive 位/布尔值。

该位的使用基本上是创建将各种对象连接在一起形成单个“powerNet”的路径。我在下面有一些关于当前方法的代码(它有效,但我会在之后解释为什么我真的讨厌它)

void findSetPoweredObjects(unsigned long x, unsigned long y, powerNetInfo * powerNet) {
  //Look for poweredObjs on this tile and set their powerNet to the given powernet
  for (int i = 0; i < level->numChunks[CHUNKTYPE_POWEREDDEF]; i++)
    if (level->poweredObjects[i]->position[0] == x && level->poweredObjects[i]->position[1] == y)
      level->poweredObjects[i]->powerNet = powerNet, powerNet->objectsInNet++;
}

void recursiveCheckTile(bool * isWalked, powerNetInfo * powerNet, unsigned long x, unsigned long y, tilemapData * levelMap) {
  //If out of bounds, return
  if (x < 0 || y < 0 || x >= level->mapDimensions[0] || y >= level->mapDimensions[1]) return;
  //If tile already walked, return
  if (isWalked[x + (y * level->mapDimensions[0])]) return;
  //If tile is nonconductive, return
  if (!(level->tiles[levelMap->map[x + (y * level->mapDimensions[0])]]->flags & TILETYPE_CONDUCTIVE)) return;

  //Valid tile to check, see if there's a poweredobj on the tile (link it to the net if it is) and check the adjacent tiles.
  isWalked[x + (y * level->mapDimensions[0])] = true;

  findSetPoweredObjects(x,y,powerNet);

  recursiveCheckTile(isWalked, powerNet, x - 1, y, levelMap);
  recursiveCheckTile(isWalked, powerNet, x + 1, y, levelMap);
  recursiveCheckTile(isWalked, powerNet, x, y - 1, levelMap);
  recursiveCheckTile(isWalked, powerNet, x, y + 1, levelMap);
}

bool buildPowerNets(void) {
  //Build the powernets used by the powered objects
  //TODO: Rewrite buildPowerNets() & recursiveCheckTile() to avoid stack overflows and make it easier to backtrace powernets in-game
  bool * isWalked;
  isWalked = new bool[(level->mapDimensions[0] * level->mapDimensions[1])];
  unsigned long x, y;
  tilemapData * levelMap = level->layers[level->activeMap];
  for (y = 0; y < level->mapDimensions[1]; y++) {
    for (x = 0; x < level->mapDimensions[0]; x++) {
      if (isWalked[x + (y * level->mapDimensions[0])]) continue;
      isWalked[x + (y * level->mapDimensions[0])] = true;
      if (level->tiles[levelMap->map[x + (y * level->mapDimensions[0])]]->flags & TILETYPE_CONDUCTIVE) {
        //it's conductive, find out what it's connected to.

        //But first, create a new powernet
        powerNetInfo * powerNet = new powerNetInfo;
        powerNet->objectsInNet = 0;
        powerNet->producerId = -1;
        powerNet->supplyType = POWER_OFF;
        powerNet->prevSupplyType = POWER_OFF;
        powerNet->powerFor = 0;

        //Find adjacent tiles to this one, add them to it's powernet, and then mark them walked.  Then repeat until the net is done.
        recursiveCheckTile(isWalked, powerNet, x, y, levelMap);
      }
    }
  }
  delete isWalked;
  for (int i = 0; i < level->numChunks[CHUNKTYPE_POWEREDDEF]; i++)
      if (level->poweredObjects[i]->powerNet == NULL) return false;
  return true;
}

请注意,返回 false 意味着函数失败(在这种情况下,它没有正确链接所有对象)。

我担心的是,由于堆栈溢出,在更复杂的地图上行走导电瓷砖的功能会完全失败。对于如何使用这些功能降低这种风险有什么想法?如果需要,我可以提供有关使用的结构的更多信息。

我考虑过修改代码,以便recursiveCheckTile仅在到达交汇点时进行递归调用,否则只会交互地遵循其所在的导电路径,但这似乎仍然只是部分解决方案,因为我无法提前知道路径可能是多么扭曲或分支。

如果它有所作为,速度在这里完全不重要,因为这个函数只在地图被使用前处理时运行一次,所以使用一点额外的时间不会有什么坏处。

4

2 回答 2

5

洪水填充

看起来您基本上是在对网格进行洪水填充。您可以通过使用需要检查的队列或一堆正方形来消除递归。有关伪代码,请参阅 Wikipedia 文章的“替代实现”部分。

自己维护队列/堆栈的好处是,当您访问它们时,您将从列表中删除方块,而在递归解决方案中,即使您访问了它们,方块仍保留在堆栈中。

这是适应您的问题的 Wikipedia 文章中的“简单”替代实现:

1. Set Q to the empty queue.
2. Add node to the end of Q.
3. While Q is not empty: 
4.     Set n equal to the first element of Q
5.     Remove first element from Q
6.     If n has already been visited:
7.         Go back to step 3.
8.     Mark n as visited.
9.     Add the node to the west to the end of Q.
10.    Add the node to the east to the end of Q.
11.    Add the node to the north to the end of Q.
12.    Add the node to the south to the end of Q.
13. Return.

请注意,您可以为此使用堆栈或队列,两者都可以。以下是一些很酷且令人着迷的动画,在视觉上显示了差异:

基于队列的洪水填充

用队列填满洪水

基于堆栈的洪水填充

用堆栈填充洪水

连接组件标签

如果您最终在同一电网上拥有多个电网,您可能还会发现连接组件标签页面很有趣。它基本上可以帮助您确定是否有多个断开的电源网,并且当您这样做时,它会告诉您每个方块属于哪一个。

连通分量标注示例

于 2009-07-24T03:44:51.937 回答
1

您可以迭代地重写此函数。

这样想:您隐式使用调用堆栈作为搜索算法的路径堆栈。每次您调用时,recursiveCheckTile您都会将一个节点推送到该堆栈上。但是,调用堆栈相对较小,因此您很快就会将其炸毁。

您需要明确管理路径堆栈。不要为四个相邻节点调用递归函数,而是将一个节点压入这个显式堆栈。您的算法将如下所示(伪):

add starting node to stack

while( nodes on stack )
{
    pop node
    if( node is conductive )
    {
        add node to powerSet
        push 4 adjoining nodes onto stack
    }
}

这将产生相同的遍历(深度优先),但您的堆栈将是显式的,因此您可以为其分配大量内存。

于 2009-07-24T03:38:29.767 回答