最近雀魂玩的很多,于是决定自己写一个立直麻将的程序,进行机器学习生成ai。
立直麻将的规则太繁琐,我先实现了简单麻将的功能,包括:
1.建立牌海,实现随机洗牌
2.生成四位玩家,分发初始手牌
3.实现按序抓牌、打牌
4.实现胡牌检测,包括自摸和点炮
5.编写简单的切牌算法,为手中每张牌加权,每次切除最不需要的牌
其中1~4已经较完善,5还只是最无脑的算法,按牌型加权。
并且还不能吃碰杠。
后面可能会做一下图形化界面、人机交互。重头还是切牌算法,看能不能让深度学习一下。
2022/7/27更新
贴一下程序结果
发牌:
模切:
对局结果:自摸
对局结果:点炮
对局结果:荒牌流局
还不能读牌河、考虑番数等等,后面会做。
下面是代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/time.h>
#include<malloc.h>
//设置全局变量——玩家手牌数组
static int player1[15];
static int player2[15];
static int player3[15];
static int player4[15];
static int base[136] = {
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77
};
void MakeRand(int base[], int count)/*洗牌程序*/
{
int i;
srand((unsigned int)time(NULL));
for( i = 0; i < count-1 ; i++)
{
int num = i + rand()%(count -1-i);
int temp = base[i];
base[i] = base[num];
base[num] = temp;
}
//return base[146];
}
void decode(int num)/*单张牌面翻译打印程序*/
{
int typeword = (num-num%10)/10;
if(typeword == 1)
{printf(" %d", num%10);
printf("萬 ") ;}
else if(typeword == 3)
{printf(" %d", num%10);
printf("饼 ") ;}
else if(typeword == 5)
{printf(" %d", num%10);
printf("索 ") ; }
else
switch(num%10)
{
case 1:printf(" 東 ");break;
case 2:printf(" 南 ");break;
case 3:printf(" 西 ");break;
case 4:printf(" 北 ");break;
case 5:printf(" 白 ");break;
case 6:printf(" 發 ");break;
case 7:printf(" 中 ");break;
}
}
void AotoCollation(int nums[],int count)/*自动理牌程序*/
{
int i, j, temp, isSorted;
for(i=0; i<count-1; i++)
{
isSorted = 1; //假设剩下的元素已经排序好了
for(j=0; j<count-1-i; j++)
{
if(nums[j] > nums[j+1])
{
temp = nums[j];
nums[j] = nums[j+1];
nums[j+1] = temp;
isSorted = 0;
}
}
if(isSorted) break;
}
}
void getcard(int hand[14], int playernum, int nowget )/*抓第nextcard张牌*/
{
int k;
hand[13]=nowget;
printf("\n\n手牌 for Player%d: 摸到↓\n",playernum);
int i;
for ( i = 0; i < 14; i++)
decode(hand[i]);
if(playernum==1)
{
for(k=0;k<14;k++){player1[k]=hand[k];}
}
if(playernum==2)
{
for(k=0;k<14;k++){player2[k]=hand[k];}
}
if(playernum==3)
{
for(k=0;k<14;k++){player3[k]=hand[k];}
}
if(playernum==4)
{
for(k=0;k<14;k++){player4[k]=hand[k];}
}
}
int putcard(int hand[],int playernum, int putnum) /*打出选中的牌,然后自动理牌*/
{
int k;
int temp = 0;
temp = hand[putnum-1];
hand[putnum-1] = 99;
printf(" 打出--->");
decode(temp);
AotoCollation(hand, 14);
if(playernum==1)
{
for(k=0;k<13;k++){player1[k]=hand[k];}
}
if(playernum==2)
{
for(k=0;k<13;k++){player2[k]=hand[k];}
}
if(playernum==3)
{
for(k=0;k<13;k++){player3[k]=hand[k];}
}
if(playernum==4)
{
for(k=0;k<13;k++){player4[k]=hand[k];}
}
return temp;
}
int hupai(int hand[15])/*判断14张牌,胡返回1,否则返回0*/
{
AotoCollation(hand,14);
int temphand[15];
int temphandnew[15];
int i,j,m,n=0,x,y;
int temp = 0, addup = 0 ;
int count1 = 0, count2 = 0, rest = 12;
int grouphead;
/*for(i=0;i<14;i++)
{
scanf("%d",&hand[i]);
}*/
for(i=0;i<13;i++)
{
temp = hand[i];
for(j=i+1;j<i+2;j++)
{
for(x=0;x<14;x++)
{
temphand[x] = hand[x];
}
if(hand[j]==temp)//找到了head
{
count1++;
temphand[i] = 0; temphand[j] = 0;//去掉head继续判断
for(x=i+2;x<15;x++)
{
temphand[x-2]=temphand[x];
}
rest=12;
for(m=0;m<12;m++)
{
if(temphand[m]>0)
{
grouphead = temphand[m];
count2=0;
if(temphand[m+1]==grouphead)//刻子
{
count2++;
if(temphand[m+2]==grouphead)
{
count2++;
if(count2==2)
{
rest=rest-3;
temphand[m]=0;
temphand[m+1]=0;
temphand[m+2]=0;
continue;
}
}
}
count2=0;
for(x=m+1;x<15;x++)
{
if(m-(m%10)/10==7)continue;
if(temphand[x]==grouphead+1)//顺子
{
count2++;
for(y=x+1;y<15;y++)
{
if(temphand[y]==grouphead+2)
{
count2++;
if(count2==2)
{
rest=rest-3;
temphand[m]=0;
temphand[x]=0;
temphand[y]=0;
}
}
}
}
}
/*for(x=0;x<13;x++)
{
printf("%d ",temphand[x]);
}
printf("\n");*/
}
}
//printf("%d",rest);
if(rest==0)
{
//printf("\n荣胡!!!");
return 1;
}
}
}
}
//printf("\nNot ting...");
return 0;
}
int value(int inthand[])
{
//判断14张手牌每张牌价值,并按价值排序
//价值取决于形成雀头或面子的难易度
/*万11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
饼31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
索51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
字71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77*/
int i,j,m,n,x,y;
int hand[20]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int hand2[20]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};//便于顺子判断
int value2[15]={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14};
/*for(n=0;n<14;n++)
{
scanf("%d",&inthand[n]);
}*/
for(i=0;i<14;i++)
{
hand[i]=inthand[i];
hand2[i]=inthand[i];
}
int value[20]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int now;
//基本价值
for(i=0;i<14;i++)
{
j=i;
if(hand[j]==11||hand[j]==19||hand[j]==31||hand[j]==39||hand[j]==51||hand[j]==59) {value[j]=value[j]+5;}
if(hand[j]==12||hand[j]==18||hand[j]==32||hand[j]==38||hand[j]==52||hand[j]==58) {value[j]=value[j]+10;}
if(hand[j]==13||hand[j]==17||hand[j]==33||hand[j]==37||hand[j]==53||hand[j]==57) {value[j]=value[j]+20;}
if(hand[j]==14||hand[j]==15||hand[j]==16||hand[j]==34||hand[j]==35||hand[j]==36||hand[j]==54||hand[j]==55||hand[j]==56) {value[j]=value[j]+30;}
if(hand[j]==71||hand[j]==72||hand[j]==73||hand[j]==74) {value[j]=value[j]+0;}
if(hand[j]==75||hand[j]==76||hand[j]==77) {value[j]=value[j]+5;}
}
//对子
for(x=0;x<7;x++)
{
for(i=0;i<13;i++)
{
if(hand2[i]==hand2[i+1] && hand2[i+1]!=hand2[i+2] && hand2[i]!=hand2[i-1]
&& (hand2[i]==hand2[i+2]-1 && hand[i]==hand2[i+3]-2)!=1//11 11 12 13
&& (hand2[i]==hand2[i-1]+1 && hand[i]==hand2[i-2]+2)!=1)//11 12 13 13
{
if(hand[i]==hand[i+1] && hand[i+2]==hand[i+3] && hand[i+4]==hand[i+5])//一杯口
{
value[i]=value[i]+80;
value[i+1]=value[i+1]+80;
value[i+2]=value[i+2]+80;
value[i+3]=value[i+3]+80;
value[i+4]=value[i+4]+80;
value[i+5]=value[i+5]+80;
for(j=0;j<8-i;j++) //去掉一杯口
{
hand2[j+i]=hand2[j+i+6];
value2[j+i]=value2[j+i+6];
}
}
if(hand2[i]==71,72,73,74,75,76,77)
{
value[value2[i]]=value[value2[i]]+35;
value[value2[i+1]]=value[value2[i+1]]+35;
}//白 白
else
{
value[value2[i]]=value[value2[i]]+30;
value[value2[i+1]]=value[value2[i+1]]+30;
}
if(hand2[i]==hand2[i-1]+1 && hand2[i+1]==hand2[i+2]-1) //11 12 12 13 去掉一个 12
{
for(j=1;j<14-i;j++)
{
hand2[j+i]=hand2[j+i+1];
value2[j+i]=value2[j+i+1];
}
}
else for(j=0;j<14-i;j++) //去掉孤立的对子
{
hand2[j+i]=hand2[j+i+2];
value2[j+i]=value2[j+i+2];
}
}
}
}
//刻子11 11 11, 杠暂定无价值
for(x=0;x<3;x++)
{
for(i=0;i<12;i++)
{
if(hand2[i-1]!=hand2[i] && hand2[i]==hand2[i+1] && hand2[i+1]==hand2[i+2] && hand2[i]!=0)
{
value[value2[i]]=value[value2[i]]+60;
value[value2[i+1]]=value[value2[i+1]]+60;
value[value2[i+2]]=value[value2[i+2]]+60;
if(hand2[i]==hand2[i-1]+1 && hand2[i+2]==hand2[i+3]-1)//11 12 12 12 13 给12 12对子的价值然后去掉12 12
{
value[value2[i]]=value[value2[i]]-60+30;
value[value2[i+1]]=value[value2[i+1]]-60+30;
value[value2[i+2]]=value[value2[i+2]]-60;
for(j=1;j<12-i;j++)
{
hand2[j+i]=hand2[j+i+2];
value2[j+i]=value2[j+i+2];
}
}
else for(j=0;j<14-i;j++)
{
hand2[j+i]=hand2[j+i+3];
value2[j+i]=value2[j+i+3];
}
}
}
}
for(i=0;i<14;i++)//字牌的顺子不算顺子
{
if(hand2[i]-(hand2[i]%10)==70)
{
hand2[i]=0;
}
}
//顺子诸多长度
//顺子在理牌后容易被分割,对子刻子部分已经去掉形如“11 12 12 12 13”结构中的多余对子
for(i=0;i<12;i++)
{
if(hand2[i]+1==hand2[i+1] && hand2[i+1]+1==hand2[i+2] && hand2[i]!=hand2[i-1]+1)//确保hand[i]是顺子的头部
{
value[value2[i]] = value[value2[i]]+45;
value[value2[i+1]] = value[value2[i+1]]+45;
value[value2[i+2]] = value[value2[i+2]]+45;
for(j=0;j<14-i;j++)
{
hand2[j+i]=hand2[j+i+3];
value2[j+i]=value2[j+i+3];
}
}
}
//14 15
for(i=0;i<12;i++)
{
if(hand2[i]==hand2[i+1]-1 && hand2[i]!=0)
{
value[value2[i]]=value[value2[i]]+30;
value[value2[i+1]]=value[value2[i+1]]+30;
}
}
//14 16
for(i=0;i<11;i++)
{
if(hand2[i]==hand2[i+1]-2 && hand2[i+1]!=0)
{
value[value2[i]]=value[value2[i]]+20;
value[value2[i+1]]=value[value2[i+1]]+20;
}
}
printf("\n");
for(n=0;n<14;n++)
{
if(value[n]<10)
{
printf(" %d ",value[n]);
}
else if(value[n]>99)
{
printf(" %d ",value[n]);
}
else
printf(" %d ",value[n]);
}
printf("<---Value");
int cardnum[14]={0,1,2,3,4,5,6,7,8,9,10,11,12,13};
for(x=0;x<13;x++)
{
if(value[x]<value[x+1])
{
value[x+1]=value[x];
cardnum[x+1]=cardnum[x];
}
}
//printf("cardnum[13]=%d",cardnum[13]);
return cardnum[13]+1;
}
int drive(int lun)//四人游戏驱动程序
{
printf("\n\n----第%d轮摸切开始----\n\n",(lun+1));
int nextcard = 52+4*lun;
int waiter,i,j,m,nowplayer;
int output;
int win=0;
for(i=1;i<=4;i++)
{
//printf("P");
if(nextcard+i==123)//荒牌流局
{
win=2;
return win;
}
if(i==1){getcard(player1, 1, base[nextcard+1]); nowplayer=hupai(player1);}
if(i==2){getcard(player2, 2, base[nextcard+2]); nowplayer=hupai(player2);}
if(i==3){getcard(player3, 3, base[nextcard+3]); nowplayer=hupai(player3);}
if(i==4){getcard(player4, 4, base[nextcard+4]); nowplayer=hupai(player4);}
printf("胡牌状态:");
if(nowplayer==1)
{
printf("自摸!!!");
win=1;
return win;
}
else {printf("没胡...");}
//scanf("%d",&waiter);
if(i==1){
AotoCollation(player1,14);
printf("\n");
for (j = 0; j < 14; j++)
decode(player1[j]);
printf("<---摸牌后自动理牌");
output=putcard(player1, 1, value(player1));//暂时固定打value算法认定的最无价值的一张牌
if(dianpao(1,output)==1){win=1;return win;}//判断是否放铳
printf("\n");
for ( m = 0; m < 13; m++)
{
decode(player1[m]);
}
printf("<---回合结束");
printf("\n\n");
}
if(i==2)
{
AotoCollation(player2,14);
printf("\n");
for (j = 0; j < 14; j++)
decode(player2[j]);
printf("<---摸牌后自动理牌");
output=putcard(player2, 2, value(player2));
if(dianpao(2,output)==1){win=1;return win;}
printf("\n");
for ( m = 0; m < 13; m++)
{
decode(player2[m]);
}
printf("<---回合结束");
printf("\n\n");
}
if(i==3)
{
AotoCollation(player3,14);
printf("\n");
for (j = 0; j < 14; j++)
decode(player3[j]);
printf("<---摸牌后自动理牌");
output=putcard(player3, 3, value(player3));
if(dianpao(3,output)==1){win=1;return win;}
printf("\n");
for ( m = 0; m < 13; m++)
{
decode(player3[m]);
}
printf("<---回合结束");
printf("\n\n");
}
if(i==4)
{
AotoCollation(player4,14);
printf("\n");
for (j = 0; j < 14; j++)
decode(player4[j]);
printf("<---摸牌后自动理牌");
output=putcard(player4, 4, value(player4));
if(dianpao(4,output)==1){win=1;return win;}
printf("\n");
for ( m = 0; m < 13; m++)
{
decode(player4[m]);
}
printf("<---回合结束");
printf("\n\n");
}
}
}
int dianpao(int playernum, int dianpaopai)
{
int i,m;
int win1,win2,win3,win4;
int temp_player1[14];
int temp_player2[14];
int temp_player3[14];
int temp_player4[14];
for(i=0;i<14;i++)
{
temp_player1[i]=player1[i];
}
temp_player1[13]=dianpaopai;
for(i=0;i<14;i++)
{
temp_player2[i]=player2[i];
}
temp_player2[13]=dianpaopai;
for(i=0;i<14;i++)
{
temp_player3[i]=player3[i];
}
temp_player3[13]=dianpaopai;
for(i=0;i<14;i++)
{
temp_player4[i]=player4[i];
}
temp_player4[13]=dianpaopai;
if(playernum==1)
{
win2=hupai(temp_player2);
win3=hupai(temp_player3);
win4=hupai(temp_player4);
}
if(playernum==2)
{
win1=hupai(temp_player1);
win3=hupai(temp_player3);
win4=hupai(temp_player4);
}
if(playernum==3)
{
win1=hupai(temp_player1);
win2=hupai(temp_player2);
win4=hupai(temp_player4);
}
if(playernum==4)
{
win1=hupai(temp_player1);
win2=hupai(temp_player2);
win3=hupai(temp_player3);
}
if(win1==1||win2==1||win3==1||win4==1)
{
printf("\n放铳!!Player%d--->",playernum);
if(win1==1)
{
printf("Player1!!\n胡牌牌型为:");
for ( m = 0; m < 13; m++)
{
decode(player1[m]);
}
printf("+");
}
if(win2==1)
{
printf("Player2!!\n胡牌牌型为:");
for ( m = 0; m < 13; m++)
{
decode(player2[m]);
}
printf("+");
}
if(win3==1)
{
printf("Player3!!\n胡牌牌型为:");
for ( m = 0; m < 13; m++)
{
decode(player3[m]);
}
printf("+");
}
if(win4==1)
{
printf("Player4!!\n胡牌牌型为:");
for ( m = 0; m < 13; m++)
{
decode(player4[m]);
}
printf("+");
}
decode(dianpaopai);
return 1;
}
else return 0;
}
主函数部分
int main()
{
int i=0;
int ii=0;
srand((unsigned)time(NULL));
/*整理全部牌*/
/*int base[136] = {
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
11,12, 13, 14, 15, 16, 17, 18, 19,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
31,32, 33, 34, 35, 36, 37, 38, 39,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
51,52, 53, 54, 55, 56, 57, 58, 59,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77,
71,72, 73, 74, 75, 76, 77
};综合考虑,将牌库设为了全局变量*/
/*4个玩家*/
//int player1[14], player2[14], player3[14], player4[14];
MakeRand (base,136);
/*发牌*/
for (i = 0; i<13;i++)
{
player1[i] = base[ii];
ii++;
}
for (i = 0; i<13;i++)
{
player2[i] = base[ii];
ii++;
}
for (i = 0; i<13;i++)
{
player3[i] = base[ii];
ii++;
}
for (i = 0; i<13;i++)
{
player4[i] = base[ii];
ii++;
}
AotoCollation (player1,13);
AotoCollation (player2,13);
AotoCollation (player3,13);
AotoCollation (player4,13);
printf("手牌 for Player1:\n");
for (i = 0; i < 13; i++)
decode(player1[i]);
printf("\n\n");
printf("手牌 for Player2:\n");
for ( i = 0; i < 13; i++)
decode(player2[i]);
printf("\n\n");
printf("手牌 for Player3:\n");
for ( i = 0; i < 13; i++)
decode(player3[i]);
printf("\n\n");
printf("手牌 for Player4:\n");
for ( i = 0; i < 13; i++)
decode(player4[i]);
int dora1 = base[126];
printf("\n\nDORA1指示牌 :");
decode(dora1);
int gameresult;
for(i=0;i<30;i++)
{
gameresult=drive(i);
if(gameresult==2){printf("\n荒牌流局!"); return 0;}
if(gameresult==1){return 0;}
}
}