【重构】(续)

牌的表示:
  一副牌有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;

}

(全文完)

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

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

    . #include <stdio.h> . #include <time.h> . #include <ctype.h> . #include <stdli ...

  2. 如何使用 js 写一个正常人看不懂的无聊代码

    如何使用 js 写一个正常人看不懂的无聊代码 代码质量, 代码可读性, 代码可维护性, clean code WAT js WTF https://www.destroyallsoftware.com ...

  3. 如何写出同事看不懂的Java代码?

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是没更新就是在家忙着带娃的Hydra. 前几天,正巧赶上组里代码review,一下午下来,感觉整个人都血压拉满了.五花八门的代码 ...

  4. 《写给大忙人看的java se 8》笔记

    现在才来了解java8,是不是后知后觉了点? 新的编程技术,个人不喜欢第一时间跟进. 待社区已有实践积淀再切入似乎更划算些? 一点点精明的考虑. 不多说,上代码. //读<写给大忙人看的java ...

  5. 转:HIBERNATE一些_方法_@注解_代码示例---写的非常好

    HIBERNATE一些_方法_@注解_代码示例操作数据库7步骤 : 1 创建一个SessionFactory对象 2 创建Session对象 3 开启事务Transaction : hibernate ...

  6. 【Xamarin挖墙脚系列:代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧(转)】

    正愁如何选择构建项目中的视图呢,现在官方推荐画板 Storybord...但是好像 xib貌似更胜一筹.以前的老棒子总喜欢装吊,用代码写....用代码堆一个HTML页面不知道你们尝试过没有.等页面做出 ...

  7. 【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结

    虽然看过一些Java 8新特性的资料,但是平时很少用到,时间长了就忘了,正好借着Java 9的发布,来总结下一些Java 8中的新特性. 接口中的默认方法和静态方法 先考虑一个问题,如何向Java中的 ...

  8. 代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧

    近期接触了几个刚入门的iOS学习者,他们之中存在一个普遍和困惑和疑问.就是应该怎样制作UI界面.iOS应用是非常重视用户体验的,能够说绝大多数的应用成功与否与交互设计以及UI是否美丽易用有着非常大的关 ...

  9. 匈牙利&&EK算法(写给自己看)

    (写给自己看)匈牙利算法(最大匹配)和KM算法(最佳匹配) 匈牙利算法 思想 不断寻找增广路,每次寻得增广路,交换匹配边和非匹配边,则匹配点数+1 这里增广路含义:交错路,即从未匹配点出发经过未匹配边 ...

随机推荐

  1. 证明自己吧--------Writeup

    原题:http://www.shiyanbar.com/ctf/28 下载一个压缩包,里面有个CrackMe1.exe,查看了下,没有壳. 直接拖到ida去反汇编 一进来就是在main里面,直接F5看 ...

  2. [BalticOI2002]Bicriterial routing

    OJ题号: BZOJ1375.ECNU1468 题目大意: 给定一个无向连通图,每条边有两个权值w1和w2.定义一条路径是优秀的当且仅当没有别的路径满足两个权值的和都比该路径小,求s到t的优秀路径条数 ...

  3. spring boot2集成ES详解

    一:运行环境 JDK:1.8 ES:5.6.4 二:学习内容 如何构建spring-data-elasticsearch环境? 如何实现常用的增删改查? 如何实现对象嵌套也就是1对多这种关系? 三:J ...

  4. sgu 261

    学习了元根的一些知识,哈哈. 总结一下: 几个概念: 阶:对于模数m和整数a,并且gcd(m,a)==1,那么定义a在模m下的阶r为满足ar=1 mod m的最小正整数. 性质1:r in [1,ph ...

  5. Codeforces Round #222 (Div. 1) A. Maze dfs

    A. Maze 题目连接: http://codeforces.com/contest/377/problem/A Description Pavel loves grid mazes. A grid ...

  6. js:深入prototype(上:内存分析)

    /**  * 下面演示了通过原型的创建方式,使用基于原型的创建能够将属性和方法  * 设置为Person专有的,不能通过window来调用.  * 原型是javascript中的一个特殊对象,当一个函 ...

  7. ORA-06502: PL/SQL: 数字或值错误 : 字符串缓冲区太小解决办法

    1.今天写的存储过程在执行过程中,报如下错误. exec PRO_T_008pro_update_add_delete(17,1,1,1,1,45.0,54.0,45.0,45.0,45.0,54.0 ...

  8. systemtap 探针定制

    http://blog.163.com/digoal@126/blog/static/163877040201391123645546/

  9. what is a process?

    A process is a program in execution. A process is more than the program code, which is sometimes kno ...

  10. iOS-实现最简单的画线功能 . 转

    前提:CoreGraphics.framework - (void)viewDidLoad { [super viewDidLoad]; UIImageView *imageView=[[UIImag ...