鬥地主AI演算法——第十一章の被動出牌(5)
阿新 • • 發佈:2019-01-28
本章是被動出牌的最後一章,截止目前,我們已經解決了大部分牌型。只剩下飛機和炸彈了。
飛機無疑是最複雜的型別,他等於順子和三帶的結合體,但又增加了很多難度。
根據上一章的演算法,我們可以大概想到,若是帶出去一張我就加一個迴圈,若是帶出去兩張我就加倆迴圈,但是這個飛機長度不一致,帶出去的牌個數也就不一致,這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)