1. 程式人生 > >鬥地主AI演算法——第十一章の被動出牌(5)

鬥地主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)