劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(六)

来源:互联网 发布:windows挂载ext3 编辑:程序博客网 时间:2024/05/17 05:02
0. #include <stdio.h>1. #include <time.h>2. #include <ctype.h>3. #include <stdlib.h>4. 5. #define BELL '\a'6. #define DEALER 07. #define PLAYER 18. 9. #define ACELOW 010. #define ACEHIGH 111. 12. int askedForName = 0;13. 14. 15. void dispTitle(void);16. void initCardsScreen(int cards[52],int playerPoints[2],17. int dealerPoints[2], int total[2], 18. int *numCards);19. int dealCard(int * numCards,int cards[52]);20. void dispCard(int cardDrawn,int points[2]);21. void totalIt(int points[2],int tatal[2],int who);22. void dealerGetsCard(int *numCards,int cards[52],23. int dealerPoints[2]);24. void playerGetsCard(int *numCards,int cards[52],25. int playerPoints[2]);26. char getAns(char mesg[]);27. void findWinner(int total[2]);28. 29. main()30. {31.    int numCards;32.    int cards[52],playerPoints[2],dealerPoints[2],total[2];33.    char ans;34.    35.    do 36.    { 37.       initCardsScreen(cards,playerPoints,dealerPoints,total, &numCards);38.       dealerGetsCard(&numCards,cards, dealerPoints);39.       printf("\n");40.       playerGetsCard(&numCards,cards,playerPoints); 41.       playerGetsCard(&numCards,cards,playerPoints);42.       do43.       {44.          ans = getAns("Hit or stand (H/S)?");45.          if ( ans == 'H' )46.          { 47.             playerGetsCard(&numCards,cards,playerPoints);48.          }  49.       }50.       while( ans != 'S' );51.       52.       totalIt(playerPoints,total,PLAYER);53.       do54.       {55.          dealerGetsCard(&numCards,cards,dealerPoints);56.       }57.       while (dealerPoints[ACEHIGH] < 17 );58.       59.       totalIt(dealerPoints,total,DEALER);60.       findWinner(total); 61.       62.       ans = getAns("\nPlay again(Y/N)?");  63.    }64.    while(ans=='Y');65.    66.    return 0;67. }68. 69. void initCardsScreen( int cards[52],int playerPoints[2],70.                       int dealerPoints[2], int total[2], 71.                       int *numCards )72. {73.    int sub,val = 1 ;74.    char firstName[15];75.    *numCards=52;76.    77.    for(sub=0;sub<=51;sub++)78.    {79.       val = (val == 14) ? 1 : val;80.       cards[sub] = val;81.       val++;  82.    }83.    84.    for(sub=0;sub<=1;sub++)85.    { 86.       playerPoints[sub]=dealerPoints[sub]=total[sub]=0;87.    }88.    dispTitle();89.    90.    if (askedForName==0)91.    { 92.       printf("What is your first name?");93.       scanf(" %s",firstName);94.       askedForName=1;95.       printf("Ok, %s,get ready for casino action!\n\n",firstName);96.       getchar();97.    }98.    return;        99. }100. 101. void playerGetsCard(int *numCards,int cards[52],int playerPoints[2])102. {103.    int newCard;104.    newCard = dealCard(numCards, cards);105.    printf("You draw:");106.    dispCard(newCard,playerPoints);107. }108. 109. 110. void dealerGetsCard(int *numCards,int cards[52],int dealerPoints[2])111. {112.    int newCard;113.    newCard = dealCard(numCards,cards);114.    printf("The dealer draws:");115.    dispCard(newCard,dealerPoints);116. }117. 118. int dealCard(int * numCards,int cards[52])119. {120.    int cardDrawn,subDraw;121.    time_t t;122.    srand(time(&t));123.    subDraw = (rand()%(*numCards));124.    cardDrawn = cards[subDraw];125.    cards[subDraw] = cards[*numCards -1];126.    (*numCards)--;127.    return cardDrawn;128. }129. 130. void dispCard(int cardDrawn, int points[2])131. {132.    switch(cardDrawn)133.    {134.       case(11): printf("%s\n","Jack");135.                 points[ACELOW] += 10;136.                 points[ACEHIGH] += 10;137.                 break;138.       case(12): printf("%s\n","Queen");139.                 points[ACELOW] += 10;140.                 points[ACEHIGH] += 10;141.                 break;142.       case(13): printf("%s\n","King");143.                 points[ACELOW] += 10;144.                 points[ACEHIGH] += 10;145.                 break;146.       default : points[ACELOW] += cardDrawn;147.                 if(cardDrawn==1)148.                 { 149.                    printf("%s\n","Ace");150.                    points[ACEHIGH]+= 11;151.                 }152.                 else153.                 {  154.                   points[ACEHIGH]+=cardDrawn;155.                   printf("%d\n",cardDrawn); 156.                 }157.    }158.    return ;159. }160. 161. void totalIt(int points[2],int total[2],int who)162. {163.    if ( (points[ACELOW] == points[ACEHIGH])164.       ||(points[ACEHIGH] < 21 ))165.    { 166.      total[who] = points[ACELOW];167.    }168.    else169.    { 170.        total[who] = points[ACEHIGH];171.    }172.    173.    if (who == PLAYER )174.    {175.       printf("You have a total of %d\n\n", total[PLAYER]);176.    }177.    else178.    {179.        printf("The house stands with a total of %d\n\n", 180.        total[DEALER]);181.    }182.    return;183. }184. 185. void findWinner(int total[2])186. {187.    if ( total[DEALER] ==  21 )188.    {189.        printf("The house wins.\n");190.        return ;191.    }192.    if ( (total[DEALER] > 21) && (total[PLAYER] > 21) )193.    { 194.       printf("%s", "Nobody wins.\n");195.       return ; 196.    }197.    if ((total[DEALER] >= total[PLAYER])&& (total[DEALER] < 21))198.    { 199.       printf("The house wins.\n");200.       return ; 201.    }202.    printf("%s%c","You win!\n",BELL);203.    return;204. }205. 206. char getAns(char mesg[])207. {208.    char ans;209.    printf("%s", mesg);210.    ans = getchar();211.    getchar();212.    return toupper(ans);213. }214. 215. void dispTitle(void)216. {217.    int i = 0 ;218.    while(i<25)219.    { 220.         printf("\n");221.         i++; 222.    }223.    printf("\n\n*Step right up to the Blackjack tables*\n\n");224.    return ;225. }
View Code

  main()函数中player完成抽牌之后,立刻计算了player的点数:  

