【重构】(续)

牌的表示:
  一副牌有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. 【洛谷】2602: [ZJOI2010]数字计数【数位DP】

    P2602 [ZJOI2010]数字计数 题目描述 给定两个正整数a和b,求在[a,b]中的所有整数中,每个数码(digit)各出现了多少次. 输入输出格式 输入格式: 输入文件中仅包含一行两个整数a ...

  2. Codeforces Round #350 (Div. 2) A. Holidays 水题

    A. Holidays 题目连接: http://www.codeforces.com/contest/670/problem/A Description On the planet Mars a y ...

  3. Window 下安装

    Window 下安装 下载地址:https://github.com/MSOpenTech/redis/releases Redis 支持 32 位和 64 位.这个需要根据你系统平台的实际情况选择, ...

  4. 【Go入门教程6】struct类型(struct的匿名字段)

    struct Go语言中,也和C或者其他语言一样,我们可以声明新的类型,作为其它类型的属性或字段的容器.例如,我们可以创建一个自定义类型person代表一个人的实体.这个实体拥有属性:姓名和年龄.这样 ...

  5. 使用jQuery异步传递Model到控制器方法,并异步返回错误信息

    需要通过jquery传递到控制器方法的Model为: public class Person { public string Name { get; set; } public int Age { g ...

  6. Android Activity的四种LaunchMode!!!

    本文转自: http://marshal.easymorse.com/archives/2950. 写的非常好,分享给大家!!! 在多Activity开发中,有可能是自己应用之间的Activity跳转 ...

  7. .NET:如何实现 “热插拔”?

    背景 如果某个“功能”需要动态更新?这种动态更新,可能是需求驱动的,也可能是为了修改 BUG,面对这种场景,如何实现“热插拔”呢?先解释一下“热插拔”:在系统运行过程动态替换某些功能,不用重启系统进程 ...

  8. Easing圆环动画

    Easing圆环动画 效果 源码 https://github.com/YouXianMing/Animations // // CircleView.h // YXMWeather // // Cr ...

  9. hue耗流量优化

    ps: 使用的hue版本为 hue-3.10.0 一.[jobbrowser刷流量] 基本一分钟刷新一次,执行GET /jobbrowser/ [17/Apr/2017 14:46:26 +0800] ...

  10. D - I Think I Need a Houseboat(1.3.1)

    Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Submit Status Descr ...