单机麻将的基本功能其实年前已经完成了,只是写文档麻烦,再加上懒癌和重度拖延症,就一直拖着没更新。今天周末一个人没什么事干,抽空把它更新了。
麻将的表示用数组表示,int card[136];值分别是 0-135;
值/4
0-8:表示万,9-17:表示饼,18-26:表示条,27-33:表示东南西北中发白
class MahjongCard : public Renderable{
private :
int mCardNumber; //牌值,0-135
bool mIsSelected; //是否被选中的状态,玩家鼠标选择出牌时用
Vector2I mOffset; //选中后的位置偏移量,鼠标选择出牌时用
public :
void SetCardNumber(int cardNumber){
mCardNumber = cardNumber;
}
int GetCardNumber(){
return mCardNumber;
}
bool IsSelected(){
return mIsSelected;
}
void SetSelected(bool bSelected){
mIsSelected = bSelected;
}
void SetOffset(int xOffset, int yOffset){
mOffset.x = xOffset;
mOffset.y = yOffset;
}
Vector2I &GetOffset(){
return mOffset;
}
};
麻将算法
分为胡牌算法和出牌ai
胡牌算法:声明一个辅助数组 int cardAssists[38],用0填充。如果是判断自摸,则遍历手上的牌并根据index的值填充辅助数组。如果是判断吃胡,则遍历手上的牌和最后一张打出的牌。并根据index的值填充辅助数字。
index = 具体的牌值/4:如果值小于等于8,cardAssists[index+1] ++;即cardAssists数组的1-9表示万,
如果index的值大于等于9并且小于等于17,cardAssists[index+2] ++;即11-19表示饼
如果index的值大于等于18并且小于等于26,cardAssists[index+3]++;即21-29表示条。
如果index的值大于等于27,cardAssists[index+4] ++;即31-37表示字牌。
class MahjongAI{
//递归调用
bool IsHu(){
int sum = 0;
for (int i = 0; i < 38; i++){
sum += mCardAssistArray[i];
}
if (sum == 0){
//匹配完了,如果有找到将牌,表示可以胡牌了。
if (mIsFindJiang)
return true;
else return false;
}
int index = 0;
//跳过没有的牌
for (index = 0; index = 3){
mCardAssistArray[index] -= 3;
//再判断剩下的牌是否胡牌。
if (IsHu()){
return true;
}
//没有胡牌,把这一刻加回去
mCardAssistArray[index] += 3;
}
//如果是一对,并且还未找到将牌,
if (mCardAssistArray[index] >= 2 && !mIsFindJiang){
mIsFindJiang = true;
mCardAssistArray[index] -= 2;
//去掉,并判断剩下的牌能不能胡
if (IsHu()){
return true;
}
mIsFindJiang = false;
//不能胡,加回去
mCardAssistArray[index] += 2;
}
//索引大于30的不判断顺子
if (index > 30)
return false;
//判断如果能组成顺子,把顺子牌拿掉,
if (mCardAssistArray[index + 1] >= 1 && mCardAssistArray[index + 2] >= 1){
mCardAssistArray[index] --;
mCardAssistArray[index + 1] --;
mCardAssistArray[index + 2] --;
//判断剩下的牌能不能胡牌
if (IsHu()){
return true;
}
//不能胡牌,加回数组中
mCardAssistArray[index] ++;
mCardAssistArray[index + 1] ++;
mCardAssistArray[index + 2] ++;
}
return false;
}
}
出牌ai:判断有没有单独的字牌和单独的牌->拆掉那些不是对子并且不是顺子的牌->判断最佳的听牌组合->经过上面的方法还选不出来该出的牌?那就随机出牌。
MahjongCard *MahjongAI::GetBestOutCard(){
MahjongCard *card = NULL;
QueryInCards();
vector randomCardList,allCardList;
CardMap &inHandcard = mPlayer->GetInHandCard();
printf("inhandcard size:%d\n",inHandcard.size());
CardMap::reverse_iterator iter = inHandcard.rbegin();
while (iter != inHandcard.rend()){
int cardIndex = GetCardIndex(iter->second);
if (cardIndex > 30){
if (mCardAssistArray[cardIndex] == 1){
//printf("单独的字牌:%d\n", cardIndex);
randomCardList.push_back(iter->second);
}
}
else{
if (mCardAssistArray[cardIndex] == 1){
if (mCardAssistArray[cardIndex - 1] == 0 && mCardAssistArray[cardIndex+1] == 0){
//printf("单独的牌:%d\n", cardIndex);
randomCardList.push_back(iter->second);
}
}
}
allCardList.push_back(iter->second);
iter++;
printf("%d,",cardIndex);
}
printf("\n");
if (randomCardList.size() == 0){
printf("不知道打什么牌1:进一步选择");
while (iter != inHandcard.rend()){
int cardIndex = GetCardIndex(iter->second);
if (cardIndex > 30){
break;
}
else{
if (mCardAssistArray[cardIndex] == 1){
if (mCardAssistArray[cardIndex - 1] >= 1 && mCardAssistArray[cardIndex + 1] >= 1){
iter++;
continue;
}
else if (mCardAssistArray[cardIndex + 1] >= 1 && mCardAssistArray[cardIndex + 2] >= 1){
iter++;
continue;
}
else{
if (cardIndex % 10>2){
if (mCardAssistArray[cardIndex - 2] >= 1 && mCardAssistArray[cardIndex - 1] >= 1){
iter++;
continue;
}
}
}
card = iter->second;
printf(":%d\n", GetCardIndex(card));
break;
}
}
}
}
else{
int index = rand() % randomCardList.size();
card = randomCardList[index];
}
if (card == NULL){
printf("进一步选择失败,判断ting\n");
int assistArray[38];
int bestIndex = -1, bestTingCount = -1;
for (int i = 0; i 0){
printf_s("Ting:%d,cardIndex:%d\n",tingCount,i);
if (tingCount > bestTingCount){
bestTingCount = tingCount;
bestIndex = i;
}
}
mCardAssistArray[i]++;
}
if (bestIndex == -1){
int index = rand() % allCardList.size();
card = allCardList[index];
printf_s("最后还是随机出:%d\n", GetCardIndex(card));
}
else{
iter = inHandcard.rbegin();
while (iter != inHandcard.rend()){
int cardIndex = GetCardIndex(iter->second);
//printf("%d-----%d\n",cardIndex,bestIndex);
if (bestIndex == cardIndex){
printf_s("TING:%d\n",iter->second->GetCardNumber());
card = iter->second;
break;
}
iter++;
}
}
}
printf("%s-----out:%d\n",typeid(*mPlayer).name(), GetCardIndex(card));
return card;
}
玩家
一个玩家,三个AI
#include "MahjongCard.h"
#include "MahjongAI.h"
#include
麻将场景
包括一个背景,2个对话框(事件对话框,结束对话框),4个玩家,管理0-135共136个牌,处理游戏流程 洗牌->发牌->打牌->游戏结束。
#pragma once
#include "../SDLGame/Scene.h"
#include "../SDLGame/Common.h"
#include "../SDLGame/MessageListener.h"
#include "Player.h"
#include
#include
#include "EventDialog.h"
#include "GameEndDialog.h"
#include "../SDLGame/MessageDispatcher.h"
using namespace std;
enum GameState{ START = 0, RIFFLECARD,SEND_CARD, RUNNING, END,
WAIT_FOR_INPUT,WAIT_FOR_DIALOG,IDLE,WAIT_FOR_END_DIALOG };
enum SCENE_MSG_TYPE{
MSG_GIVE_UP = 0,
MSG_PENG, MSG_GANG, MSG_HU, MSG_RESTART, MSG_GET_CARD,MSG_OUT_CARD
};
//自定义消息的扩展消息
struct EVENT_EXTRA{
Player *outPlayer;
Player *handlePlayer;
MahjongCard *outCard;
};
class MahjongScene : public Scene,public MessageListener{
private:
//背景
Renderable *mBackground;
//事件对话框
EventDialog *mEventDialog;
//结束对话框
GameEndDialog *mGameEndDialog;
//玩家
Player *mMyPlayer, *mNextPlayer, *mOppositePlayer, *mPrevPlayer;
vector mPlayers;
Player *mCurrentPlayer;
//发牌时用到的标记
int mCurrentPopIndex;
int mServerIndex;
int mCurrentGetIndex;
//牌的表示
int mCard[136];
float mDelayTime = 1.0f;
float mTimeElapse = 0.0f;
//牌池
CardMap mCardPool;
//当前游戏状态
GameState mCurrentState;
//最后一张出的牌
MahjongCard *mLastCard;
public:
MahjongScene(){
//初始化麻将牌
for (int i = 0; i SetCardNumber(i);
mCardPool.insert(make_pair(i, card));
}
mCurrentState = START;
}
void NextPopCardIndex(){
mCurrentPopIndex--;
if (mCurrentPopIndex 3)
mCurrentGetIndex = 0;
}
bool IsSendCardComplete(){
bool IsComplete = true;
for (int i = 0; i GetInHandCardCount() != 13){
IsComplete = false;
break;
}
}
return IsComplete;
}
void SetServerIndex(int index){
mServerIndex = index;
}
void RandServer(){
srand((uint32_t)time(NULL));
int index = rand() % 4;
SetServerIndex(index);
}
void RandPopIndex(){
srand((uint32_t)time(NULL));
int index = rand() % 4;
mCurrentPopIndex = index;
}
//处理摸牌消息
void HandleCardInEvent(Player *player,MahjongCard *inCard){
//调用ai判断摸牌事件
int ev = player->GetAI()->CardInEvent(inCard);
printf("%s---cardIN EV:%d\n", typeid(*player).name(),ev);
if (ev & EVENT_HU){
//胡牌,2秒后发送胡牌消息。()
Dispatcher.DispatchMsg(2.0f, this, this, MSG_HU);
}
else if (ev & EVENT_GANG){
//杠,把牌放到特殊牌中,2秒后再摸牌
player->PutToSpecialCard(inCard,4);
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, player);
}
else{
//没事件,2秒后出牌
Dispatcher.DispatchMsg(2.0f, this, this, MSG_OUT_CARD, player);
}
}
//其他玩家处理出牌消息
void HandleCardOutEvent(MahjongCard *outCard, Player *player){
int ev = 0;
bool bHandelEvent = false;
for (int i = 0; i GetAI()->CardOutEvent(outCard, player);
//如果玩家触发了事件,玩家弹出对话框,其他的就不用处理了。
//其实按正常的逻辑,是先判断每个人的事件,然后对应的人去选择处理,选择完了后,按照胡牌优先的顺序处理事件。这里单机玩的,所以玩家优先。
if (ev >= 0x01 && mPlayers[i] == mMyPlayer){
mEventDialog->ShowEvent(ev);
mEventDialog->SetTag(player);
mCurrentState = WAIT_FOR_DIALOG;
mLastCard = outCard;
bHandelEvent = true;
break;
}
if (ev & EVENT_HU){
printf_s("%s----HU:%d\n", typeid(*mPlayers[i]).name(),i);
EVENT_EXTRA *extra = new EVENT_EXTRA();
extra->outPlayer = player;
extra->handlePlayer = mPlayers[i];
extra->outCard = outCard;
Dispatcher.DispatchMsg(2.0f, this, this, MSG_HU, extra);
bHandelEvent = true;
break;
}
else if (ev & EVENT_GANG){
bHandelEvent = true;
EVENT_EXTRA *extra = new EVENT_EXTRA();
extra->outPlayer = player;
extra->handlePlayer = mPlayers[i];
extra->outCard = outCard;
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GANG, extra);
}
else if (ev & EVENT_PENG){
bHandelEvent = true;
EVENT_EXTRA *extra = new EVENT_EXTRA();
extra->outPlayer = player;
extra->handlePlayer = mPlayers[i];
extra->outCard = outCard;
Dispatcher.DispatchMsg(2.0f, this, this, MSG_PENG, extra);
}
}
if (!bHandelEvent){
//没人触发事件,2秒后下一家摸牌
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, player->GetNextPlayer());
}
}
//弹出一张未知牌
MahjongCard *PopUnknownCard(){
MahjongCard *card = NULL;
int count = 0;
while (card == NULL)
{
card = mPlayers[mCurrentPopIndex]->PopUnknownCard();
if (card == NULL){
NextPopCardIndex();
count++;
//都没有牌可弹出
if (count >= 4)
break;
}
}
return card;
}
//洗牌
void RiffleCard(){
srand((uint32_t)time(NULL));
for (int i = 0; i < 300; i++){
int m = rand() % 136;
int n = rand() % 136;
//随机交换两个牌的位置;
int temp = mCard[m];
mCard[m] = mCard[n];
mCard[n] = temp;
}
//把交换后的牌分发到每一个玩家的未知牌里
for (int i = 0; i second;
switch (index){
case 0:
mMyPlayer->PutUnknowCard(card);
break;
case 1:
mNextPlayer->PutUnknowCard(card);
break;
case 2:
mOppositePlayer->PutUnknowCard(card);
break;
case 3:
mPrevPlayer->PutUnknowCard(card);
break;
}
}
}
}
virtual void LoadScene();
~MahjongScene(){
CardMap::iterator it = mCardPool.begin();
while (it != mCardPool.end()){
delete (it->second);
it++;
}
mCardPool.clear();
}
//鼠标事件
virtual void HandleEvent(SDL_Event &ev);
virtual void Update(float);
//自定义事件的处理
virtual bool OnMessage(const GameMessage &msg);
};
#include "stdafx.h"
#include "MahjongScene.h"
#include "../SDLGame/SDLGame.h"
#include "EventDialog.h"
#include "GameEndDialog.h"
#include "../SDLGame/GameMessage.h"
#include
void MahjongScene::LoadScene(){
mBackground = new Renderable();
if (mBackground->LoadTexture("background.png")){
this->AddRenderable(mBackground);
}
int windowWidth = theGame.GetWindowWidth();
int windowHeight = theGame.GetWindowHeight();
//我
mMyPlayer = new MyPlayer();
mMyPlayer->CreateRenderTexture(300, windowHeight-320, windowWidth-300, 320);
mMyPlayer->SetInHandCardPos(10, 200);
mMyPlayer->SetUnknownCardPos(85, 20);
mMyPlayer->SetOutCardPos(10, 100);
mMyPlayer->SetSpecialCardPos(0,0);
this->AddRenderable(mMyPlayer);
//下家
mNextPlayer = new NextPlayer();
mNextPlayer->CreateRenderTexture(windowWidth - 350, 0, 350, windowHeight);
mNextPlayer->SetInHandCardPos(300, windowHeight - 250);
mNextPlayer->SetUnknownCardPos(0, windowHeight - 250);
mNextPlayer->SetOutCardPos(100, windowHeight - 250);
this->AddRenderable(mNextPlayer);
//上家
mPrevPlayer = new PrevPlayer();
mPrevPlayer->CreateRenderTexture(0, 0, 350, windowHeight);
mPrevPlayer->SetInHandCardPos(50, 210);
mPrevPlayer->SetUnknownCardPos(314, 210);
mPrevPlayer->SetOutCardPos(220,210);
this->AddRenderable(mPrevPlayer);
//对家
mOppositePlayer = new OppositePlayer();
mOppositePlayer->CreateRenderTexture(0, 0, windowWidth, 300);
mOppositePlayer->SetInHandCardPos(windowWidth - 240, 30);
mOppositePlayer->SetUnknownCardPos(windowWidth - 415, 260);
mOppositePlayer->SetOutCardPos(windowWidth - 415, 180);
this->AddRenderable(mOppositePlayer);
mPlayers.push_back(mMyPlayer);
mPlayers.push_back(mNextPlayer);
mPlayers.push_back(mOppositePlayer);
mPlayers.push_back(mPrevPlayer);
mMyPlayer->SetNextPlayer(mNextPlayer);
mNextPlayer->SetNextPlayer(mOppositePlayer);
mOppositePlayer->SetNextPlayer(mPrevPlayer);
mPrevPlayer->SetNextPlayer(mMyPlayer);
mCurrentPopIndex = 0;
mCurrentGetIndex = 0;
//事件对话框
EventDialog *diag = new EventDialog();
diag->CreateRenderTexture(windowWidth - 368, windowHeight - 300, 368, 206);
diag->LoadButtons();
diag->SetVisible(false);
this->AddRenderable(diag);
diag->RegisterListener(this);
mEventDialog = diag;
//游戏结束对话框
GameEndDialog *endDialog = new GameEndDialog();
endDialog->CreateRenderTexture((windowWidth - 611)/2, (windowHeight - 600)/2, 611, 600);
endDialog->LoadButtons();
endDialog->SetVisible(false);
this->AddRenderable(endDialog);
endDialog->RegisterListener(this);
mGameEndDialog = endDialog;
}
//处理鼠标事件
void MahjongScene::HandleEvent(SDL_Event &ev){
int x = 0, y = 0;
switch (ev.type){
case SDL_MOUSEBUTTONDOWN:
if (ev.button.button == SDL_BUTTON_LEFT){
x = ev.button.x;
y = ev.button.y;
if (mCurrentState == WAIT_FOR_INPUT){
MahjongCard* card = mMyPlayer->HandleMouseEvent(ev.type, x, y);
if (card != NULL){
mMyPlayer->PopCardOut(card);
HandleCardOutEvent(card, mMyPlayer);
mCurrentState = RUNNING;
}
}
}
break;
case SDL_MOUSEMOTION:
x = ev.motion.x;
y = ev.motion.y;
mMyPlayer->HandleMouseEvent(ev.type, x, y);
break;
}
if (mCurrentState == WAIT_FOR_DIALOG){
mEventDialog->HandleEvent(ev);
}
else if (mCurrentState == WAIT_FOR_END_DIALOG){
mGameEndDialog->HandleEvent(ev);
}
}
void MahjongScene::Update(float time_step){
mTimeElapse += time_step;
if (mTimeElapse > 1.0f){
mTimeElapse -= 1.0f;
MahjongCard *card = NULL;
//printf("state:%d\n",mCurrentState);
switch (mCurrentState)
{
case START:
printf("start\n");
mCurrentState = RIFFLECARD;
break;
case RIFFLECARD:
printf("riffle card\n");
RiffleCard();
mCurrentState = SEND_CARD;
break;
case SEND_CARD:
//发牌
for (int i = 0; i PutCardIn(card,false);
printf("%d,",card->GetCardNumber());
if (mPlayers[mCurrentGetIndex]->GetInHandCardCount() == 13)
break;
}
printf("\n");
NextGetCardIndex();
if (IsSendCardComplete()){
mCurrentState = RUNNING;//(GameState)((int)MY_TURN + mCurrentGetIndex);
//mCurrentPlayer = mMyPlayer;
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, mMyPlayer);
}
break;
case RUNNING:
break;
case END:
printf("game end\n");
{
mGameEndDialog->SetVisible(true);
mCurrentState = WAIT_FOR_END_DIALOG;
}
break;
default:
break;
}
for (int i = 0; i Update(time_step);
}
}
}
//自定义消息处理
bool MahjongScene::OnMessage(const GameMessage &msg){
int ev = msg.message;
if (ev == MSG_GIVE_UP){
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, mCurrentPlayer->GetNextPlayer());
mEventDialog->SetVisible(false);
}
else if (ev == MSG_PENG){
EVENT_EXTRA *extra = (EVENT_EXTRA*)msg.ExtraInfo;
if (extra != NULL){
extra->outPlayer->RemoveOutCard();
extra->handlePlayer->PutToSpecialCard(extra->outCard, 2);
Dispatcher.DispatchMsg(2.0f, this, this, MSG_OUT_CARD, extra->handlePlayer);
}
else{
Player *lastPlayer = (Player*)mEventDialog->GetTag();
if (lastPlayer != NULL){
lastPlayer->RemoveOutCard();
}
mMyPlayer->PutToSpecialCard(mLastCard, 2);
mCurrentState = WAIT_FOR_INPUT;
mEventDialog->SetVisible(false);
mEventDialog->SetTag(NULL);
}
SAFE_DELETE(extra);
}
else if (ev == MSG_GANG){
EVENT_EXTRA *extra = (EVENT_EXTRA*)msg.ExtraInfo;
if (extra != NULL){
extra->outPlayer->RemoveOutCard();
extra->handlePlayer->PutToSpecialCard(extra->outCard, 3);
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, extra->handlePlayer);
}
else{
Player *lastPlayer = (Player*)mEventDialog->GetTag();
if (lastPlayer != NULL){
lastPlayer->RemoveOutCard();
}
mMyPlayer->PutToSpecialCard(mLastCard, 4);
Dispatcher.DispatchMsg(2.0f, this, this, MSG_GET_CARD, mMyPlayer);
mEventDialog->SetVisible(false);
mEventDialog->SetTag(NULL);
}
SAFE_DELETE(extra);
}
else if (ev == MSG_HU){
mCurrentState = END;
mEventDialog->SetVisible(false);
mEventDialog->SetTag(NULL);
for (int i = 0; i SetGameEnd(true);
}
}
else{
Player* player;
MahjongCard *card;
switch (ev)
{
case MSG_RESTART:
for (int i = 0; i Restart();
}
mCurrentState = START;
mGameEndDialog->SetVisible(false);
break;
case MSG_GET_CARD:
card = PopUnknownCard();
if (card == NULL){
mCurrentState = END;
break;
}
player = (Player*)msg.ExtraInfo;
player->PutCardIn(card);
mCurrentPlayer = player;
if (player == mMyPlayer){
mCurrentState = WAIT_FOR_INPUT;
int e = mMyPlayer->GetAI()->CardInEvent(card);
if (e > 0){
mLastCard = card;
mEventDialog->ShowEvent(e);
mEventDialog->SetTag(NULL);
mCurrentState = WAIT_FOR_DIALOG;
}
}
else{
HandleCardInEvent(player, card);
}
break;
case MSG_OUT_CARD:
player = (Player*)msg.ExtraInfo;
card = player->GetAI()->GetBestOutCard();
player->PopCardOut(card);
HandleCardOutEvent(card, player);
break;
}
}
return true;
}
效果图
本来以为这文档会很快写完,谁知到修修改改加上注释,花了将近4个小时的时间,写文档果然是不擅长啊。