斗地主AI算法——第十五章の测试模块
前面几章已经把整个斗地主AI算法工程完成的差不多了,接下来进入整合联调以及模拟测试模块。
测试模块主要任务就是代替服务器给出我们需要的数据。因为我们本来的计划是封装成类库通过服务器调用获取,其调用的接口无非就是叫分、被动出牌、主动出牌。
被动出牌和主动出牌我们已经完成,叫分我们已经实现了权值的获取,只需要在外面加一个区间划分即可:
- /*
- 获取叫分函数
- */
- int LandScore(GameSituation &clsGameSituation, HandCardData &clsHandCardData)
- {
- int SumValue = 0;
- clsHandCardData.uctHandCardValue=get_HandCardValue(clsHandCardData);
- SumValue = clsHandCardData.uctHandCardValue.SumValue;
- cout << "SumValue is :" << SumValue << ",";
- cout << "NeedRound is :" << clsHandCardData.uctHandCardValue.NeedRound << endl;
- if (SumValue<10)
- {
- return 0;
- }
- else if (SumValue < 15)
- {
- return 1;
- }
- else if (SumValue < 20)
- {
- return 2;
- }
- else
- {
- return 3;
- }
- }
接下来就是模拟数据了,首先完成洗牌,即初始化牌值与随机打乱。
- //洗牌
- void InitCards(vector <int> &Cards)
- {
- //先清空Cards
- Cards.clear();
- vector <int> tmpCards;
- int i;
- //大王56,小王52,没有53,54,55号牌
- for (i = 0; i < 53; i++) {
- tmpCards.push_back(i);
- }
- tmpCards.push_back(56);
- //顺序随机打乱
- for (i = tmpCards.size(); i>0; i--) {
- srand(unsigned(time(NULL)));
- // 选中的随机下标
- int index = rand() % i;
- Cards.push_back(tmpCards[index]);
- tmpCards.erase(tmpCards.begin() + index);
- }
- }
同时为了方便测试,我也做了一个指定牌型的函数。
- //洗牌(指定牌型,用于测试)
- void InitCards_Appoint(vector <int> &Cards)
- {
- //先清空Cards
- Cards.clear();
- /***********飞机与炸弹连续拆分逻辑测试**********/
- Cards.push_back(48); Cards.push_back(50); Cards.push_back(49);
- Cards.push_back(44); Cards.push_back(47); Cards.push_back(35);
- Cards.push_back(40); Cards.push_back(46); Cards.push_back(34);
- Cards.push_back(36); Cards.push_back(45); Cards.push_back(33);
- Cards.push_back(23); Cards.push_back(43); Cards.push_back(31);
- Cards.push_back(22); Cards.push_back(42); Cards.push_back(30);
- Cards.push_back(21); Cards.push_back(41); Cards.push_back(29);
- Cards.push_back(19); Cards.push_back(39); Cards.push_back(27);
- Cards.push_back(18); Cards.push_back(38); Cards.push_back(26);
- Cards.push_back(17); Cards.push_back(37); Cards.push_back(25);
- Cards.push_back(15); Cards.push_back(32); Cards.push_back(20);
- Cards.push_back(14); Cards.push_back(28); Cards.push_back(16);
- Cards.push_back(13); Cards.push_back(24); Cards.push_back(12);
- Cards.push_back(11); Cards.push_back(3); Cards.push_back(7);
- Cards.push_back(10); Cards.push_back(2); Cards.push_back(6);
- Cards.push_back(9); Cards.push_back(1); Cards.push_back(5);
- Cards.push_back(8); Cards.push_back(0); Cards.push_back(4);
- Cards.push_back(51); Cards.push_back(52); Cards.push_back(56);
- }
洗完牌就是发牌了,发牌这里我们需要定义一个包含三个人手牌的结构,因为作为正常调用来说我们是不应该有这样的数据的。
- //下发到三名玩家的手牌序列,此数据只用于测试,作为AI时不会获取
- struct ALLCardsList
- {
- vector <int> arrCardsList[3];
- };
然后依次发送到玩家对应的手牌数组里,最后三张为底牌。
- //发牌
- void SendCards(GameSituation & clsGameSituation, ALLCardsList &uctALLCardsList)
- {
- //洗牌
- vector <int> Cards;
- InitCards(Cards);
- //InitCards_Appoint(Cards);
- int i, j, k;
- j = 0;
- for (k = 0; k < 17; k++) {
- for (i = 0; i < 3; i++,j++)
- {
- uctALLCardsList.arrCardsList[i].push_back(Cards[j]);
- }
- }
- //三张底牌
- clsGameSituation.DiPai[0] = Cards[j];
- clsGameSituation.DiPai[1] = Cards[j+1];
- clsGameSituation.DiPai[2] = Cards[j+2];
- return;
- }
再然后就是模拟游戏过程,首先定义游戏全局类,与三名玩家的手牌信息类。调用发牌函数完成发牌环节,可以用手牌信息类里面的PrintAll输出你想要的数据信息。
- GameSituation clsGameSituation;
- ALLCardsList uctALLCardsList;
- //发牌
- SendCards(clsGameSituation, uctALLCardsList);
- HandCardData arrHandCardData[3];
- arrHandCardData[0].color_nHandCardList = uctALLCardsList.arrCardsList[0];
- arrHandCardData[1].color_nHandCardList = uctALLCardsList.arrCardsList[1];
- arrHandCardData[2].color_nHandCardList = uctALLCardsList.arrCardsList[2];
- for (int i = 0; i < 3; i++)
- {
- arrHandCardData[i].Init();
- arrHandCardData[i].nOwnIndex = i;
- }
- cout << "0号玩家牌为:" << endl;
- arrHandCardData[0].PrintAll();
- cout << "1号玩家牌为:" << endl;
- arrHandCardData[1].PrintAll();
- cout << "2号玩家牌为:" << endl;
- arrHandCardData[2].PrintAll();
- cout << "底牌为:" << endl;
- cout << get_CardsName(clsGameSituation.DiPai[0]) << ','
- << get_CardsName(clsGameSituation.DiPai[1]) << ','
- << get_CardsName(clsGameSituation.DiPai[2]) << endl;
- cout << endl;
发完牌后开始叫地主,调用LandScore函数返回其叫的分值,只有比当前已叫的分值更高才可以刷新叫地主记录。若无人叫地主重新开一局,否则将三张底牌给地主,同时刷新地主手牌,且将地主设置成将要出牌的玩家
- for (int i = 0; i < 3; i++)
- {
- int tmpLandScore = LandScore(clsGameSituation, arrHandCardData[i]);
- if (tmpLandScore > clsGameSituation.nNowLandScore)
- {
- clsGameSituation.nNowLandScore = tmpLandScore;
- clsGameSituation.nNowDiZhuID = i;
- }
- }
- if (clsGameSituation.nNowDiZhuID == -1)
- {
- cout << "无人叫地主" << endl;
- return;
- }
- cout << clsGameSituation.nNowDiZhuID << "号玩家是地主,叫分为:" << clsGameSituation.nNowLandScore << endl;
- clsGameSituation.nDiZhuID=clsGameSituation.nNowDiZhuID;
- clsGameSituation.nLandScore =clsGameSituation.nNowLandScore;
- //将三张底牌给地主
- arrHandCardData[clsGameSituation.nDiZhuID].color_nHandCardList.push_back(clsGameSituation.DiPai[0]);
- arrHandCardData[clsGameSituation.nDiZhuID].color_nHandCardList.push_back(clsGameSituation.DiPai[1]);
- arrHandCardData[clsGameSituation.nDiZhuID].color_nHandCardList.push_back(clsGameSituation.DiPai[2]);
- //地主手牌刷新
- arrHandCardData[clsGameSituation.nDiZhuID].Init();
- //出牌玩家ID
- int indexID= clsGameSituation.nDiZhuID;
- cout << endl;
- cout << "0号玩家牌为:" << endl;
- arrHandCardData[0].PrintAll();
- cout << "1号玩家牌为:" << endl;
- arrHandCardData[1].PrintAll();
- cout << "2号玩家牌为:" << endl;
- arrHandCardData[2].PrintAll();
- //当前控手玩家先为地主
- clsGameSituation.nCardDroit = indexID;
接下来就是循环进行出牌了。在游戏全局类里我们设置了一个标志是否结束的变量,可以用于控制循环。出牌时我们只需调用get_PutCardList出牌函数即可。若某个玩家出完牌后手牌为0,则游戏结束。若玩家出过牌,则刷新游戏全局类里面当前牌型信息。
- while (!clsGameSituation.Over)
- {
- get_PutCardList_2(clsGameSituation, arrHandCardData[indexID]);//获取出牌序列
- arrHandCardData[indexID].PutCards();
- cout << indexID << "号玩家出牌:" << endl;
- for (vector<int>::iterator iter = arrHandCardData[indexID].color_nPutCardList.begin();
- iter != arrHandCardData[indexID].color_nPutCardList.end(); iter++)
- cout << get_CardsName(*iter) << (iter == arrHandCardData[indexID].color_nPutCardList.end() - 1 ? '\n' : ',');
- cout << endl;
- if (arrHandCardData[indexID].nHandCardCount == 0)
- {
- clsGameSituation.Over = true;
- if (indexID == clsGameSituation.nDiZhuID)
- {
- cout << "地主" << indexID << "号玩家获胜" << endl;
- }
- else
- {
- for (int i = 0; i < 3; i++) {
- if (i != clsGameSituation.nDiZhuID)
- {
- cout << "农民" << i << "号玩家获胜" << endl;
- }
- }
- }
- }
- if (arrHandCardData[indexID].uctPutCardType.cgType != cgZERO)
- {
- clsGameSituation.nCardDroit = indexID;
- clsGameSituation.uctNowCardGroup = arrHandCardData[indexID].uctPutCardType;
- }
- indexID == 2 ? indexID = 0 : indexID++;
- }
get_PutCardList函数做了一个分支,通过nCardDroit当前控手对象判断是主动出牌还是被动出牌
- /*
- 2.0版本策略 根据场上形势决定当前预打出的手牌——分支处理
- */
- void get_PutCardList_2(GameSituation &clsGameSituation, HandCardData &clsHandCardData)
- {
- if (clsGameSituation.nCardDroit == clsHandCardData.nOwnIndex)
- {
- get_PutCardList_2_unlimit(clsGameSituation, clsHandCardData);
- }
- else
- {
- get_PutCardList_2_limit(clsGameSituation, clsHandCardData);
- }
- return;
- }
完成测试模块后,我们就可以调试程序了。
那么现在我们就可以愉快的玩耍了,下一章我们将观察几次对局情况进行样例的分析。
敬请关注下一章:斗地主AI算法——第十六章の样例分析
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)