Poker time 2 (enhanced version)
聲明:
????????本文很長,并且最終不會有完整代碼,僅提供算法;
????????本文知識點中等偏難,但是看懂和學會之后有一定好處;
????????本文絕對不是最優解,并且很多步驟待封裝成函數,這里逐一分析;
????????本文純屬個人刷題方法和總結,如有錯誤,歡迎指正;
? ? ? ? 如果你做的時候有些吃力,真正想學點什么,請耐心看完,看懂。
題目如下:
????????背景:兩個人每人發3張牌(各從一副牌中),每張牌包括花色(紅桃(Heart)>黑桃(Spade)>方塊(Diamond)>梅花(Club))和大小(從小到大依次是:2-10、J、Q、K、A),勝負規則如下:同花順(3張同花色的連牌,先比大小,再比花色,后同)>炸彈(3張相同大小的牌)>連牌(3張不同花色的連牌)>對子(兩張相同大小的牌)>單牌。例如,紅桃QKA>黑桃QKA>梅花567>方塊234>AAA(紅桃、方塊、梅花)>AAA(黑桃、方塊、梅花)>JQK(紅桃、紅桃、方塊)>JQK(黑桃、紅桃、方塊)>AA2(梅花黑桃梅花)>QQJ(紅桃梅花方塊)>JQA(紅桃紅桃紅桃)。注:A23不算連牌。
輸入:A的3張牌(未排序)和B的3張牌(未排序)。
輸出:A的3張牌的排序后的輸出和B的3張牌的排序后的輸出,以及A和B誰獲勝。
分析:
? ? ? ? 這里繼承了上一題,也就是Poker time的部分內容,這里不多贅述那題的做法,如果有需要的話可以私聊我,我視情況出第一題的算法。
? ? ? ? 首先是排序,這里的最終排序取決于取決于牌的大小而不是花色(*);其次是勝利條件的判斷,有明顯的分級,對于每一級內部也有嚴格的先大小后花色的判斷順序(**);再者,順子的QKA是否合理,這里沒有給出解釋,也是留了一手,不然題目的判斷會更惡心(***);最后是讓人最頭疼的10,這個數字在第一題就作為最后一個隱藏示例存雷。
? ? ? ? 不過有10必有‘1’,只需要找到這個1就行。
? ? ? ? 這次編寫,我不知道隱藏示例都是什么,主要是我一次就過了,除了一個本身就是錯誤的示例(凡爾賽)。
算法正文:
零、參數聲明
? ? ? ? 這里給出本文主要的參數
char a[20]; // 這里給20,防一下 10 的炸彈char b[20];gets(a);gets(b);int i=0;int j=0;int flag=1; // 循環進行指標 int flag1=1; // 是否雙贏指標 int flag2=0; // 判斷 A 贏 int flag3=0; // 判斷 B 贏一、建立在排序基礎上的整體數組構造以及參數讀入
? ? ? ? 由于最終需要對牌進行先大小后花色的排序,我這里使用的是二位數組
????????為什么定義成13*4呢?????????例如:HA 就在數組 [0][0]的位置
? ? ? ? 我的思路是:13行存儲牌,4列存儲花色,數字大、花色大的牌前,最后一次遍歷輸出牌序。
? ? ? ? 這里需要將flag的指標置零,如果直接 return 0 也可以。
int As[13][4]={0};int Bs[13][4]={0};?????????于是,可以對數組內讀入參數,這里只給出其中一個字符串的數據讀入
while(a[i]!='\0'&&flag) // A 的數據填充 {if(a[i]==' ') // 跳過空格{i++;continue;}if(a[i]=='H'||a[i]=='S'||a[i]=='D'||a[i]=='C'){int m=0; // 列指標switch(a[i]) // 判斷花色{case 'H':m=0;break;case 'S':m=1;break;case 'D':m=2;break;case 'C':m=3;break;}int n=0; // 行指標//往后是 i+1 因為現在的 i 指向的是花色,往后以為才是牌的內容 // 向二維數組中填充數據 if(isdigit(a[i+1])||a[i+1]=='A'||a[i+1]=='J'||a[i+1]=='Q'||a[i+1]=='K') {if(isdigit(a[i+1])&&a[i+1]!='1') //判斷2~9,小的在下往上填{n=12-(a[i+1]-'0'-2); // 回憶一下 ascii碼 就知道這步在干啥}else if(a[i+1]=='A') // 其他牌的讀入{n=0; }else if(a[i+1]=='K'){n=1;}else if(a[i+1]=='Q'){n=2;}else if(a[i+1]=='J'){n=3;} if(a[i+1]=='1'&&a[i+2]!='\0'&&a[i+2]=='0') // 判斷是不是 10 {n=4;}i+=n==4?3:2; // 結合 i 的指向,如果不是10就跳過兩位,是10就跳過三位}else // 不符合的剔除 {printf("Input Error!\n");flag=0;flag1=0;break; }if(As[n][m]==0) // 如果沒有重復的牌,那就可以在數組中標記這張牌{As[n][m]++;}else // 否則報錯{printf("Input Error!\n");flag=0; flag1=0;break;}}else{printf("Input Error!\n");flag=0;flag1=0;break;}}二、建立在先大小后花色基礎上的排序
? ? ? ? 是插入過程的逆,重新輸出符合要求的字符串
int k=0;for(i=0;i<13;i++){for(j=0;j<4;j++){if(As[i][j]==1) //判斷有沒有這張牌{switch (j) // 以下的選擇與剛才插入的時候剛好相反,屬于反操作{case 0:a[k++]='H';break;case 1:a[k++]='S';break;case 2:a[k++]='D';break;case 3:a[k++]='C';break;} switch (i){case 0:a[k++]='A';break; // 根據牌所在的行數回推牌的內容case 1:a[k++]='K';break;case 2:a[k++]='Q';break;case 3:a[k++]='J';break;case 4:a[k++]='1';a[k++]='0';break;default:a[k++]=14-i+'0'; // 2~9 轉回字符}a[k++]=' ';}elsecontinue;}}a[--k]='\0'; // 數組結尾,最好加上,免得訪問越界三、建立在規則上的初判斷與初步輸贏判定
? ? ? ? 這里初判斷 a 和 b 的牌型,如果牌型不同,直接就可以輸出唯一勝者
????????這里粗返回的 0~4 可以初步判斷勝者,或者仍需比較
? ? ? ? 這里返回的0~4可不是亂來的,后面有用到
? ? ? ? 函數如下:
// 返回差,類似與冒泡排序,前一個與后一個比,根據差調整數組中元素的位置 // 打一下,試一試就會了,這個函數,很容易上手 int cmp(const void*e1,const void*e2) // 比較方式 {return *(int*)e1-*(int*)e2; }int search(int a[13][4]) // 初步判斷牌的類型 {int i=0;int j=0;int c[3]={0}; // 創建新的數組,存儲牌的 行指標 !!!int current=0; // 因為 行指標 標記了牌的大小for(i=0;i<13;i++) // 遍歷查找牌{for(j=0;j<4;j++){if(a[i][j]==1){// 同花順,占據連續的一列中的三個,最易判斷if(a[i+1][j]==1&&a[i+2][j]==1&&i<=10) // 這里i防止越界訪問{return 4;}c[current++]=i;}elsecontinue;}}qsort(c,3,sizeof(int),cmp); // 對牌進行排序,這里使用了 qsort 函數// qsort(數組名,元素個數,元素大小,比較函數)if(c[0]==c[1]&&c[1]==c[2]) // 炸彈(全部相等){return 3;}else if(c[2]-c[1]==1&&c[1]-c[0]==1) // 順子(等差數列){return 2;}else if(c[0]==c[1]||c[1]==c[2]) // 對子{return 1;}return 0; // 啥也不是,只有單牌 }?
?判斷是否有勝者:
if((flag2>flag3||flag2<flag3)&&flag1) // 唯一勝者,且獲勝方式單純 {if(flag2>flag3){printf("Winner is A!\n");}else if(flag2<flag3){printf("Winner is B!\n");}printf("A: %s\n",a); // 打印排序后的牌內容printf("B: %s\n",b);}else if(flag2==flag3&&flag1) // 需要下一步比較?
?四、建立在再次判斷上的函數指針數組及其調用
? ? ? ? 函數指針數組:一個數組(定語),數組內元素是函數指針(狀語)
? ? ? ? 故名思意:根據牌型,選擇不同的函數來判斷最終的勝利,也稱回調函數
int (*pfarr[4])(char c[15],char d[15])={nmsl,wdnm,cnmd,mdzz};// *pfarr[4] 指針數組,運算符優先級決定它先與 [4] 結合成為數組 // (char c[15],char d[15]),參數類型,類似于函數的參數聲明 // {nmsl,wdnm,cnmd,mdzz},函數名,我亂起的,沒別的意思// 總的意思就是,一個數組,里面四個元素,分別指向一個獨立的函數 else if(flag2==flag3&&flag1) // 需要下一步比較 {if(flag2) // 非零 {int (*pfarr[4])(char c[15],char d[15])={nmsl,wdnm,cnmd,mdzz};switch(flag2){case 4: // 這里的調用都是已經排序過的牌的字符串t=pfarr[0](a,b);break; // 函數調用類似于訪問數組的元素case 3: // 不過記得帶上函數的參數調用,就是后面的 (a,b)t=pfarr[1](a,b);break;case 2:t=pfarr[2](a,b);break;case 1:t=pfarr[3](a,b);break;}if(t==0){printf("Winner is A!\n"); }else if(t==1){printf("Winner is B!\n"); }else{printf("Draw!\n");}}else // 都是單牌,比較方法就是第一題的全部內容,不說了{int count=0;for(i=0;i<4&&flag;i++) // 對比兩個數組判斷輸贏 {for(j=0;j<13&&flag;j++){if((As[i][j]==0&&Bs[i][j]==0)){continue;}else if(As[i][j]==1&&Bs[i][j]==1){count++;if(count==3){printf("Draw!\n");goto end;}}else if(As[i][j]>Bs[i][j]){printf("Winner is A!\n");flag=0;break;}else if(As[i][j]<Bs[i][j]){printf("Winner is B!\n");flag=0;break;}}}}五、建立在近似判斷機制上的四個函數
? ? ? ? 剛才我們設定了四個函數,分別是{nmsl,wdnm,cnmd,mdzz},現在需要一一實現他們,達到我們預期的不同情況下的比較效果。
// 同花順 判斷 int nmsl(char a[15],char b[15]) {int i=0;if(strlen(a)==8) // 判斷有沒有萬惡的 10,這里顯然沒有{for(i=0;i<15;i++){if(a[i]==b[i]) //相同跳過{continue;}else{if(a[i]=='H'&&b[i]!='H') // 花色碾壓{return 1;}else if(a[i]!='H'&&b[i]=='H'){return 0;}else if(a[i]=='A'&&b[i]!='A') // 謹記,有A就沒有10,這里是大小碾壓{return 0;}else if(a[i]!='A'&&b[i]=='A'){return 1;}else // 其他牌的大小碾壓{return b[i]-a[i]?1:0;}}}}else // 如果有萬惡的10{for(i=0;i<15;i++){if(a[i]==b[i]){continue;}else{if(a[i]=='H'&&b[i]!='H'){return 1;}else if(a[i]!='H'&&b[i]=='H'){return 0;}else if(a[i]=='1'||b[i]=='1') // 出現了 10 !{if(a[i]=='1'){return b[i]<=57?0:1; // 10 跟另外一張牌比較,這里57,代表字符'9'} // 細想一下,是不是也合理?else{return a[i]<=57?1:0;}}else // 其他牌的大小碾壓{return b[i]-a[i]?1:0;}}}}return 2; }?
????????其余的判斷都是建立在剛才判斷的基礎上的,可以說比較部分幾乎完全一致。
? ? ? ? 這里多說一句,對子的判斷,可以先找到是哪一對,創建一個兩個元素的數組或者字符串,存下來這一個對子中的一張牌,然后跟另一個對子中的一張牌進行 皇城PK 即可。
? ? ? ? 這里不給出余下的三個函數了,有心的同學看懂上面的思路一定信手拈來,不就是三個函數嘛?!何況這里已經給出了最核心的比較算法了。
六、寫在最后的話
? ? ? ? 我不否認出這道題的人有很多沒有考慮到的地方,題目出的不是很嚴謹,但是在現有知識點的使用上,這絕對是綜合的難題(就我的算法而言)
? ? ? ? 如果你說你用一次遍歷,哈希遍歷,等等,那你可以完美的優化這道題的全部算法;
? ? ? ? 但是就我而言,我希望寫可讀性更高的代碼,不是說我不會寫高效代碼,hashmap我也可以寫,但是這只是獨善其身,不是每一個學C的人一開始就可以寫0ms,2Mb的代碼。
? ? ? ? 如果你覺得這很難,那很抱歉,我已經盡我的一切可能,寫最詳細的注釋,寫最簡單的代碼了,誰又能剛開始一看就懂呢?
? ? ? ? 如果你說,這博主怎么這么扣,這也不寫,那也不贅述。
????????不好意思,我都寫了,是時候舉一反三了。
????????
? ? ? ? 最后的最后,碼字不易,要是本文多少學了點東西的友友們,點個贊讓我看一下唄,至少我會覺得我的付出可以讓大家獲益
總結
以上是生活随笔為你收集整理的Poker time 2 (enhanced version)的全部內容,希望文章能夠幫你解決所遇到的問題。