劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(八)
【重构】(续)
牌的表示:
一副牌有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点程序(八)的更多相关文章
- 劣质代码评析——《写给大家看的C语言书(第2版)》附录B之21点程序(六)
. #include <stdio.h> . #include <time.h> . #include <ctype.h> . #include <stdli ...
- 如何使用 js 写一个正常人看不懂的无聊代码
如何使用 js 写一个正常人看不懂的无聊代码 代码质量, 代码可读性, 代码可维护性, clean code WAT js WTF https://www.destroyallsoftware.com ...
- 如何写出同事看不懂的Java代码?
原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是没更新就是在家忙着带娃的Hydra. 前几天,正巧赶上组里代码review,一下午下来,感觉整个人都血压拉满了.五花八门的代码 ...
- 《写给大忙人看的java se 8》笔记
现在才来了解java8,是不是后知后觉了点? 新的编程技术,个人不喜欢第一时间跟进. 待社区已有实践积淀再切入似乎更划算些? 一点点精明的考虑. 不多说,上代码. //读<写给大忙人看的java ...
- 转:HIBERNATE一些_方法_@注解_代码示例---写的非常好
HIBERNATE一些_方法_@注解_代码示例操作数据库7步骤 : 1 创建一个SessionFactory对象 2 创建Session对象 3 开启事务Transaction : hibernate ...
- 【Xamarin挖墙脚系列:代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧(转)】
正愁如何选择构建项目中的视图呢,现在官方推荐画板 Storybord...但是好像 xib貌似更胜一筹.以前的老棒子总喜欢装吊,用代码写....用代码堆一个HTML页面不知道你们尝试过没有.等页面做出 ...
- 【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结
虽然看过一些Java 8新特性的资料,但是平时很少用到,时间长了就忘了,正好借着Java 9的发布,来总结下一些Java 8中的新特性. 接口中的默认方法和静态方法 先考虑一个问题,如何向Java中的 ...
- 代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧
近期接触了几个刚入门的iOS学习者,他们之中存在一个普遍和困惑和疑问.就是应该怎样制作UI界面.iOS应用是非常重视用户体验的,能够说绝大多数的应用成功与否与交互设计以及UI是否美丽易用有着非常大的关 ...
- 匈牙利&&EK算法(写给自己看)
(写给自己看)匈牙利算法(最大匹配)和KM算法(最佳匹配) 匈牙利算法 思想 不断寻找增广路,每次寻得增广路,交换匹配边和非匹配边,则匹配点数+1 这里增广路含义:交错路,即从未匹配点出发经过未匹配边 ...
随机推荐
- [BZOJ4567][SCOI2016]背单词(Trie+贪心)
1.题意表述十分难以理解,简单说就是:有n个单词,确定一个背的顺序,使总代价最小. 2.因为第(1)种情况的代价是n*n,这个代价比任何一种不出现第(1)种情况的方案都要大,所以最后肯定不会出现“背某 ...
- zoj 3469 区间dp **
题意:有一家快餐店送外卖,现在同时有n个家庭打进电话订购,送货员得以V-1的速度一家一家的运送,但是每一个家庭都有一个不开心的值,每分钟都会增加一倍,值达到一定程度,该家庭将不会再订购外卖了,现在为了 ...
- 河南省队选拔 HAOI2015 解题报告
其实省选在四天前就已经结束了,但由于题目难度略大我到今天上午才补完所有题目……(捂脸逃)考场上很幸运,打完了所有我会写的部分分,最后Round1的110分 + Round2的70分,勉强算是没有被 ...
- Codeforces Round #361 (Div. 2) E. Mike and Geometry Problem 离散化 排列组合
E. Mike and Geometry Problem 题目连接: http://www.codeforces.com/contest/689/problem/E Description Mike ...
- jmeter3.3—插件管理器的安装
一.介绍JMeter Plugins 一直以来, JMeter Plugins 为我们提供了很多高价值的JMeter插件,比如: 用于服务器性能监视的 PerfMon Metrics Collecto ...
- 1、安装Redis的PHP扩展
1.安装Redis的PHP扩展 1.1 安装phpize yum install php-devel 1.2 下载扩展源码包,直接用wget #wget下载github上的文件 wget https: ...
- Slickflow.NET 开源工作流引擎基础介绍(二) -- 引擎组件和业务系统的集成
集成流程引擎的必要性 业务过程的变化是在BPM系统中常见的现象,企业管理层需要不断优化组织架构,改造业务流程,不可避免地带来了业务流程的变化,企业信息系统就会随之面临重构的可能性.一种直接的方式是改造 ...
- Git_搭建Git服务器
在远程仓库一节中,我们讲了远程仓库实际上和本地仓库没啥不同,纯粹为了7x24小时开机并交换大家的修改. GitHub就是一个免费托管开源代码的远程仓库.但是对于某些视源代码如生命的商业公司来说,既不想 ...
- Disable File System Redirector For Windows x64 (Python recipe)(转)
This disables the Windows File System Redirector.When a 32 bit program runs on a 64 bit operating sy ...
- 【maven】排除maven中jar包依赖的解决过程 例子:spring cloud启动zipkin,报错maven依赖jar包冲突 Class path contains multiple SLF4J bindings.
一直对于maven中解决jar包依赖问题的解决方法纠结不清: 下面这个例子可以说明一个很简单的解决方法: 项目启动报错: Connected to the target VM, address: '1 ...