河北联合大学信息学院联网游戏锄大地课设.docx
《河北联合大学信息学院联网游戏锄大地课设.docx》由会员分享,可在线阅读,更多相关《河北联合大学信息学院联网游戏锄大地课设.docx(26页珍藏版)》请在冰豆网上搜索。
河北联合大学信息学院联网游戏锄大地课设
1、软件背景介绍
锄大地属于基础类扑克游戏,具有规则简单易学,打法生动精彩的特点,并且颇具发展成为竞技性智力游戏的潜力。
锄大地较之竞技项目桥牌,运气成分偏大一些,但这一点也使其群众基础更为广泛。
因锄大地游戏富含哲理,变化多端,精彩激烈,故有人说,锄大地可以提升到与麻将并列的适合大众参与的国粹地步。
麻将是各自为战,互相牵制,而锄大地是在相互配合中尽量求得最大的利益,且敌我关系转瞬即变,并不固定,这成为锄大地最大的魅力所在。
图1锄大地游戏界面
游戏规则:
扑克牌去掉大小王共52张,四人游戏,每家13张牌。
一、牌型锄大地的出牌牌型有以下一些:
单张:
任何一张单牌。
一对:
二张牌点相同的牌。
三个:
三张牌点相同的牌。
顺:
连续五张牌点相邻的牌,如“34567”“910JQK”“10JQKA”“A2345”等,顺的张数必须是5张,A既可在顺的最后,也可在顺的最前,但不能在顺的中间,如“JQKA2”不是顺。
杂顺:
花色不全部相同的牌称为杂顺。
同花顺:
每张牌的花色都相同的顺称为同花顺。
同花五:
由相同花色的五张牌组成,但不是顺,称“同花五”。
.如红桃“278JK”。
三个带一对:
例如:
99955。
四个带单张:
例如:
99995。
二、牌的大小
1.只有张数相同的牌可以比较大小,例如:
99〉88,J〉10,但不能比较:
99和10。
2.单张牌的大小:
首先比较牌点,如果牌点相同再比较牌的花色。
牌点从大到小依次为:
2AKQJ109876543。
花色从大到小的顺序为:
黑桃、红桃、梅花、方块。
比如:
黑桃9〉红桃9〉梅花9〉方块9〉黑桃8
3.其它牌型在进行比较时都取其中一张最大的牌按单张的方式进行比较:
三个带一对时,取三个中的最大一张。
四个带单张时,取四个中最大的一张进行比较。
顺子中最大的一张进行比较,注意2在顺子中作为小牌,如:
65432顺子比较时,只取6进行比较;A在和K相连作顺时,按大牌进行比较,在和2连在一起作顺时,作小牌处理。
4.五张牌的牌型中,同花顺最大,四个带单张第二,三个带一对第三,同花五第四,杂顺最小。
也就是说,上家出了杂顺后,你的任何一副同花五、三个带一对、四个带单张或同花顺都比杂顺大。
三、出牌规则
1.第一副牌都由拿方块3的一方首先出牌,而且第一轮出牌中必须包含方块3。
以后每副牌都由上副牌获胜者(第一个打完手中牌的一方)出牌,并且第一轮牌不需要包含方块3。
2.首家可以出任何一种合法的牌型。
3.首家出牌后,下家所出的牌张数必须和首家的相同,同时比首家所出的牌大;下家也可以Pass表示不出牌,由再下一家继续出牌。
4.如果连续三家都Pass,这时最后出牌的一家可以重新打出新的牌型。
5.如此继续,直到一人手中的牌全部打光为止
计算分数:
在其中一方手中的牌全部出完后开始计分,计分过程举例如下:
1.假设A、B、C、D四个玩家在一局游戏结束后,手中剩余的牌张数分别为:
A:
5张 B:
8张 C:
0张 D:
12张
2.先根据每家手中剩余的牌张数计算牌分,假设剩余牌张数为n:
(1)n<8时,牌分为n
(2)8≤n<10时,牌分为2n
(3)10≤n<13时,牌分为3n
(4)n=13时,牌分为4n
(5)如果游戏结束时,手上还有8张或更多的牌,同时有黑桃2,牌分还要再乘以2
根据上述算法,ABCD的牌分分别如下:
A:
5分 B:
8x2=16分 C:
0分 D:
12x3=36分
3.按下面的公式计算最后得分:
A的得分=(B的牌分-A的牌分)+(C的牌分-A的牌分)+(D的牌分-A的牌分)
B的得分=(A的牌分-B的牌分)+(C的牌分-B的牌分)+(D的牌分-B的牌分)
C的得分=(A的牌分-C的牌分)+(B的牌分-C的牌分)+(D的牌分-C的牌分)
D的得分=(A的牌分-D的牌分)+(B的牌分-D的牌分)+(C的牌分-D的牌分)
按上述公式计算得到ABCD的实际得分为:
A:
(16-5)+(0-5)+(36-5)=11+-5+31=37分
B:
(5-16)+(0-16)+(36-16)=-11+-16+20=-7分
C:
(5-0)+(16-0)+(36-0)=5+16+36=57
D:
(5-36)+(16-36)+(0-36)=-31+-20+-36=-87
2、核心算法思想
根据锄大地游戏的游戏规则,可以得知游戏的流程为:
发完牌后,手中有方块3的玩家获得出牌权,之后是按顺时针方向顺序出牌,可以选择pass,即放弃出牌,当其他三方玩家都选择放弃出牌时,重新获得出牌优先权。
直至一家手中的牌全部出完,游戏结束,并且计分。
1、牌型结构体,以1-8的整形数值来表示各种牌型,由于所有牌型中,最多包含的牌数不超过5张,所以设置指向纸牌的指针数组大小为5就可以了,用来指向该牌型中的牌在原来一副牌中的位置。
单张:
牌型为1,权值为该牌的权值;
一对:
牌型为2,权值为该对中权值最大的牌的权值;
三张:
牌型为3,权值为该三张中权值最大的牌的权值;
杂顺:
牌型为4,权值为该杂顺中权值最大的牌的权值;
五同花:
牌型为5,权值为该五同花中权值最大的牌的权值;
三带二:
牌型为6,权值为该三带二中的三张中权值最大的牌的权值;
四带一:
牌型为7,权值为该四带一中的四张牌中权值最大的牌的权值;
同花顺:
牌型为8,权值为该同花顺中权值最大的牌的权值。
另外,顺为A2345,权值不能为牌2的权值,而要用牌5的权值;顺为23456,也一样,不能用牌2的权值,而要用到牌6的权值。
2、设置全局变量count[4],用来记录当前玩家手中牌的数量,初始为12,当其中一个值为0时,游戏结束。
intcount[4];//玩家手中牌的数量
3、位置记录参数sait,用来记录各个玩家所在的位置,主要是配合count[sait]来使用,在牌面评估中也起到比较大作用。
0、1、2、3分别表示机器玩家1、机器玩家2、机器玩家3、用户玩家。
4、洗牌与发牌算法:
第一种:
定义一个拥有52个元素的一维数组赋值为-1。
然后随机生成0-52之间的数,然后判断生成的数是否在数组中已存在,不存在则存入数组,已存在则重新生成,直到52个数全部出现为止。
第二种:
也可以这样,比如,定义一个拥有52个元素的一维数组,依次赋值为0-51,然后随机生成两个0-51的数字,把这两个位置的数字互换。
这样做比较多的次数之后,也就形成乱序的了,这个效率也不是特别高,但是比第一种要好。
第三种:
定义一个拥有52个元素的一维数组,依次赋值为0-51,随机52次,每次随机出一个数字,和第i个位置也即是当前位置的数字交换,这样就比较不错了。
考虑到效率跟公平性的问题,我所采用的第三种的算法来实现的。
5、用户出牌、牌型判断与合法性检测算法:
轮到用户玩家出牌时,玩家根据自己手中牌选择是否出牌,选择pass则直接跳过,不做其他处理,如果是出牌,用户玩家需要选择要出的牌的序号进行出牌。
在用户玩家选择出牌后,机器根据用户玩家所出的牌进行牌型的判断、合法性的检测,具体算法如下:
(1)如果用户所出的牌数为1张,课直接判断为单张。
如果是2两张,则要判断这两张牌的牌值是否相等,如相等,课判断为对。
如果是三张牌,要判断这三张牌的牌值是否相等,如相等,可判断为三张。
(2)关键是用户所出的牌为5张的情况,可能有杂顺、五同花、同花顺、三带二、四带一这五中情况。
首先根据权值进行排序并判断这五张牌中是否存在牌值相等的两张牌,如果有则可能是三带二、四带一或不合法,如果第1张牌与第4张牌相等或第2张牌与第5张牌相等,并且有一个对子,则可以判断为三带二;
如果是第1张牌与第3张牌相等或第3张牌与第5牌相等,则可以判断为四带一;
不符合以上两种情况为不合法。
假如不存在牌值相等的两张牌,则可能的情况为杂顺、五同花、同花顺,判断这五张牌的牌值是否为依次增大的,如果是,那么可以判断为杂顺或同花顺,如果花色一样,可以判断为同花顺,否则为杂顺;
如果这五张牌的牌值不是依次增大的,可能的情况为五同花或者是不合法,只有花色相同时才能判断为五同花,否则不合法。
判断所出的牌大小是否合法,如果是优先出牌,那么只要牌型合法就可以出牌,如果是跟牌,则一定要比上家大的牌才合法,主要是通过比较上家牌型与用户出牌牌型来实现的。
3、核心算法流程图
图2牌值合法判断流程图
判断所出的牌大小是否合法,如果是优先出牌,那么只要牌型合法就可以出牌,如果是跟牌,则一定要比上家大的牌才合法,主要是通过比较上家牌型与用户出牌牌型来实现的。
图3洗牌、发牌算法流程图
定义一个拥有52个元素的一维数组,依次赋值为0-51,随机52次,每次随机出一个数字,和第i个位置也即是当前位置的数字交换
4、源代码
下面给出的是锄大地游戏洗牌发牌算法源代码:
#defineBASE3//底牌数
#defineEVERYONE17//每人发牌数
#definePLAYMEM3//玩家数
#defineSUM(EVERYONE*PLAYMEM+BASE)//总牌数
#defineSELECT(EVERYONE+BASE)//选择底牌之后的牌数
#defineSTIMES10//洗牌次数
usingnamespacestd;
voidShuffle();//洗牌
intDealCard();//发牌
inlinevoidSortCard(int*nCard,intnSize);//排序用户手中的牌
inlineconstcharGetSuit(intnCard);//确定花色
inlineconstchar*GetFace(intnCard);//确定牌的大小(字符串)
inlineintGetFacePoints(intnCard);//确定牌的大小(数值)
intnCard[SUM];//代表一副张的牌
constcharchSuit[4]={6,3,4,5};//代表牌的花色
constchar*pchFace[13]={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};//代表牌的大小,mm代表小王,MM大王
intnCurrentCard=-1;//当前牌数
voidOutputCards(int*nCard,intnSize);//输出牌,包括发给玩家的张牌,张底牌,玩家选择底牌之后的张牌
intAgainCard[SELECT];//选择底牌之后的牌归并
inlinevoidInsertArray(int*nCard,int*bCard);//选择某个玩家之后的牌,归并
下面给出的是用锄大地游戏出牌的主要源代码:
caseIDB_BTN_LOGIN:
{
MSG_LOGINMyLogin;//定义当前用户登录
MyLogin.dwType=MSGID_LOGIN;//新用户登录类型赋值
MyLogin.nLen=sizeof(structMSG_LOGIN);
charcText[20];
GetWindowText(Hwnd_Edit_Name,cText,20);
strcpy(MyLogin.chPlayerName,cText);
char*p=(char*)&MyLogin;//将新牌结构体强转化为字符串指针
MySocket.Send(p,sizeof(structMSG_LOGIN));//通过MyGame的MySocket的Send方法发送
}
break;
caseIDB_BTN_START:
{
Btn_Start.BtnEnable(false);
MSG_GAME_READYMyReady;//定义当前用户准备
MyReady.dwType=MSGID_GAME_READY;//新牌结构体类型赋值
MyReady.nLen=sizeof(structMSG_GAME_READY);
MyReady.bReady=true;
MyReady.nRoom=MyCards.MyRoom;
MyReady.nGameNo=MyCards.MyGameNo;
char*p=(char*)&MyReady;//将新牌结构体强转化为字符串指针
MySocket.Send(p,sizeof(structMSG_GAME_READY));//MySocket的Send方法发送
}
break;
caseIDB_BTN_PASS:
{
MSG_GAME_PASSMyPass;//定义新牌结构体
MyPass.dwType=MSGID_GAME_PASS;//新牌结构体类型赋值
MyPass.nLen=sizeof(structMSG_GAME_PASS);//长度赋值
MyPass.nGameNo=MyCards.MyGameNo;
MyPass.nRoom=MyCards.MyRoom;
char*p=(char*)&MyPass;//将新牌结构体强转化为字符串指针
MySocket.Send(p,sizeof(structMSG_GAME_PASS));//MySocket的Send方法发送
MyCards.CancelSel();
}
break;
caseIDB_BTN_CP:
if(MyCards.CardRule())
{
MSG_GAME_NOWCARDMyNowCard;//定义新牌结构体
MyNowCard.dwType=MSGID_GAME_NOWCARD;//新牌结构体类型赋值
MyNowCard.nLen=sizeof(structMSG_GAME_NOWCARD);//长度赋值
MyNowCard.nGameNo=MyCards.MyGameNo;
MyNowCard.dwPlayerID=MyCards.MyPlayerID;
MyNowCard.nNumber=MyCards.CardCurSel.size();//出牌数赋值,这里为随机值,注意真正在游戏中是由玩家选取数决定
MyNowCard.nLeave=MyCards.CardCur.size()-MyCards.CardCurSel.size();//计算还剩多少张
MyNowCard.nCurType=MyCards.nCurPutType;
MyNowCard.nRoom=MyCards.MyRoom;
for(inti=0;i{
MyNowCard.NowCardArray[i]=MyCards.CardCurSel[i].cCard;
}
char*p=(char*)&MyNowCard;//将新牌结构体强转化为字符串指针
MySocket.Send(p,sizeof(structMSG_GAME_NOWCARD));//通过MyGame的MySocket的Send方法发送
}
break;
}
break;
MSG_GAME_NOWCARD*pGameCard=(MSG_GAME_NOWCARD*)pMsgReceive->pReceiveData;//知道是新牌消息时转化为新牌消息
char*p=(char*)pGameCard;
RoomArray[pGameCard->nRoom].MyCards.nPassNum=0;//只要有人出牌,则pass数归零
RoomArray[pGameCard->nRoom].MyCards.g_Result.nLeave[pGameCard->nGameNo]=pGameCard->nLeave;
for(inti=0;i<4;i++)
{
if(pGameCard->nGameNo!
=3)//当前不是轮到最后一个人
{
if(i==pGameCard->nGameNo+1)//让下一个人能出牌
pGameCard->bTurn=true;
else//其它人不能出
pGameCard->bTurn=false;
}
else//当前轮到最后一个人
{
if(i==0)//让第一个人出牌
pGameCard->bTurn=true;
else//其它人不能出
pGameCard->bTurn=false;
}
SendMsgTo(g_RoomInfoArray[pGameCard->nRoom].dwPlayerID[i],p,pGameCard->nLen);
}
if(pPass->nGameNo!
=3)//当前不是轮到最后一个人
{
if(i==pPass->nGameNo+1)//让下一个人能出牌
{
pPass->bTurn=true;
if(RoomArray[pPass->nRoom].MyCards.nPassNum>=3)
pPass->bNew=true;
else
pPass->bNew=false;
}
else//其它人不能出
{
pPass->bTurn=false;
}
}
else//当前轮到最后一个人
{
if(i==0)//让第一个人出牌
{
pPass->bTurn=true;
if(RoomArray[pPass->nRoom].MyCards.nPassNum>=3)
pPass->bNew=true;
else
pPass->bNew=false;
}
else//其它人不能出
{
pPass->bTurn=false;
}
}
下面是创建游戏窗口的部分源代码:
HWNDHwnd_Edit_Ip;
constintIDB_EDIT=11;//定义文本框
HWNDHwnd_Edit_Name;
HWNDhwndPush;
constintIDB_PUSHBUTTON=13;//定义文本框
HWNDhwndChatEdit;//只读文本框显示
charbigchar[5000];//显示在大文本框里的文本
BOOLbActive=TRUE;//应用程序是否活跃
HWNDhWndGame;
voidLoginFinish();//设置登录完成后的处理函数
boolbShowInfo=false;
intWINAPIWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,
PSTRszCmdLine,intiCmdShow)
{
WNDCLASSwndcls;
wndcls.cbClsExtra=0;//类结构后附加的额外的byte数
wndcls.cbWndExtra=0;
wndcls.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);//窗口刷新的画笔句柄
wndcls.hCursor=LoadCursor(NULL,IDC_ARROW);
wndcls.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wndcls.hInstance=hInstance;
wndcls.lpfnWndProc=WndProc;//指定消息回调函数
wndcls.lpszClassName="GameWindow";//标题栏显示标题
wndcls.lpszMenuName=NULL;//菜单
wndcls.style=CS_HREDRAW|CS_VREDRAW;//水平重画和竖直重画
RegisterClass(&wndcls);
hInst=hInstance;//Storeinstancehandleinourglobalvariable
//设置窗口风格:
DWORDdwStyle;
dwStyle=WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX;
//取得去掉windows系统菜单栏后的工作区
RECTrcArea;
SystemParametersInfo(SPI_GETWORKAREA,NULL,&rcArea,NULL);
//取得客户区大小为800*600对应的窗口大小
RECTrect;
rect.left=rect.top=0;
rect.right=800;
rect.bottom=600;
:
:
AdjustWindowRectEx(&rect,dwStyle,TRUE,0);
//将视窗的位置设定在屏幕(工作范围)的中央
intw=rect.right-rect.left;
inth=rect.bottom-rect.top-GetSystemMetrics(SM_CYMENU);
intx0=rcArea.left+(rcArea.right-rcArea.left-w)/2;
inty0=rcArea.top+(rcArea.bottom-rcArea.top-h)/2;
//创建窗口
hWnd=CreateWindow("GameWindow","锄大地",dwStyle,
x0,y0,w,h,NULL,NULL