21点游戏要求做到设计出人和电脑对战的21点游戏,同时电脑既然作为另一个玩家,也就必须具备自主选择的意识,那么在21点中,需要电脑做出选择的也就是几个选项,一是牌点的比较,二是根据目前形势是否要牌的判断,三是自主判断A点作为几点更加合适,只有实现这三个目标,电脑玩家才能被成为比较智能的进行游戏,而不同情况下,电脑玩家如何选择是否要牌也就是一大难点。
关于设计
- 首先是在洗牌程序中避免繁杂洗牌,只用分别定义两个数组来进行“牌”和“空位”的交换就可以以此达到随机的目的,并且简洁易懂。
- 然后是在发牌亮牌函数中,要让记牌器随发牌移动,同时要统计玩家手牌,这里我采用了for循环来达到目的,也就是在每次发牌之后自动让记牌器顺沿一位,这样就避免了多次书写记牌器移动,而玩家手上亮牌也可以用for循环,直接在i<5的情况下,不断自加然后亮出每一张现有的手牌(玩家手牌小于等于5)。
- 还有在电脑判断是否要牌的情况,在主函数里加入true和false两个声明,分别定义要牌和不要牌,这样就大量减少了代码重复,同时也非常简洁明了,只需要判断当前状况后加上return true or false某一种情况即可。
最后是在程序中设计到的计算概率,直接在计算概率中引入了数组,每一个数组空位用来计算一张牌的剩余量,通过switch的结构让循环剩余牌不断在数组中自加,也就计算出了剩余牌各自的张数,再通过一个for循环,让小于爆牌点数的牌数全部相加,这样做不仅减少了很大的工作量,同时也让对剩余牌的计算更加简洁,效率更高
对于A牌点数的选择
Int point,a;
//a为A牌数量
Int A=11;
//将所有a牌先当做11计算
if (a > 0)
{ for (int i = 0; i < a; i++)
{
if(point>21)
{
point -= 10;//将a一律先当做11计算,如果爆牌则不断自减10,也就是把a再看为1点,直到不爆牌为止
}
}
return(point);
}
else return(point);
}
以下是未进行封装的各个文件源代码:
AI21.Cpp:
#include "StdAfx.h"
#include "AI21.h"
CAI21::CAI21(void)
{
}
CAI21::~CAI21(void)
{
}
//函数作用:判断玩家是否应该拿牌的决策函数
//输入参数:id—玩家ID(0或1),cards—洗好的52张牌数组,roundCards—两玩家回合存牌数组,roundCardCnts—两玩家回合存牌计数器数组,topIndex—牌数组中当前面上的牌序号
//返回值:是否应该拿下一张牌
//函数说明:本函数被机器人拿牌函数RobotTakeCard所调用,RobotTakeCard大体上是与人类拿牌函数TakeCard差不多的,而主要的核心差别就是对本函数的调用取代了玩家1的TakeCard
bool CAI21::IfTakeCard(int id, char cards[52], char roundCards[2][5], int roundCardCnts[2], int topIndex)
{ //请在以下分段注释下空白处填写相应的实现代码
//1、排除必须拿和必须不拿的情况
//如果已经无牌可拿,则直接返回false
if (topIndex == 52)
return false;
else {
//计算自己和对方当前手上牌点
int cai = this->CalcuPoint(roundCards[0], roundCardCnts[0]);
int gou = this->CalcuPoint(roundCards[1], roundCardCnts[1]);
//如果自己已经爆了或者正好拿到21点满分,或者对方已经爆了,则肯定不再拿牌,返回false
if ((cai >= 21) || (gou > 21))
return false;
//在都未爆的情况下,如果自己牌点落后于对方,则无论如何都必须拿牌,返回true
else {
if ((cai < 21) && (cai < gou) && (gou < 21))
return true;
//2、在当前牌点领先或持平于对方的情况下,依据爆的概率大小决定是否拿牌
//统计13种面值的剩余牌张数(为避免记忆已经打过的牌,这里直接从topIndex开始统计剩余牌,但要清楚实际打牌的时候是一副牌扣除打过的牌得到剩余牌张及其数量的,即需要记牌)
else {
int c[13];
for (int i = topIndex; i <= 51; i++)
{
switch (cards[i])
{
case'A':c[0]++; break;
case'2':c[1]++; break;
case'3':c[2]++; break;
case'4':c[3]++; break;
case'5':c[4]++; break;
case'6':c[5]++; break;
case'7':c[6]++; break;
case'8':c[7]++; break;
case'9':c[8]++; break;
case'D':c[9]++; break;
case'J':c[10]++; break;
case'Q':c[11]++; break;
case'K':c[12]++; break;
}
}
//生成预测拿牌临时数组(该临时数组仅用于预测拿牌后是否会爆)
char myCards[5], myCnt = roundCardCnts[id];//我的预测拿牌数组和当前牌数
for (int i = 0; i < myCnt; i++)
myCards[i] = roundCards[id][i];//复制当前我手上的牌数组
//计算拿下一张牌后爆的总概率(可能性)
float percent = 0;//总爆率
if (cai <= 11)
return true;
else
{
int d=21 - cai, sum= 0;
for(int i= d;i< 13;i++)
{
sum+= c[i];
}
percent = sum / (51 - topIndex);
}
//依据爆率是否小于0.5决定是否拿牌,爆率小于0.5则返回true,否则返回false
if ((percent < 0.5))
{
return false;
}
else
return true;
}
}
}
}
//函数作用:机器人玩家的拿牌函数(包括提示拿牌、拿牌、亮牌三个过程)
//输入参数:id—机器人玩家ID(0或1),cards—洗好的52张牌数组
//输入输出参数:roundCards—两玩家回合存牌数组,roundCardCnts—两玩家回合存牌计数器数组,topIndex—牌数组中当前面上的牌序号
//返回值:执行本函数后,玩家是否还有要牌权
bool CAI21::RobotTakeCard(int id, char cards[52], char roundCards[2][5], int roundCardCnts[2], int &topIndex)
{
//系统提示拿牌
std::cout << "玩家"<<id + 1<<":按下\'y\'键拿牌或按下\'n\'键放弃:" ;
//机器人自动决策拿牌并自动回应
bool bRet = IfTakeCard(id, cards, roundCards, roundCardCnts, topIndex);//确定是否有要牌权
std::cout << (bRet ? 'y' :