C语言结构体数组洗牌异常:僵尸人类网格游戏回合顺序问题
嘿,我看到你遇到的问题了——调用洗牌函数后打印Agent ID时大部分都是0,只有零星几个正确的ID。咱们来一步步拆解这个问题,找到根源再解决它。
问题根源分析
先看你传递给shuffle函数的参数:你把整个20x20的网格数组my_world.grid传了进去,同时传入的nagents是实际创建的有效Agent数量(大概40个左右,因为只有10%的概率生成Agent)。但这里有个致命的错误:有效Agent并不是连续存放在网格数组的前nagents个位置,而是随机分散在整个20x20的网格里,剩下的大部分位置都是None类型的Agent(它们的id默认是0)。
你的洗牌函数只会交换数组的前nagents个元素,而这些元素里绝大多数都是None类型的0-ID Agent,真正的有效Agent可能在数组后面的位置根本没被洗牌到,所以打印出来自然都是0,只有少数刚好落在前nagents位置的有效Agent能被显示出来。
另外还有个小问题:你在shuffle函数里又调用了srand(time(NULL)),但main函数里已经初始化过随机数种子了。如果程序运行速度快,time(NULL)返回的秒数相同,会导致随机数序列被重置,洗牌效果变差,但这不是你看到大量0的主要原因。
解决方案
我们需要先把所有**有效Agent(type不为None的)**收集到一个单独的数组里,再对这个数组进行洗牌——这样就能保证我们洗牌的都是真正有意义的Agent,不会混入大量0-ID的空Agent。
步骤1:修改主函数,收集有效Agent
在调用洗牌函数之前,先遍历整个网格,把有效Agent的指针收集到临时数组里(用指针的好处是修改Agent状态会直接同步到原网格,且更高效):
int main() { // ... 之前的代码保持不变 ... // 收集所有有效Agent的指针 AGENT **valid_agents = malloc(nagents * sizeof(AGENT*)); if (!valid_agents) { fprintf(stderr, "内存分配失败!\n"); return 1; } int idx = 0; for (int i = 0; i < WORLD_X; ++i) { for (int j = 0; j < WORLD_Y; ++j) { if (agent_grid[i][j].type != None) { valid_agents[idx++] = &agent_grid[i][j]; } } } // 对有效Agent的指针数组洗牌 shuffle_pointers(valid_agents, nagents); // 后续可遍历洗牌后的数组处理Agent行动 // for (int i = 0; i < nagents; ++i) { // // 执行valid_agents[i]指向的Agent的行动逻辑 // } // 用完临时数组后释放内存 free(valid_agents); // ... 后续代码保持不变 ... }
步骤2:编写针对指针数组的洗牌函数
因为我们现在要洗牌的是Agent指针数组,所以需要一个对应的洗牌函数:
#include "example.h" #include <stdio.h> #include <stdlib.h> void shuffle_pointers(AGENT **agents, unsigned int nagents) { printf("%s\n\n", "------------- Shuffling agent pointers ----------------"); unsigned int i, j; AGENT *temp; // 去掉重复的srand,main里已经初始化过随机数种子了 for (i = nagents - 1; i > 0; i--) { // 标准Fisher-Yates洗牌:从0到i的范围随机选一个索引 j = rand() % (i + 1); temp = agents[i]; agents[i] = agents[j]; agents[j] = temp; } // 打印洗牌后的ID for (i = 0; i < nagents; i++) { printf("\n\t%d", agents[i]->id); } }
步骤3:清理原洗牌函数的冗余代码
如果你还是想用原来的Agent拷贝数组洗牌,记得把原shuffle函数里的srand(time(NULL))删掉,避免重置随机数种子影响随机性。
额外说明
这样修改后,我们洗牌的是所有有效Agent的指针数组,既不会修改原网格的位置(符合你的要求),又能得到正确的随机行动顺序。后续处理每个Agent的行动时,只需要遍历这个洗牌后的指针数组即可,每个指针都指向原网格里的Agent,修改它们的状态会直接同步到网格中。
内容的提问来源于stack exchange,提问作者Rui Martins

