【重构】(续)

牌的表示:
  一副牌有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. 处理QMenu的triggered信号时遇到的一个问题

    最近,在一个Qt程序中使用QMenu类时,遇到了一个小问题,特记录下.首先,我模仿一下问题出现的场景:假设我在做一个高大上的XX管理系统,比如说:学生信息管理系统.在这个系统中,学生的各项信息(比如: ...

  2. mongoDB系列之(三):mongoDB 分片

    1. monogDB的分片(Sharding) 分片是mongoDB针对TB级别以上的数据量,采用的一种数据存储方式. mongoDB采用将集合进行拆分,然后将拆分的数据均摊到几个mongoDB实例上 ...

  3. JS实现《黑客帝国》落地字母背景

    JS实现<黑客帝国>落地字母背景.这个特别有意思,主要是通过设置字符相关属性.控制循环字母距离顶部的高度值,来达到字母不断循环下落的功能. 恩,还有加上一个随机机制,出现各种大小 各个位置 ...

  4. systemtap 用户态调试2

    [root@localhost ~]# cat user.stpprobe process(@1).function(@2){print_ubacktrace();exit();} session 1 ...

  5. 借助LVS+Keepalived实现负载均衡

    原文地址:http://www.cnblogs.com/edisonchou/p/4281978.html 一.负载均衡:必不可少的基础手段 1.1 找更多的牛来拉车吧 当前大多数的互联网系统都使用了 ...

  6. Xcode GDB 调试

    关于GDB 对于大多数Cocoa程序员来说,最常用的debugger莫过于Xcode自带的调试工具了.而实际上,它正是gdb的一个图形化包装.相对于gdb,图形化带来了很多便利,但同时也缺少了一些重要 ...

  7. ie不支持max-height的解决之法

    .div{ max-height: 100px; _height:expression(this.scrollHeight > 100 ? "100px" : "a ...

  8. 【spring boot】【log4jdbc】使用log4jdbc打印mybatis的sql和Jpa的sql语句运行情况

    在spring boot 中使用mybatis 想看到sql语句的运行情况. 虽然按照 之前说的配置了 logging.level.你的mapper包位置 = debug 但是依旧没有起作用. 所以采 ...

  9. 新浪行情 vb代码

    Sub 新浪行情() Cells.Clear Dim n As Integer, Js As Object Dim i As Integer, j As Integer, m As Integer, ...

  10. Spring+Quartz 集群

    这几天给Spring+Quartz的集群折腾得死去活来,google了无数页总算搞定,记下一些要点备以后使用. 单独的Quartz集群在http://unmi.blogjava.net/有Unmi翻译 ...