扑克游戏的洗牌算法及简单测试

我在学习《写给大家看的C语言书》这本书时,对书后面附录的一个扑克游戏程序非常感兴趣。源代码在帖子最后。
PS:这本C语言教学书真的很好呀,非常适合我的水平。

在我弄明白作者的程序后,感觉有点小小的遗憾,就是因为作者的发牌程序,当然不是他的算法问题。我真的一直不知道作者的发牌算法竟然是FisherYates算法,当然我以前也根本就不知道什么是FisherYates算法。作者的发牌原理就是每次从牌堆里面随机抽出一张发出去,每次发牌,每次现抽。当然这个随机并不代表真正意义上的随机。我总觉得这个有点不舒服。因为我们玩扑克的时候,都是先洗好了牌,然后从上到下依次拿牌。我打算改造一下作者的程序。

其实很简单,就是把每次随机抽取出来的牌存到一个数组里面就好,而这就是FisherYates算法。你如果不同意的话,可以分析一下。我贴一下代码:

void ShuffleArray_Fisher_Yates(char* arr, const int len)
{
    int i = len, j;
    char temp;
    if ( i == 0 ) return;
    while ( --i ) {
        j = rand() % (i+1);
        temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
PS:这段代码我第一次是在http://coolshell.cn/articles/8593.html看到的,原帖作者的代码中,数组是字符型的,这个不重要。
这段代码的原理,其实就是把随机抽到的牌从后往前排列。

http://coolshell.cn/articles/8593.html帖子作者还贴出了其他几个算法,他说大家常用的洗牌算法如下:
void ShuffleArray_General(char* arr, int len)
{
    const int suff_time = len;
    for(int idx=0; idx<suff_time; idx++) {
        int i = rand() % len;
        int j = rand() % len;
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}
这段代码的原理,就是从牌堆里面,随机抽取两张牌交换位置。

还有两个别的算法,不过我没有看明白到底是怎么一回事,我就不再贴出来了。

既然算法不一样,那么就要考虑到底哪种更好一点,或者说哪个更具有随机性。
如果仅仅是比较一下每张牌出现在每个位置上的概率的话,其实是很好实现的。::帖子作者的结论就是FisherYates算法明显比General算法好。我自己写了一下代码,测试了一下FisherYates算法,结果确实很理想,统计次数越多,效果越明显。我的代码如下:
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>

#define CARDSNUM 10                                      /* 牌数 */ 
#define NUMXIPAI 1                                       /* 洗牌次数 */ 
#define TONGJINUM 10000000                               /* 统计次数 */ 
#define CARDS cards                                      /* 扑克数组 */ 
#define TONGJI tongji                                    /* 统计数组 */          
#define CARDS_F  int CARDS[CARDSNUM]                     /* 扑克数组定义及函数参数 */ 
#define TONGJI_F __int64 TONGJI[CARDSNUM][CARDSNUM]      /* 统计数组定义及函数参数 */ 

/* 洗牌函数声明 */ 
void InitCards(CARDS_F);
void ShuffleArray_Fisher_Yates(CARDS_F);
void PrintCards(CARDS_F);
void Puke(CARDS_F); 

/* 统计函数声明 */ 
void InitTongji(TONGJI_F);
void Tongjisum(CARDS_F, TONGJI_F);
void PrintTongji(TONGJI_F);
void Tongji(CARDS_F, TONGJI_F);

main()
{
	time_t t; 
	CARDS_F;
	TONGJI_F;
	
	srand(time(&t));

//	Puke(CARDS);
//	PrintCards(CARDS);
	
	Tongji(CARDS, TONGJI);
	PrintTongji(TONGJI);
	
	return 0;
}

void InitCards(CARDS_F)       /*码牌   扑克数组初始化 */ 
{
	int i;
	for(i = 0; i < CARDSNUM; i++)   
	{
		CARDS[i] = i+1;
	}
}

void ShuffleArray_Fisher_Yates(CARDS_F)    /* Fisher_Yates算法 */ 
{
	int i, j, k;

	for(i = CARDSNUM; i > 0; i--)
	{
		
		j = (rand()%(i)); 
		k = CARDS[i-1];
		CARDS[i-1] = CARDS[j];
		CARDS[j] = k;
	}
}

void PrintCards(CARDS_F)   /* 输出牌 */ 
{
	int j;
	
	printf("\nYour Cards Is: ");
	for(j = 0; j < CARDSNUM; j++)
	{
		if(!(j%10))
		{
			printf("\n");
		}	
		printf("%d  ", CARDS[j]);
	}
}

void Puke(CARDS_F)      /* 洗牌 */ 
{
	int i;
	
	InitCards(CARDS); 
	
	for(i = 1; i <= NUMXIPAI; i++)
	{
		ShuffleArray_Fisher_Yates(CARDS);
	}
	

}

void InitTongji(TONGJI_F)              /* 统计数组初始化 */ 
{
	int j, k;
	
	for(j = 0; j < CARDSNUM; j++)
	{
		for(k = 0; k < CARDSNUM; k++)
		{
			TONGJI[j][k] = 0;
		}
	}
}

void Tongjisum(CARDS_F, TONGJI_F)    /* 统计扑克牌的出现位置 */ 
{
	int j, k;
	
	Puke(CARDS);
	
	for(j = 0; j < CARDSNUM; j++)
	{
		k = CARDS[j];
		TONGJI[j][k-1] += 1;
	}
}

void PrintTongji(TONGJI_F)   /* 输出统计结果 */ 
{
	int i, j;
	
	printf("\nTongji Result Is: \n");
	for(i = 0; i < CARDSNUM; i++)
	{
		for(j = 0; j < CARDSNUM; j++)
		{	
			printf("%d  ", TONGJI[i][j]);
		}
		printf("\n");
	}
}

void Tongji(CARDS_F, TONGJI_F)    /* 扑克牌的概率统计 */ 
{
	__int64 i;
	
	InitTongji(TONGJI);
	
	for(i = 0; i < TONGJINUM; i++)
	{
		Tongjisum(CARDS, TONGJI);
	}
}
需要说明的是,我默认用了10个数字测试,测试次数是1千万次。对于洗牌次数,我最初认为洗牌算法开始的时候是按照顺序洗牌的,那么我们可以把已经洗好的牌在用算法洗一次,效果不应该更好吗。但是从测试结果看来,其实没有多大改变,也就是说没有必要反复洗牌。

《写给大家看的C语言书》的附录扑克游戏二十一点代码我也顺便贴出来。需要说明的是,二十一点的计算规则可能有点不一样,代码里面,如果出现J,Q,K的话,是按照10点计算的。而A则既可以按照1点计算,也可以按照11点计算,这取决于哪种更有利。你有兴许的话,该一下就好了。
/* filename: 21game.cpp
This program plays a game of Blackjack with you. */

/********************************************************
ANSI C standard header files appear next */
#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>

/********************************************************
Defined constants appear next */
#define BELL '\a'
#define DEALER 0
#define PLAYER 1

/* Must keep two sets of totals for dealer and for player.
The first set counts Aces as 1 and the second counts Aces
as 11.Unlike "real world" Blackjeck,this program doesn't
allow some Aces to be 1 while others Aces are 11 in the 
same hand. */
#define ACELOW 0
#define ACEHIGH 1
/* Only one global variable is used in this entire program.
The variable holds 0,which means fales initially. Once the 
user enters his or her name in initCardsScreen(), this variable
is set to 1 (for true), so the name is never asked for again
the rest of the progame. */
int askedForName = 0; /* False initially*/
/*********************************************************
This progame's specific prototypes */
void dispTitle(void);
void initCardsScreen(int cards[52], int playerpoints[2],
int dealerPoints[2], int total[2], int * numCards);
int dealCard(int * numCards, int cards[52]);
void dispCard(int cardDrawn, int points[2]);
void totalIt(int points[2], int total[2], int who);
void dealerGetsCard(int * numCards, int cards[52],
int dealerPoints[2]);
void playerGetsCard(int * numCards, int cards[52], 
int playerPoints[2]);
char getAns(char mesg[]);
void findWinner(int total[2]);

/*********************************************************
C's program execution always begins at main() here */
main()
{
	int numCards; /* Equals 52 at beginning of each game */
	int cards[52], playerPoints[2], dealerPoints[2], total[2];
	char ans; /* For user's Hit/Stand or Yes/No pesponse */
	do 
	{
		initCardsScreen(cards, playerPoints, dealerPoints, total, &numCards);
		
		dealerGetsCard(&numCards, cards, dealerPoints);
		printf("\n");  /*Prints a blank line*/
		playerGetsCard(&numCards, cards, playerPoints);
		playerGetsCard(&numCards, cards, playerPoints);
		do
		{
			ans = getAns("Hit or stand (H/S)?");
			if (ans == 'H')
			{
				playerGetsCard(&numCards, cards, playerPoints);
			}
		} while (ans != 'S');
		totalIt(playerPoints, total, PLAYER);
		/* Player's total */
		do
		{
			dealerGetsCard(&numCards, cards, dealerPoints);
		} while (dealerPoints[ACEHIGH] < 17);
		/* 17: Dealer stops */
		totalIt(dealerPoints, total, DEALER);
		/* Dealer's total */
		findWinner(total);
		ans = getAns("\nPlay again (Y/N)? ");
	} while (ans == 'Y');
	return 0;
}

/***************************************************************
This function initializes the face values of the deck of cards
by putting four sets of 1-13 in the 52-card array .Also clears
the screen and displays a title.  */
void initCardsScreen(int cards[52], int playerPoints[2], 
int dealerPoints[2], int total[2], int * numCards )
{
	int sub , val = 1; /* This function's Work variables */
	char firstName[15]; /* Holds user's first name */
	*numCards = 52; /* Holds running total of number of cards */
	for (sub = 0; sub <= 51; sub++)  /* Counts from 0 to 51 */
	{
		val = (val == 14) ? 1 : val; /* If val is 14,resets to 1 */
		cards[sub] = val;
		val++;
	}
	for(sub = 0; sub <= 1; sub++) /* Counts from 0 to 1 */
	{
		playerPoints[sub] = dealerPoints[sub] = total[sub] = 0;
	}
	dispTitle();
	if (askedForName == 0) /* Name asked for only once */
	{
		printf("\nWhat is your first name? ");
		scanf(" %s", firstName);
		askedForName = 1; /* Don't ask prompt again */
		printf("Ok, %s, get ready for casino action!\n\n",
				firstName);
		getchar(); /* Discards newline. You can safely */
	}				/* ignore comliler warning here. */
	return;
}

/*********************************************************
This function gets a card for the player and updates the 
player's points. */
void playerGetsCard(int *numCards, int cards[52], int playerPoints[2])
{
	int newCard;
	newCard = dealCard(numCards, cards);
	printf("You draw: ");
	dispCard(newCard, playerPoints);
}

/***********************************************************
This function gets a card for the dealer and updates the 
dealer's points. */
void dealerGetsCard(int *numCards, int cards[52], int dealerPoints[2])
{
	int newCard;
	newCard = dealCard(numCards, cards);
	printf("The dealer draws: ");
	dispCard(newCard, dealerPoints);
}

/*****************************************************
This function gets a card from the deck and stores it in 
either the dealer's or the player's hold of cards. */
int dealCard(int * numCards, int cards[52])
{
	int cardDrawn, subDraw;
	time_t t; /* Gets for a random value */
	srand(time(&t)); /* Seeds random-number generator */
	subDraw = (rand()%(*numCards)); /* From 0 to numCards */
	cardDrawn = cards[subDraw];
	cards[subDraw] = cards[*numCards - 1]; /* Puts top card */
	(*numCards)--;                  /* in place of drawn one */
	return cardDrawn;
}

/********************************************************
Displays the last-drawn card and updates points with it. */
void dispCard(int cardDrawn, int points[2])
{
	switch (cardDrawn)
	{
		case(11): printf("%s\n", "Jack");
		          points[ACELOW] += 10;
		          points[ACEHIGH] += 10;
		          break;
  		case(12): printf("%s\n", "Queen");
				  points[ACELOW] += 10;
		          points[ACEHIGH] += 10;
		          break;
		case(13): printf("%s\n", "King");
				  points[ACELOW] += 10;
		          points[ACEHIGH] += 10;
		          break;
		default:  points[ACELOW] += cardDrawn;
				  if (cardDrawn ==1)
				  {
  					  printf("%s\n", "Ace");
  					  points[ACEHIGH] += 11;
  				  }
				  else
				  {
  					  points[ACEHIGH] += cardDrawn;
  					  printf("%d\n", cardDrawn);
  				  }
	}
	return;
}

/**************************************************************
Figurs the total for player or dealer to see who won. This
function takes into account the face that Ace is either 1
or 11. */
void totalIt(int points[2], int total[2], int who)
{
	/* The following routine first looks to see if the total
	points counting Aces as 1 is equal to the total points
	counting Aces as 11. If so, or if the total points
	counting Aces as 11 is more than 21, the program uses
	the total with Aces as 1 only. */
	if ((points[ACELOW] == points[ACEHIGH]) || (points[ACEHIGH] > 21))
	{
		total[who] = points[ACELOW];  /* Keep all Aces as 1 */
	}
	else
	{
		total[who] = points[ACEHIGH];   /* Keeps all Aces as 11 */
	}
	if (who == PLAYER) /* Determines the message printed */
	{
		printf("You have a total of %d\n\n", total[PLAYER]);
	}
	else
	{
		printf("The house stands with a total of %d\n\n", total[DEALER]);
	}
	return;
}

/***********************************************************
Prints the winning player. */
void findWinner(int total[2])
{
	if (total[DEALER] == 21)
	{
		printf("The house wins.\n");
		return;
	}
	if ((total[DEALER] > 21) && (total[PLAYER] > 21))
	{
		printf("%s", "Nobody wins.\n");
		return;
	}
	if ((total[DEALER] >= total[PLAYER]) && (total[DEALER] < 21))
	{
		printf("The house wins.\n");
		return;
	}
	if ((total[PLAYER] > 21) && (total[DEALER] < 21))
	{
		printf("The house wins.\n");
		return;
	}
	printf("%s%c", "You win!", BELL);
	return;
}

/**************************************************************
Gets the user's uppercase, single-character response. */
char getAns(char mesg[])
{
	char ans;
	printf("%s", mesg); /* Prints the prompt message passed */
	ans = getchar();
	getchar();  /* Discards newline. You can safely ignore */
	return toupper(ans);    /* compiler warning here. */
}

/************************************************************
Clears everything off the screen. */
void dispTitle(void)
{
	int i = 0;
	while (i < 25)  	/* Clears screen by printing 25 blank */
	{                   /* lines to "push off" stuff that */
		printf("\n");	/* might be left over on the screen */
		i++;			/* before this program */
	}
	printf("\n\n*Step right up to the Blackjack tables*\n\n");
	return;
}

转载于:https://my.oschina.net/shusheng/blog/91157

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值