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

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

1. #include <time.h>

2. #include <ctype.h>

3. #include <stdlib.h>

4. 

5. #define BELL '\a'

6. #define DEALER 0

7. #define PLAYER 1

8. 

9. #define ACELOW 0

10. #define ACEHIGH 1

11. 

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.       do

43.       {

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.       do

54.       {

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.                 else

153.                 {  

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.    else

169.    { 

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.    else

178.    {

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.                 else

153.                 {  

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.       do

43.       {

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

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

你可能感兴趣的:(C语言)