52.       totalIt(playerPoints,total,PLAYER);

  这个计算结果基于ACE的点数被作为1或11两种可能性,取最好一种作为最后的结果。 

161. void totalIt(int points[2],int total[2],int who)162. {163.    if ( (points[ACELOW] == points[ACEHIGH])164.       ||(points[ACEHIGH]  >  21 ))165.    { 166.      total[who] = points[ACELOW];167.    }168.    else169.    { 170.        total[who] = points[ACEHIGH];171.    }172.    173.    if (who == PLAYER )174.    {175.       printf("You have a total of %d\n\n", total[PLAYER]);176.    }177.    else178.    {179.        printf("The house stands with a total of %d\n\n", 180.        total[DEALER]);181.    }182.    return;183. }

  这个函数让我们得以领略什么叫思路含糊和废话连篇。首先 

163.    if ( (points[ACELOW] == points[ACEHIGH])164.       ||(points[ACEHIGH]  >  21 ))165.    { 166.      total[who] = points[ACELOW];167.    }168.    else169.    { 170.        total[who] = points[ACEHIGH];171.    }

  它的意思是当较高点数超过21点时把较低作为最终的点数,否则把较高点数作为最后的点数。显而易见这可以更简洁地表述为 

      if ( points[ACEHIGH] > 21 )      {          total[who] = points[ACELOW];      }      else      {          total[who] = points[ACEHIGH];      }

原来的代码把“(points[ACELOW] == points[ACEHIGH])||”写出来是思路不清导致的拖泥带水。

更简洁的写法是: 

total[who] = ( points[ACEHIGH] > 21 )? points[ACELOW]: points[ACEHIGH];

   “?:”这个三目运算在这里应用得恰到好处。

有些人对三目运算有一种无名的恐惧,鼓吹所谓“尽量不要用三目运算符”。这是毫无道理的,这种无理源自无知。他们自己不会用刀,于是就骗人骗己地宣称使用木棍强于用刀。 

173.    if (who == PLAYER )174.    {175.       printf("You have a total of %d\n\n", total[PLAYER]);176.    }177.    else178.    {179.        printf("The house stands with a total of %d\n\n", 180.        total[DEALER]);181.    }

  这一段同样拖泥带水,其实它的效果和下面写法没有本质区别: 

   printf( "%s a total of %d\n\n",            who == PLAYER ? "You have" : "The house stands with",           total[who] );

  所以totalIt()函数应改写为 

      void totalIt(int [],int [],int );            void totalIt(int points[],int total[],int who)      {                    total[who] = ( points[ACEHIGH] > 21 )? points[ACELOW]: points[ACEHIGH];                  printf( "%s a total of %d\n\n",                  who == PLAYER ? "You have" : "The house stands with",                 total[who] );            }

  计算了完player的点数之后,按照规则在main()中由dealer继续抽牌(前面已抽过第一张牌)。dealer抽牌的策略是不到17点则继续,据代码作者说现实中的庄家的策略也是如此。

53.       do54.       {55.          dealerGetsCard(&numCards,cards,dealerPoints);56.       }57.       while ( dealerPoints[ACEHIGH] < 17 );

  dealerGetsCard ()函数的功能与playerGetsCard()函数重叠,前面已经提到过,甚至可以说这两个函数都是多余的。

此外这里还有一个更严重的问题,那就是“dealerPoints[ACEHIGH] < 17”这个表达式的逻辑问题。这个表达式要求dealer的点数达到17点或以上时停止抽牌,但问题在于点数有两种计算方法,一种是把Ace作为11点(Soft hand),另一种是把Ace作为1点。“dealerPoints[ACEHIGH] ”的意义是Soft hand点数,但是原作者在对程序的说明中压根就没有明确dealer的Soft hand点数达到或超过17点时停牌,只是泛泛地说了一句“the dealer stands on 17”。按软件工程的说法,这叫需求不清,是比代码错误更加严重的错误。

紧接着,矛盾出现了。 

59.       totalIt(dealerPoints,total,DEALER);

totalIt()函数计算dealer的点数却是按照最好成绩计算的,这就发生了矛盾。比如dealer为15点时又取了一张牌Ace,按照Soft hand规则,dealer的点数是25点,但最后的成绩却是按照硬牌规则为16点,而如果dealer的点数为16点,那么前面他根本就不应该停牌。这是“双重标准”的C语言版。

这里较为合理的写法应该是

       do       {          dealerGetsCard(&numCards,cards,dealerPoints);             totalIt(dealerPoints,total,DEALER);       }       while ( total [DEALER] < 17 );

<script type="text/javascript"><!--google_ad_client = "ca-pub-1944176156128447";/* cnblogs 首页横幅 */google_ad_slot = "5419468456";google_ad_width = 728;google_ad_height = 90;//--></script><script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"></script>
原创粉丝点击