斗地主AI算法——第十一章の被动出牌(5)
本章是被动出牌的最后一章,截止目前,我们已经解决了大部分牌型。只剩下飞机和炸弹了。
飞机无疑是最复杂的类型,他等于顺子和三带的结合体,但又增加了很多难度。
根据上一章的算法,我们可以大概想到,若是带出去一张我就加一个循环,若是带出去两张我就加俩循环,但是这个飞机长度不一致,带出去的牌个数也就不一致,这TM怎么加啊!!我一开始的想法是外置一个全排列函数,给定count个数,然后列举所有的方案去改变value_aHandCardList数组再筛选出最优解。但这样做有坏处,第一,这个外置函数非常的麻烦,因为他全排列的全集和子集个数都不确定。第二,影响了程序分支一致性,别的模块都差不多,就飞机这里搞特殊化不太好。第三,不一定安全,因为这种做法意味着我又要多了一个可以影响value_aHandCardList的模块,我并不希望这样。
所以思考了很久,我觉得宁愿多做几个分支,毕竟飞机的个数还是可控的,就2-4。虽然这种代码写起来跟看起来都很傻逼,但是也没有办法。。。
飞机带单:
- //暂存最佳的价值
- HandCardValue BestHandCardValue = get_HandCardValue(clsHandCardData);
- //我们认为不出牌的话会让对手一个轮次,即加一轮(权值减少7)便于后续的对比参考。
- BestHandCardValue.NeedRound += 1;
- //暂存最佳的牌号
- int BestMaxCard = 0;
- //是否出牌的标志
- bool PutCards = false;
- //验证顺子的标志
- int prov = 0;
- //顺子起点
- int start_i = 0;
- //顺子终点
- int end_i = 0;
- //顺子长度
- int length = clsGameSituation.uctNowCardGroup.nCount / 4;
- int tmp_1 = 0;
- int tmp_2 = 0;
- int tmp_3 = 0;
- int tmp_4 = 0;
- //2与王不参与顺子,从当前已打出的顺子最小牌值+1开始遍历
- for (int i = clsGameSituation.uctNowCardGroup.nMaxCard - length + 2; i < 15; i++)
- {
- if (clsHandCardData.value_aHandCardList[i] > 2)
- {
- prov++;
- }
- else
- {
- prov = 0;
- }
- if (prov >= length)
- {
- end_i = i;
- start_i = i - length + 1;
- for (int j = start_i; j <= end_i; j++)
- {
- clsHandCardData.value_aHandCardList[j] -= 3;
- }
- clsHandCardData.nHandCardCount -= clsGameSituation.uctNowCardGroup.nCount;
- /*本来想做全排列选取带出的牌然后枚举出最高价值的,但考虑到当飞机长度也就是在2-4之间
- 所以干脆做三个分支处理算了*/
- //为两连飞机
- if (length == 2)
- {
- for (int j = 3; j < 18; j++)
- {
- if (clsHandCardData.value_aHandCardList[j] > 0)
- {
- clsHandCardData.value_aHandCardList[j] -= 1;
- for (int k = 3; k < 18; k++)
- {
- if (clsHandCardData.value_aHandCardList[k] > 0)
- {
- clsHandCardData.value_aHandCardList[k] -= 1;
- HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
- clsHandCardData.value_aHandCardList[k] += 1;
- //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
- if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
- {
- BestHandCardValue = tmpHandCardValue;
- BestMaxCard = end_i;
- tmp_1 = j;
- tmp_2 = k;
- PutCards = true;
- }
- }
- }
- clsHandCardData.value_aHandCardList[j] += 1;
- }
- }
- }
- //为三连飞机
- if (length == 3)
- {
- for (int j = 3; j < 18; j++)
- {
- if (clsHandCardData.value_aHandCardList[j] > 0)
- {
- clsHandCardData.value_aHandCardList[j] -= 1;
- for (int k = 3; k < 18; k++)
- {
- if (clsHandCardData.value_aHandCardList[k] > 0)
- {
- clsHandCardData.value_aHandCardList[k] -= 1;
- for (int l = 3; l < 18; l++)
- {
- if (clsHandCardData.value_aHandCardList[l] > 0)
- {
- clsHandCardData.value_aHandCardList[l] -= 1;
- HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
- //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
- if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
- {
- BestHandCardValue = tmpHandCardValue;
- BestMaxCard = end_i;
- tmp_1 = j;
- tmp_2 = k;
- tmp_3 = l;
- PutCards = true;
- }
- clsHandCardData.value_aHandCardList[l] += 1;
- }
- }
- clsHandCardData.value_aHandCardList[k] += 1;
- }
- }
- clsHandCardData.value_aHandCardList[j] += 1;
- }
- }
- }
- //为四连飞机
- if (length == 4)
- {
- for (int j = 3; j < 18; j++)
- {
- if (clsHandCardData.value_aHandCardList[j] > 0)
- {
- clsHandCardData.value_aHandCardList[j] -= 1;
- for (int k = 3; k < 18; k++)
- {
- if (clsHandCardData.value_aHandCardList[k] > 0)
- {
- clsHandCardData.value_aHandCardList[k] -= 1;
- for (int l = 3; l < 18; l++)
- {
- if (clsHandCardData.value_aHandCardList[l] > 0)
- {
- clsHandCardData.value_aHandCardList[l] -= 1;
- for (int m = 3; m < 18; m++)
- {
- if (clsHandCardData.value_aHandCardList[m] > 0)
- {
- clsHandCardData.value_aHandCardList[m] -= 1;
- HandCardValue tmpHandCardValue = get_HandCardValue(clsHandCardData);
- //选取总权值-轮次*7值最高的策略 因为我们认为剩余的手牌需要n次控手的机会才能出完,若轮次牌型很大(如炸弹) 则其-7的价值也会为正
- if ((BestHandCardValue.SumValue - (BestHandCardValue.NeedRound * 7)) <= (tmpHandCardValue.SumValue - (tmpHandCardValue.NeedRound * 7)))
- {
- BestHandCardValue = tmpHandCardValue;
- BestMaxCard = end_i;
- tmp_1 = j;
- tmp_2 = k;
- tmp_3 = l;
- tmp_4 = m;
- PutCards = true;
- }
- clsHandCardData.value_aHandCardList[m] += 1;
- }
- }
- clsHandCardData.value_aHandCardList[l] += 1;
- }
- }
- clsHandCardData.value_aHandCardList[k] += 1;
- }
- }
- clsHandCardData.value_aHandCardList[j] += 1;
- }
- }
- }
- for (int j = start_i; j <= end_i; j++)
- {
- clsHandCardData.value_aHandCardList[j] += 3;
- }
- clsHandCardData.nHandCardCount += clsGameSituation.uctNowCardGroup.nCount;
- }
- }
- if (PutCards)
- {
- for (int j = start_i; j <= end_i; j++)
- {
- clsHandCardData.value_nPutCardList.push_back(j);
- clsHandCardData.value_nPutCardList.push_back(j);
- clsHandCardData.value_nPutCardList.push_back(j);
- }
- if (length == 2)
- {
- clsHandCardData.value_nPutCardList.push_back(tmp_1);
- clsHandCardData.value_nPutCardList.push_back(tmp_2);
- }
- if (length == 3)
- {
- clsHandCardData.value_nPutCardList.push_back(tmp_1);
- clsHandCardData.value_nPutCardList.push_back(tmp_2);
- clsHandCardData.value_nPutCardList.push_back(tmp_3);
- }
- if (length == 4)
- {
- clsHandCardData.value_nPutCardList.push_back(tmp_1);
- clsHandCardData.value_nPutCardList.push_back(tmp_2);
- clsHandCardData.value_nPutCardList.push_back(tmp_3);
- clsHandCardData.value_nPutCardList.push_back(tmp_4);
- }
- clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgTHREE_TAKE_ONE_LINE, BestMaxCard, clsGameSituation.uctNowCardGroup.nCount);
- return;
- }
大家可以看到我回溯的处理方式和之前的不一样了,因为飞机类型很有可能把对牌当成两个单牌带出,甚至可以拆炸弹。所以每个循环内当确定了一个点就先处理value_aHandCardList状态,这样也相对安全,上一章中在四带二环节我也有提到过这方面。
飞机带对类似,而且这里是被动出牌,所以不存在4连飞机的情况,因为4连飞机带对的话就有20张牌了。只考虑2连和3连就可以了。
最后再说一下炸弹,这个炸弹就厉害了,我给的策略就是————————————
直接炸丫的!不要怂!!
- else if (clsGameSituation.uctNowCardGroup.cgType == cgBOMB_CARD)
- {
- //更大的炸弹——这里直接炸,不考虑拆分后果。因为信仰。
- for (int i = clsGameSituation.uctNowCardGroup.nMaxCard + 1; i < 16; i++)
- {
- if (clsHandCardData.value_aHandCardList[i] == 4)
- {
- clsHandCardData.value_nPutCardList.push_back(i);
- clsHandCardData.value_nPutCardList.push_back(i);
- clsHandCardData.value_nPutCardList.push_back(i);
- clsHandCardData.value_nPutCardList.push_back(i);
- clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgBOMB_CARD, i, 4);
- return;
- }
- }
- //王炸
- if (clsHandCardData.value_aHandCardList[17] > 0 && clsHandCardData.value_aHandCardList[16] > 0)
- {
- clsHandCardData.value_nPutCardList.push_back(17);
- clsHandCardData.value_nPutCardList.push_back(16);
- clsHandCardData.uctPutCardType = clsGameSituation.uctNowCardGroup = get_GroupData(cgKING_CARD, 17, 2);
- return;
- }
- //管不上
- clsHandCardData.uctPutCardType = get_GroupData(cgZERO, 0, 0);
- return;
- }
当然也可以改成考虑拆分后果什么的,或者如果你手上有多个炸弹是否对比一下出那个接下来更好 等等逻辑。
不过对于我来说,你都有俩炸弹了,还怕什么,肯定都是要炸的!宁输不拆!就是这么浪!
好了至此被动出牌模块就全部写完了,从下一章开始,我们讲主动出牌。
敬请关注下一章:斗地主AI算法——第十二章の主动出牌(1)
【推荐】国内首个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)