闲来无事,发现命令行上的游戏较少,便想模仿FC双人麻将,写一个麻将游戏,使用键盘输入数字控制出牌,如第一张就输入1,电脑和玩家可以捉炮和自摸,可以胡n×AAA + m×ABC + DD和七对,没有吃碰杠,玩家打出了30张牌就流局。
运行效果(DOS):

代码如下
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int paishan[10][5];
char echo_hua[6]={' ','W','T','S','Z'}; /*万,筒,条,字*/
char echo_num[11]={' ','1','2','3','4','5','6','7','8','9'};
int she_pc_num = 1;
int she_me_num = 1;
struct me{
int hua;
int num;
} me[15];
struct pc{
int hua;
int num;
} pc[15];
struct she_pc{
int hua;
int num;
} she_pc[31];
struct she_me{
int hua;
int num;
} she_me[31];
void all_init(void)
{
int i,j;
for(i=1;i<5;i++)
{
for(j=1;j<10;j++)
{
paishan[j][i]=4;
}
}
paishan[8][4]=0;
paishan[9][4]=0;
}
int rand_range(int min, int max)
{
return min + rand() % (max - min + 1);
}
void fapai_pc(void)
{
int i,j,tmp_hua,tmp_num;
for(i=1;i<11;i=i+3)
{
switch(rand_range(1,2)) /*电脑组建手牌,1为顺,2为刻,听牌雀头*/
{
case 1:
shun:
tmp_hua=rand_range(1,3);
tmp_num=rand_range(1,7);
for(j=0;j<3;j++)
{
if(paishan[tmp_num + j][tmp_hua] < 1)
{
goto shun;
}
}
for(j=0;j<3;j++)
{
pc[i+j].hua=tmp_hua;
pc[i+j].num=tmp_num+j;
paishan[tmp_num+j][tmp_hua]--;
}
break;
case 2:
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
while(paishan[tmp_num][tmp_hua]<3)
{
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
}
for(j=0;j<3;j++)
{
pc[i+j].hua=tmp_hua;
pc[i+j].num=tmp_num;
paishan[tmp_num][tmp_hua]--;
}
}
}
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
while(paishan[tmp_num][tmp_hua]<1)
{
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
}
pc[13].hua=tmp_hua;
pc[13].num=tmp_num;
paishan[tmp_num][tmp_hua]--;
}
int fapai(int a,int b)
{
int tmp_hua,tmp_num,i,j;
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
while(tmp_hua==4 && tmp_num>7) /*字牌只有7种*/
{
tmp_hua=rand_range(1,4);
tmp_num=rand_range(1,9);
}
if(paishan[tmp_num][tmp_hua]>0)
{
switch(a)
{
case 1: /*玩家摸牌*/
me[b].hua=tmp_hua;
me[b].num=tmp_num;
return 0;
break;
case 2: /*PC不允许胡牌*/
she_pc[she_pc_num].hua=tmp_hua;
she_pc[she_pc_num].num=tmp_num;
return 0;
break;
case 3: /*PC允许胡牌*/
if(pc[13].hua==tmp_hua && pc[13].num==tmp_num)
{
pc[14].hua=tmp_hua;
pc[14].num=tmp_num;
printf("%s\n", "You lose");
fflush(stdout);
for(i=1;i<15;i++)
{
printf("%c", echo_num[pc[i].num]);
printf("%c", echo_hua[pc[i].hua]);
printf("%c", ' ');
}
exit(0);
}
else
{
she_pc[she_pc_num].hua=tmp_hua;
she_pc[she_pc_num].num=tmp_num;
return 0;
}
break;
}
}
else
{
return 1;
}
}
void sort_hand(void) /*冒泡排序*/
{
int i,j,tmp;
for(i = 1; i < 14; i++)
{
for(j = 1; j < 15 - i; j++)
{
if( me[j].hua > me[j+1].hua || (me[j].hua == me[j+1].hua && me[j].num > me[j+1].num) )
{
tmp = me[j].hua;
me[j].hua = me[j+1].hua;
me[j+1].hua = tmp;
tmp = me[j].num;
me[j].num = me[j+1].num;
me[j+1].num = tmp;
}
}
}
}
void read_int(int *out)
{
int ret;
while(1)
{
ret = scanf("%d", out);
if(ret == 1)
{
while(getchar() != '\n');
break;
}
while(getchar() != '\n');
printf("no:");
}
}
void screen(void)
{
int i,j;
for(i = 0; i < 40; i++)
{
printf("\n");
}
/*
for(i=1;i<15;i++)
{
printf("%c", echo_num[pc[i].num]);
printf("%c", echo_hua[pc[i].hua]);
printf("%c", ' ');
}
printf("\n");
*/
/*电脑手牌打印*/
printf("%s\n", "================================");
printf("%s\n", "PC:");
for(i=1;i<4;i++)
{
for(j=1;j<11;j++)
{
int tmp_num = (i-1)*10 + j;
printf("%c", echo_num[she_pc[tmp_num].num]);
printf("%c", echo_hua[she_pc[tmp_num].hua]);
printf("%c", ' ');
}
printf("\n");
}
printf("%s\n", "================================");
printf("%s\n", "YOUR:");
for(i=1;i<4;i++)
{
for(j=1;j<11;j++)
{
int tmp_num = (i-1)*10 + j;
printf("%c", echo_num[she_me[tmp_num].num]);
printf("%c", echo_hua[she_me[tmp_num].hua]);
printf("%c", ' ');
}
printf("\n");
}
printf("%s\n", "================================");
for(i=1;i<15;i++)
{
printf("%c", echo_num[me[i].num]);
printf("%c", echo_hua[me[i].hua]);
printf("%c", ' ');
}
printf("\n");
printf("%s\n", "01 02 03 04 05 06 07 08 09 10 11 12 13 14");
}
int check_qidui(int check[10][5])
{
int i,j;
int dui_cnt = 0;
for(i = 1; i < 5; i++)
{
for(j = 1; j < 10; j++)
{
int c = check[j][i];
if(c == 0)
continue;
if(c == 2)
{
dui_cnt ++;
}
else
{
/*出现1、3、4,七对不成立*/
return 0;
}
}
}
if(dui_cnt ==7)
{
printf("%s\n", "You win");
fflush(stdout);
exit(0);
}
return 0;
}
int check_mianzi(int cnt[10][5])
{
int i,j;
for(i=1;i<5;i++)
{
for(j=1;j<10;j++)
{
if(cnt[j][i]>0)
{
if(cnt[j][i]>=3) /*检测刻子*/
{
cnt[j][i]=cnt[j][i]-3;
if(check_mianzi(cnt))
{
return 1;
}
cnt[j][i]=cnt[j][i]+3;
}
if(i!=4 && j<=7 && cnt[j][i]>0 && cnt[j+1][i]>0 && cnt[j+2][i]>0) /*检测顺子*/
{
cnt[j][i]=cnt[j][i]-1;
cnt[j+1][i]=cnt[j+1][i]-1;
cnt[j+2][i]=cnt[j+2][i]-1;
if(check_mianzi(cnt))
{
return 1;
}
cnt[j][i]=cnt[j][i]+1;
cnt[j+1][i]=cnt[j+1][i]+1;
cnt[j+2][i]=cnt[j+2][i]+1;
}
return 0;
}
}
}
return 1;
}
int check_hupai(int a)
{
int check[10][5];
int tmp_check[10][5];
int i,j,m,n;
switch(a)
{
case 1: /*捉炮*/
for(i=1;i<5;i++)
{
for(j=1;j<10;j++)
{
check[j][i]=0;
}
}
check[she_pc[she_pc_num-1].num][she_pc[she_pc_num-1].hua]++;
for(i=2;i<15;i++)
{
check[me[i].num][me[i].hua]++;
}
break;
case 2: /*自摸*/
for(i=1;i<5;i++)
{
for(j=1;j<10;j++)
{
check[j][i]=0;
}
}
for(i=1;i<15;i++)
{
check[me[i].num][me[i].hua]++;
}
break;
}
check_qidui(check);
for(m=1;m<5;m++)
{
for(n=1;n<10;n++)
{
tmp_check[n][m]=check[n][m];
}
}
for(i=1;i<5;i++)
{
for(j=1;j<10;j++)
{
if(check[j][i]>=2)
{
for(m=1;m<5;m++)
{
for(n=1;n<10;n++)
{
tmp_check[n][m]=check[n][m];
}
}
tmp_check[j][i]=tmp_check[j][i]-2;
if(check_mianzi(tmp_check))
{
printf("%s\n", "You win");
fflush(stdout);
exit(0);
}
}
}
}
}
int main(void)
{
int i,j;
int chose = 0;
int state = 1;
int pc_hu_time;
srand((unsigned)time(NULL));
all_init();
fapai_pc();
for(i=1;i<14;i++)
{
while(fapai(1,i));
}
sort_hand();
pc_hu_time = rand_range(1,20);
while (she_me_num <= 30)
{
switch (state)
{
case 1: /*PC*/
if(she_me_num < pc_hu_time)
{
while(fapai(2,0)); /*直接舍牌*/
she_pc_num++;
}
else
{
while(fapai(3,0)); /*允许胡牌*/
she_pc_num++;
}
check_hupai(1);
state=2;
break;
case 2: /*玩家*/
while(fapai(1,1));
screen();
check_hupai(2);
read_int(&chose);
she_me[she_me_num].hua = me[chose].hua;
she_me[she_me_num].num = me[chose].num;
me[chose].hua = 0;
me[chose].num = 0;
if(pc[13].hua==she_me[she_me_num].hua && pc[13].num==she_me[she_me_num].num && she_me_num > pc_hu_time) /*电脑捉炮*/
{
pc[14].hua=she_me[she_me_num].hua;
pc[14].num=she_me[she_me_num].num;
printf("%s\n", "You lose");
fflush(stdout);
for(i=1;i<15;i++)
{
printf("%c", echo_num[pc[i].num]);
printf("%c", echo_hua[pc[i].hua]);
printf("%c", ' ');
}
exit(0);
}
else
{
sort_hand();
she_me_num++;
state=1;
}
break;
}
}
printf("%s\n", "Draw!"); /*流局*/
fflush(stdout);
}
机制讲解
使用W代表万,T代表筒,S代表条,Z是字牌(东南西北中发白)。电脑随机组建n×AAA + m×ABC + D的手牌,听牌D,之后随机一个胡牌时间,当玩家舍牌达到预定个数时,电脑就允许胡牌(需要自摸或放炮,不是直接胡牌),在此之前,电脑直接舍牌。电脑胡牌时,程序会显示“You lose”,下一行显示电脑手牌。玩家胡牌时,程序显示“You win”。
胡牌判断
先判断七对,就是判断手牌中是否有7个相同的对子。
n×AAA + m×ABC + DD的判断较为复杂,首先将手牌中每一种大于两张的牌尝试设为雀头,剩下的牌通过递归,看剩下能不能全部拆成面子,如果可以就胡牌。
递归算法完整流程
双重循环找到第一张还存在的牌 cnt[j][i]>0;
从最小的牌开始处理,不做排列组合,避免重复搜索。
尝试刻子:如果 >=3 张,扣掉 3 张,递归继续拆解剩下;如果递归返回 1;失败:回溯,把 3 张加回来,恢复数组状态,尝试下一种可能性。
尝试顺子:字牌 (i=4) 不能组成顺子;数字 j 不能大于 7,防止 j+2 越界;需要同时有 j、j+1、j+2 三张同花色;各扣掉一张,递归;失败就回溯,三张加回来。
如果刻子、顺子两种路径全部失败 → 当前分支无解,return 0。
递归出口:循环走完,没有任何牌cnt>0 → 所有牌拆解完毕 return 1
编译
Linux(openwrt_mipsel):
mipsel-openwrt-linux-musl-gcc -O3 maj.c -o maj -I. -lm
DOS(使用Turbo C):
1.打开tc.exe,载入maj.c
2.按F10,用键盘将光标移动到Compile,回车,移动到Make EXE file,回车


298

被折叠的 条评论
为什么被折叠?



