【重构】(续)

牌的表示:
  一副牌有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. 关于django Class-based views的理解

    django是mvt模式,其中v就是这个显示逻辑部分,简单来讲,view函数可以说是接收request,然后处理,返回response的主体函数. 对于一些简单的逻辑关系,可以用直接用函数模式来进行处 ...

  2. Codeforces Beta Round #97 (Div. 1) C. Zero-One 数学

    C. Zero-One 题目连接: http://codeforces.com/contest/135/problem/C Description Little Petya very much lik ...

  3. Java的Spi机制心得

    Java spi : 是Java EE 给服务供应商提供的接口,供应商遵循接口契约提供自己的实现.. 简单来讲就是为某个接口寻找服务实现的机制. 在看JDBC源码当看到DriverManage.get ...

  4. CentOS 7 与老版本CentOS防火墙配置的区别

    一.CentOS 7 以下版本防火墙的配置: 1.开放80,22,8080 端口/sbin/iptables -I INPUT -p tcp --dport 80 -j ACCEPT/sbin/ipt ...

  5. XBee Level Shifting

    http://www.faludi.com/bwsn/xbee-level-shifting/ The XBee communication (RX/TX) pins definitely opera ...

  6. spring mvc接收数组

    (一)前言 对于springmvc接收数组的问题啊,我试验过几次,但是了有时候成功了,有时候失败了,也不知道为啥的,然后现在又要用到了,所以打算具体看看到底怎么回事,但是了我实验成功了顺便找了好多资料 ...

  7. golang 实现轻量web框架

    经常看到很多同学在打算使用go做开发的时候会问用什么http框架比较好.其实go的 http package 非常强大,对于一般的 http rest api 开发,完全可以不用框架就可以实现想要的功 ...

  8. How to check Ubuntu version

    Below you can find some tips on how to check Ubuntu version you are currently running. The first pla ...

  9. MVC控制器传递多个Model到视图,使用ViewData, ViewBag, 部分视图, TempData, ViewModel, Tuple

    从控制器传递多个Model到视图,可以通过ViewData, ViewBag, PartialView, TempData, ViewModel,Tuple等,本篇逐一体验.本篇源码在github. ...

  10. 【docker】docker基础原理,核心技术简介

    关于docker的核心技术,就是以下的三大技术: 1.namespaces [命名空间] 使用linux的命名空间实现的进程间隔离.Docker 容器内部的任意进程都对宿主机器的进程一无所知. 除了进 ...