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

来源:互联网 发布:手机麻将 源码 编辑:程序博客网 时间:2024/05/17 02: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

继续走查dispCard()函数:  

20. void dispCard(int cardDrawn,int points[2]);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. }

  dispCard()函数的功能是显示抽到的牌的点数并计算抽牌者目前的总点数。这个函数最主要的毛病是可读性差,原因主要有两点,第一,在switch语句中蹩脚地嵌套了一句if语句,实际上这个switch语句可以这样写: 

void dispCard(int cardDrawn,int points[]);void dispCard(int cardDrawn, int points[]){   switch(cardDrawn)   {      case 11: puts("Jack");               points[ACELOW]  += 10;               points[ACEHIGH] += 10;               break;      case 12: puts("Queen");               points[ACELOW]  += 10;               points[ACEHIGH] += 10;               break;      case 13: puts("King");               points[ACELOW]  += 10;               points[ACEHIGH] += 10;               break;      case 1  :puts("Ace");               points[ACELOW]  += 1 ;               points[ACEHIGH] += 11;               break;                      default :printf("%d\n",cardDrawn);               points[ACELOW]  += cardDrawn;               points[ACEHIGH] += cardDrawn;               break;   }}

显然要好看得多。
第二,就是不应把输出牌面点数与计算点数放在这一个函数中同时完成,应该分为两个函数。 

void dispCard(int cardDrawn);void dispCard(int cardDrawn){   switch(cardDrawn)   {      case 11: puts("Jack");               break;      case 12: puts("Queen");               break;      case 13: puts("King");               break;      case 1  :puts("Ace");               break;                      default :printf("%d\n",cardDrawn);               break;   }}void update(int cardDrawn,int points[]);void update(int cardDrawn, int points[]){   switch(cardDrawn)   {      case 11:       case 12:       case 13: points[ACELOW]  += 10;               points[ACEHIGH] += 10;               break;      case 1  :points[ACELOW]  += 1 ;               points[ACEHIGH] += 11;               break;                      default :points[ACELOW]  += cardDrawn;               points[ACEHIGH] += cardDrawn;               break;   }}

这样代码更简单。
现在回到main()函数,考察 dealerGetsCard(&numCards,cards, dealerPoints); 之后的代码。 

40.       playerGetsCard(&numCards,cards,playerPoints); 41.       playerGetsCard(&numCards,cards,playerPoints);

  这两行的意思是在dealer(计算机)取得一张牌后,player开始抽牌。由于player至少要抽两张牌,所以连续两处调用playerGetsCard()函数。然而查看一下这个函数的定义就会发现: 

24. void playerGetsCard(int *numCards,int cards[52],25. int playerPoints[2]);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. }

  这个函数其实与dealerGetsCard()函数是一样的函数。同样的函数写了两次,无论如何都是很垃圾的写法。

写代码有一条基本原则——要“拽”DRY(Don’t repeat yourself)(参见http://www.cnblogs.com/pmer/archive/2011/07/16/2108436.html)。相同的函数定义了两次就是不够“拽”。

由于这两个函数基本相同,所以很容易合并为一个。它们的差别只在 

105. printf("You draw:");

  

114.    printf("The dealer draws:");

 这两句,可以通过为函数增加一个参数的办法统而为一,也可以把这两句从函数中直接剥离,这时playerGetsCard()和dealerGetsCard()这两个函数本身都没有存在的必要性了。即在main()中直接可调用dispCard()函数: 

int main(void){    /*……*/    do{        /*……*/        printf("The dealer draws:");        dispCard ( dealCard( numCards , cards ) , dealerPoints ) ;                printf("You draw:");        dispCard ( dealCard( numCards , cards ) , playerPoints) ;        /*……*/    }    while( getAns("\nPlay again(Y/N)?") == 'Y' );  /*询问是否继续*/    return 0;}

  main()中的 

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' );

 的作用是让player输入。如果输入为H,则继续抽取一张牌;如果输入为S,则player取牌过程结束;如果输入其他字符,则继续提问。

这段代码的逻辑有些复杂化。如果我写,大概会按下面方式写:

      do      {         char ans;                  ans = getAns("Hit or stand (H/S)?");                  if ( ans == 'H' )             playerGetsCard(&numCards,cards,playerPoints);                  if ( ans == ' S ' )            break;               }      while( 1 );

 并且把它抽象为一个函数。 


<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>
原创粉丝点击