【重構】(續)
牌的表示:
一副牌有52張,可用一整數數組描述。但是由於在游戲過程中牌數在不斷減少,所以用一表示剩余張數的整數和一整數數組共同描述。C99支持一種變量長度數組,但用在這里並沒有什么特別的好處,並不合適。
typedef struct { int cards[52]; int num_cards; } POKER ;
參與者:
共兩位:庄家(計算機)、人(dealer , player )。
描述它們的數據主要就是得分。根據21點游戲的soft hand規則,A(ACE)可以被視為1點,也可以被視為11點,因此至少要有兩個分量來描述參與者。兩種情況下的分數分別用low和high描述。最后的有效分數用score描述。
typedef struct { int score; int low ; int high ; } GAMER ;
game_21()函數需要三個變量:一副牌,兩個游戲者。
void game_21( void ) { POKER poker; GAMER player = { 0 , 0 , 0 } , dealer = { 0 , 0 , 0 } ; //游戲過程 }
游戲開始,首先對pkr初始化:
init_poker( &poker ); void init_poker( POKER * ); void init_poker( POKER *p_pkr ) { int i ; p_pkr->num_cards = sizeof p_pkr->cards / sizeof p_pkr->cards[0] ;//52 for ( i = 0 ; i < p_pkr->num_cards ; i ++ ){ p_pkr->cards[i] = i % 13 + 1; } }
以保證牌由四套1~13點的牌面組成。這里沒寫洗牌的過程,而是在后面用隨機抽牌的方法來模擬牌面分布的隨機性。
p_pkr->num_cards的初值就是52,寫成 sizeof p_pkr->cards / sizeof p_pkr->cards[0] 是當時想到了真正的21點游戲的牌可能是由多副52張牌組成的,這個寫法可能太一本正經了。按照這個想法POKER類型其實應該這樣描述
#define N 1 typedef struct { int cards[52*N]; int num_cards; } POKER ;
不過后來覺得這沒什么意義,因為對於單人游戲沒必要有那么多牌。所以這個想法並沒有貫徹始終。
13是一個Magic Number,不過就這么直接寫也不會有什么問題,因為在整個程序中這個常量只出現一次。沒用宏描述這個常量的另一個原因是實在想不出應該取什么名。
接下來是輪流抽牌的過程,為了模擬“隨機”,在抽牌之前要設置偽隨機數的種子。
#include <time.h> srand( ( unsigned )time(NULL) );
time(NULL)的值就是當前時間。不顯示地進行類型轉換直接用這個值做srand()的實參的寫法也有,但本質上是根據實參、形參賦值規則進行隱式的( unsigned )類型轉換。
按照規則,庄家首先抽一張牌
puts("庄家拿牌:"); getcard( &dealer , dealcard( &poker ) ); int dealcard( POKER * ); void disp( int ); void getcard( GAMER * , int ); int dealcard( POKER *p_pkr ) { int num = rand() % p_pkr->num_cards ; int card = p_pkr->cards[ num ] ; p_pkr->cards[ num ] = p_pkr->cards[ --p_pkr->num_cards ] ; return card; } void getcard( GAMER *p_plr , int card ) { disp( card ); switch ( card ){ case 1 : p_plr->low += 1 ; p_plr->high += 11 ; break ; default : p_plr->low += card ; p_plr->high += card ; break ; case 11: case 12: case 13: p_plr->low += 10 ; p_plr->high += 10 ; break ; } p_plr->score = p_plr->high > 21 ? p_plr->low : p_plr->high; printf("總分:%d\n",p_plr->score); } void disp( int card ) { switch(card){ case 1 :puts("Ace"); return; default :printf("%d\n",card); return; case 11: puts("Jack"); return; case 12: puts("Queen"); return; case 13: puts("King"); return; } }
其中的dealcard()模擬從poker中隨機抽一張牌,然后根據這張牌的點數通過getcard()函數更新dealer的分數。抽牌后調用disp()函數輸出了這張牌的牌面。在dealcard()函數中調用disp()函數顯示牌面也可以。
接下來,游戲者抽牌。
puts("你拿牌:"); getcard( &player , dealcard( &poker ) ); do{ getcard( &player , dealcard( &poker ) ); }while ( again("繼續要牌(Y/N)?") == YES );
由於游戲者第一次抽兩張,這里處理為先抽一張,再加上一個do-while語句。寫到這里發現,詢問游戲者時代繼續抽牌的函數與續文游戲者是否繼續游戲的函數幾乎一致,所以把他們概括為一個函數,並進行了適當修改。
YESNO again( char * ); YESNO again( char *p_message ) { int c; puts( p_message ); c = getchar() ; while ( getchar() != '\n'){ //讀完一行 } if ( c=='y' || c == 'Y' ){ return YES; } return NO; }
接下來,庄家繼續抽牌,終止條件為點數達到17或17以上。
puts("庄家繼續拿牌:"); do{ getcard( &dealer , dealcard( &pkr ) ); }while ( dealer.score < 17 );
最后,宣布勝負:
declare_winner( dealer , player );
這個函數的函數類型聲明及定義如下:
void declare_winner( GAMER , GAMER ); void declare_winner( GAMER dealer , GAMER player ) { if ( dealer.score == 21 ){ puts("你輸了。"); return ; } if ( dealer.score > 21 ){ if( player.score > 21 ){ puts("平局。"); return ; } } if ( dealer.score < 21 ){ if( player.score > 21 ){ puts("你輸了。"); return ; } if( dealer.score >= player.score ){ puts("你輸了。"); return ; } } puts("你贏了!\a"); return; }
下面是完整的代碼,個別地方進行了微小的修飾性改動。
/* 21點游戲:對《寫給大家看的C語言書》附錄B之21點程序的重構 */ #include <stdio.h> #include <stdlib.h> #include <time.h> typedef enum { NO , YES, } YESNO ; typedef struct { int cards[52]; int num_cards; } POKER ; typedef struct { int score; int low ; int high ; } GAMER ; YESNO again( char * ); void game_21( void ); void init_poker( POKER * ); int dealcard( POKER * ); void disp( int ); void getcard( GAMER * , int ); void declare_winner( GAMER , GAMER ); int main( void ) { do{ system("CLS"); game_21(); //一輪游戲 }while ( again( "繼續游戲(Y/N)?" ) == YES ); system("PAUSE"); return 0; } int dealcard( POKER *p_pkr ) { int num = rand() % p_pkr->num_cards ; int card = p_pkr->cards[ num ] ; p_pkr->cards[ num ] = p_pkr->cards[ -- p_pkr->num_cards ] ; return card; } /* 宣布勝利 */ void declare_winner( GAMER dealer , GAMER player ) { if ( dealer.score == 21 ){ puts("你輸了。"); return ; } if ( dealer.score > 21 ){ if( player.score > 21 ){ puts("平局。"); return ; } } if ( dealer.score < 21 ){ if( player.score > 21 ){ puts("你輸了。"); return ; } if( dealer.score >= player.score ){ puts("你輸了。"); return ; } } puts("你贏了!\a"); return; } /* 計算*p_plr獲得card后的分數 */ void getcard( GAMER *p_plr , int card ) { disp( card ); switch ( card ){ case 1 : p_plr->low += 1 ; p_plr->high += 11 ; break ; default : p_plr->low += card ; p_plr->high += card ; break ; case 11: case 12: case 13: p_plr->low += 10 ; p_plr->high += 10 ; break ; } p_plr->score = p_plr->high > 21 ? p_plr->low : p_plr->high; printf("總分:%d\n",p_plr->score); } /* 顯示card牌面 */ void disp( int card ) { switch(card){ case 1 :puts("Ace"); return; default :printf("%d\n",card); return; case 11: puts("Jack"); return; case 12: puts("Queen"); return; case 13: puts("King"); return; } } /* 初始化*p_pkr */ void init_poker( POKER *p_pkr ) { int i ; p_pkr->num_cards = sizeof p_pkr->cards / sizeof p_pkr->cards[0] ;//52 for ( i = 0 ; i < p_pkr->num_cards ; i ++ ){ p_pkr->cards[i] = i % 13 + 1; } } void game_21( void ) { POKER poker; GAMER player = { 0 , 0 , 0 } , dealer = { 0 , 0 , 0 } ; init_poker( &poker ); srand( ( unsigned )time(NULL) ); puts("庄家拿牌:"); //庄家取第一張 getcard( &dealer , dealcard( &poker ) ); puts("\n你拿牌:"); //player抽牌 getcard( &player , dealcard( &poker ) ); do{ getcard( &player , dealcard( &poker ) ); }while ( again("繼續要牌(Y/N)?") == YES ); puts("\n庄家繼續拿牌:"); //庄家繼續抽牌 do{ getcard( &dealer , dealcard( &poker ) ); }while ( dealer.score < 17 ); declare_winner( dealer , player ); } YESNO again( char * p_message ) { int c; puts( p_message ); c = getchar() ; while ( getchar() != '\n'){ //讀完一行 } if ( c=='y' || c == 'Y' ){ return YES; } return NO;}
(全文完)