斗地主游戏"/>
C语言实现斗地主游戏
我们先来实现主函数部分:
#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <dos.h>
#include <math.h>
#include <time.h>
#include <stdlib.h>#define CLS system("cls")
#define PUS system("pause")
#define CLR system("color 0f")
#define PR printf
#define SC scanf
#define UNDEALT_PUKER -32
#define EMPTY_CARD 63
#define GET fgets(FC,32,fp);sscanfvoid gotoxy(HANDLE HDO, int x, int y);
void printSmallPuker(int who, int n);
void getClientInfo();
void printPuker();
void AI(int who);
void initial();
void deal();
void _deal();
void over();
void clearInfo();
int home();
int play();
int AIplay();
int getMouseEvent();
int daipai(int n, int cmd[32]);
int shunzi_liandui(int n, int cmd[32]);
int isTherePuker(int special, int n, int cmd[32]);
int comparePuker(int recSpecial, int special, int recN, int n, int rec[32], int cmd[32]);char fBuffer[64];
char space[32] = " ";
char color[9] = "*&#%";
char table[16] = "3456789XJQKA2RR";
char playerLevel[8]; //玩家等级
char playerName[16]; //玩家名字
char player1[16]; //电脑名字
char player2[16]; //电脑名字
char consoleColor[32]; //窗口颜色
char consoleTitle[32]; //窗口标题
char dt[16]; //登录校验
char cd[64]; //存档校验
char FC[32]; //文件内容 fileContent
char LED[512]; //主页滚动显示
int col, line; //窗口大小
int basis; //底分
int extra[3]; //三张底牌
int LEDlength; //主页 LED 数组长度
int AIcmdNum; //AIcommandNumber,存储 AI 出牌数量用于打印,
int AIcmd[32]; //AIcommand,存储 AI 出的牌
int mouse[2]; //鼠标坐标
int mark; //分数
int quit;
int atHome;typedef struct{int num; //扑克点数:0->12分别代表3->2,13为王int color; //花色:0 ?,1 ?,2 ?,3 ?.int owner; //归属,0为未发放
} puker_property;
typedef struct{int card[20]; //持有的牌int num; //扑克牌数int rank; //总赢盘数int isOver; //是否已结束
} player_property;
typedef struct{int turn; //回合统计int basis; //回合倍数int who; //出牌权int special; //回合特殊牌型int record[3][32]; //农民玩家上回合记录int isPass[3]; //玩家是否出过牌int color; //最后一张牌的花色,纯属用于装饰int hasPrinted; //是否打印完成,用于防止循环不出
}pass_property;puker_property puker[64];
player_property player[3];
pass_property pass;INPUT_RECORD inRec; // 返回数据记录
DWORD numRead; // 返回已读取的记录数
HANDLE HDO = GetStdHandle(STD_OUTPUT_HANDLE); // 获得标准输出设备句柄
HANDLE HDI = GetStdHandle(STD_INPUT_HANDLE); // 获取标准输入设备句柄
HWND hwnd = FindWindow("ConsoleWindowClass", NULL); // 控制台窗口句柄
COORD position; // 存储使用Windows API获取的坐标。由于后面开始游戏那里要点击,所以定义为全局变量
COORD size = {100, 32}; // 存储窗口大小void gotoxy(HANDLE HDO, int x, int y) { //光标转移position.X = x;position.Y = y;SetConsoleCursorPosition(HDO, position);
}
/************* 存档读取 **************/
void getClientInfo() {FILE *fp; // 读取显示配置文件,具体参照 ACM 上的实验报告十三if ((fp = fopen("Data/client.ini", "r")) == NULL) { PR("存档丢失或被破坏, 请拷贝新存档或自己作假一个存档!\n"); PUS; exit(0); }GET(FC, "dt %s", dt); // 读取登录校验GET(FC, "cd %s", cd); // 读取存档校验GET(FC, "buffer %s", fBuffer); // 缓冲行GET(FC, "player1 %s", player1); // 电脑 1 的名字GET(FC, "player2 %s", player2); // 电脑 2 的名字fgets(consoleColor, 32, fp); // 颜色属性GET(FC, "playerName %s", playerName); // 玩家名字GET(FC, "basis %d", &basis); // 底分GET(FC, "title %s", consoleTitle); // 窗口标题fclose(fp); // 关闭文件SetConsoleTitle(consoleTitle); // 设置窗口标题SetConsoleScreenBufferSize(HDO, size); // 设置缓冲区大小mark = atoi(cd);if (strlen(playerName) > 16 || strlen(player1) > 16 || strlen(player2) > 16) {CLS; PR("\n\t注意:\n\t有玩家的名字过长。请重新设置名字,使其小于八个汉字。\n\t");PR("Length of nick exceeds the limit. Reset to control within 16 Bytes.\n\t");PUS; exit(0);}if (basis > 1000 ) {CLS; PR("\n\t注意:\n\t底分过高。为创造良好的游戏体验,请调低底分。\n\t");PR("Base score exceeds limit. Please lower it. \n\t");PUS; exit(0);}
}
/************* 游戏初始化 **************/
void initial() {int i, j;CONSOLE_CURSOR_INFO cursor_info = {1, 0}; // 定义一个光标结构体SetConsoleCursorInfo(HDO, &cursor_info); // 隐藏控制台光标getClientInfo(); //读取存档//初始化扑克牌for (i = 0; i < 13; i++) for (j = 0; j < 4; j++) {puker[i * 4 + j].num = i;puker[i * 4 + j].color = j;puker[i * 4 + j].owner = UNDEALT_PUKER;}puker[53].num = 14; // 大王赋值puker[52].num = 13; // 小王赋值puker[53].color = puker[52].color = 0;puker[53].owner = puker[52].owner = UNDEALT_PUKER; // 出过的、没发的牌都标记为 UNDEALT_PUKERpuker[EMPTY_CARD].num = UNDEALT_PUKER; // 空牌指定的位置也标记为 UNDEALT_PUKER//初始化玩家for (i = 0; i < 3; i++) { player[i].num = 0;player[i].rank = 0;player[i].isOver = 0;for (j = 0; j < 20; j++)player[i].card[j] = EMPTY_CARD;}//初始化出牌全局变量pass.who = 0;pass.turn = 0;pass.basis = 1;pass.special = 0;pass.hasPrinted = 0;for (i = 0; i < 3; i++) {for (j = 0; j < 32; j++) pass.record[i][j] = EMPTY_CARD; pass.isPass[i] = 0;}
}
/**************** 自动发牌 ***************/
void deal() {int i = 0, j, rnd = 0, temp;system(consoleColor);srand((unsigned)time(NULL));for (; i < 3; i++) { //抽出三张底牌rnd = rand() % 54;while (puker[rnd].owner != UNDEALT_PUKER) rnd = rand() % 54;puker[rnd].owner = 0;extra[i] = rnd;PR("%d \n", extra[i]);}for (i = 0; i < 54; i++) { //发掉所有牌if (puker[i].owner == UNDEALT_PUKER) {rnd = rand() % 3; // 产生 [0,3) 的随机整数while (player[rnd].num > 16) rnd = rand() % 3; // 若该玩家牌满,则重新选择牌主puker[i].owner = rnd;player[rnd].card[player[rnd].num] = i;player[rnd].num++;} else continue;}for (i = 0; i < 3; i++) { //将三张牌赋给地主player[0].card[17 + i] = extra[i];player[0].num++;}for (i = 0; i < 19; i++) //冒泡理牌,大->小for (j = 0; j < 19 - i; j++) // j开始等于0,if (player[0].card[j] > player[0].card[j + 1]) {temp = player[0].card[j];player[0].card[j] = player[0].card[j + 1];player[0].card[j + 1] = temp;}printPuker();
}
/**************** 调试发牌 ***************/
void _deal() { int i = 0, rnd = 0; for (i = 0; i < 54; i++) { rnd = i > 19 ? i > 36 ? 2 : 1 : 0; puker[i].owner = rnd; player[rnd].card[player[rnd].num] = i; player[rnd].num++; } printPuker(); } // 只在调试环境下作用!
char levelStandard[23][7] = {"包身工","短工","长工","佃户","贫农","渔夫","猎人","中农","富农","掌柜","商人","衙役","小财主","大财主","小地主","大地主","知县","通判","知府","总督","巡抚","丞相","帝王"};
int standard[23] = {0,1000,2500,4000,8000,14000,23000,36500,50000,70000,100000,150000,220000,300000,400000,550000,770000,1000000,1400000,2000000,3000000,4500000,7000000};
/**************** 打印手牌 ****************/
void printPuker() {CLS;char joker[4] = "王";char ch;int i = 0, j;int place;//-------======= 打印3张牌 ======------//gotoxy(HDO, 0, 0); PR("%35s┏━━┓┏━━┓┏━━┓\n", space);PR("%35s┃ ┃┃ ┃┃ ┃\n", space);PR("%35s┃ ┃┃ ┃┃ ┃\n", space);PR("%35s┃ ┃┃ ┃┃ ┃\n", space);PR("%35s┗━━┛┗━━┛┗━━┛", space);for (j = 0; j < 3; j++) {ch = table[puker[extra[j]].num];switch (ch) {case 'X':gotoxy(HDO, 37 + 8 * j, 1); PR("10");gotoxy(HDO, 39 + 8 * j, 3); PR("10");break;case 'R':gotoxy(HDO, 37 + 8 * j, 1);if (puker[extra[j]].num == 13) PR("小");else PR("大");gotoxy(HDO, 39 + 8 * j, 3); PR("王");break;default:gotoxy(HDO, 37 + 8 * j, 1); PR("%c", ch);gotoxy(HDO, 39 + 8 * j, 3); PR("%2c", ch);}gotoxy(HDO, 39 + 8 * j, 2);if (ch != 'R') PR("%c", color[puker[extra[j]].color]);}//-------======= 底分与倍数 ======------//gotoxy(HDO, 68, 0); PR("底分:%d", basis);gotoxy(HDO, 68, 1); PR("倍数:%d", pass.basis);//-------======= 打印牌框 ======------//if (player[0].num > 0) { //当手里有牌时执行gotoxy(HDO, 45 - 2 * player[0].num, 21); // 将牌框按轴对称对齐for (i = 0; i < player[0].num; i++) PR("┏━");PR("━━━┓");for (j = 22; j < 28; j++) {gotoxy(HDO, 45 - 2 * player[0].num, j);for (i = 0; i < player[0].num; i++) PR("┃ ");PR(" ┃");}gotoxy(HDO, 45 - 2 * player[0].num, 28);for (i = 0; i < player[0].num; i++) PR("┗━");PR("━━━┛");//-------======= 打印牌面 ======------//place = 19;for (i = player[0].num - 1; i >= 0; i--) { // 将牌面按轴对称对齐gotoxy(HDO, 43 + 2 * player[0].num - 4 * i, 23);PR("%2c", color[puker[player[0].card[i]].color]);gotoxy(HDO, 43 + 2 * player[0].num - 4 * i, 22);while (player[0].card[place] == EMPTY_CARD) place--;ch = table[puker[player[0].card[place]].num];switch (ch) {case 'X': PR("10"); break;default: PR("%2c", ch); break;case 'R': //判断是大王还是小王if (player[0].card[place] == 52) strcpy(space, "joker"); else strcpy(space, "JOKER");for (j = 22; j < 27; j++) {gotoxy(HDO, 43 + 2 * player[0].num - 4 * i, j);PR("%2c", space[j - 22]);}}place--;}//打印最后一张牌的牌面gotoxy(HDO, 50 + 2 * player[0].num, 26); PR("%c", color[puker[player[0].card[0]].color]);gotoxy(HDO, 49 + 2 * player[0].num, 27); PR("%2c", ch);}//-------======= 打印电脑 ======------//strcpy(space, " "); gotoxy(HDO, 0, 7); PR(" ┏━━━━┓%73s┏━━━━┓\n", space); // 打印,没什么借鉴意义for (j = 0; j < 6; j++) PR(" ┃┆ ┆┃%73s┃┆ ┆┃\n", space);PR(" ┗━━━━┛%73s┗━━━━┛\n", space);PR(" 农民:%6s%73s农民:%6s\n", player1, space, player2); PR(" 剩余: %2d 张%73s剩余: %2d 张\n", player[2].num, space, player[1].num);gotoxy(HDO, 3, 8); PR("┌┈┈┐"); gotoxy(HDO, 3, 13); PR("└┈┈┘"); gotoxy(HDO, 88, 8); PR("┌┈┈┐"); gotoxy(HDO, 88, 13); PR("└┈┈┘");gotoxy(HDO, 5, 10); PR("SIEE"); gotoxy(HDO, 90, 10); PR("SIEE"); gotoxy(HDO, 3, 11); PR("智慧信电"); gotoxy(HDO, 88, 11); PR("智慧信电");gotoxy(HDO, 25, 30); PR("地主:%s", playerName); gotoxy(HDO, 25, 31); PR("剩余:%2d 张", player[0].num);gotoxy(HDO, 60, 30); PR("等级:%s", playerLevel); gotoxy(HDO, 60, 31); PR("积分:%d", mark);//for (i = 2; i > 0; i--) {gotoxy(HDO, 20, 33 - i);for (j = 16; j >= 0; j--) {PR("%c", color[puker[player[i].card[j]].color]);PR("%c ", table[puker[player[i].card[j]].num]); }PR("\n");} // 电脑手牌,仅在调试环境下生效//-------======= 打印上回合记录 ======------//if (pass.turn != 0 ) for (j = 1; j < 3; j++) {if (pass.isPass[j] == 1 ) {gotoxy(HDO, 144 - 64 * j, 11);PR("不出");} else {for (i = 1; i < 32; i++)if (pass.record[j][i] == EMPTY_CARD) {place = i;break;}printSmallPuker(j, place);}} // 行列显示,仅在调试环境下作用//for (i = 0; i < 90; i++) { gotoxy(HDO, i, 0); if (i % 10 )PR("%d", i % 10); } for (i = 0; i < 31; i++) { gotoxy(HDO, 0, i); PR("%d", i); }
}
/********************** 玩家出牌 **************************/
int play() {char _cmd[32] = "";int cmd[32];int cmdNum = 0; // 记录每次出几张牌int recNum = 0; // 记录上一回合玩家出了几张牌int i = 0, j = 0, CtoI = 0; // CtoI 是字符转整形的临时变量int special = 0; // 记录特殊牌类型:具体含义如何映射在后文会有注释pass.isPass[0] = 0; // 初始化出牌标记for (; i < 32; i++) cmd[i] = EMPTY_CARD; // 初始化 cmdfor (; j < 32; j++) pass.record[0][j] = EMPTY_CARD; // 初始化 recordCLS;printPuker();gotoxy(HDO, 0, 0);PR("控制台\n\n");PR("输入出牌:");//-------======= 获取指令 ======------//if (!pass.who) gets(_cmd);cmdNum = strlen(_cmd); if (!cmdNum && pass.hasPrinted) { // 如果出牌数为 0 ,且上家都出完牌,则可以确定玩家选择了不出牌if (!pass.turn || pass.isPass[1] && pass.isPass[2]) {PR("你不能不出牌!小老弟\n"); PUS;return play();}gotoxy(HDO, 46, 19); PR("不出");pass.isPass[0] = 1; // 标记该玩家为不出for (j = 0; j < 32; j++) pass.record[0][j] = pass.record[2][j]; // 传递 recordpass.who++;return AIplay();}i = j = 0;while (i < cmdNum) {if ((_cmd[i] < '1' || _cmd[i] > '9') && _cmd[i] != 'J' && _cmd[i] != 'Q' && _cmd[i] != 'K' && _cmd[i] != 'A' && _cmd[i] != 'j' && _cmd[i] != 'q' && _cmd[i] != 'k' && _cmd[i] != 'a' && _cmd[i] != '-' && _cmd[i] != '=') {PR("错误:请输入正确的指令!\n"); PUS;return play();}//-----===== 扑克点数转换为数值 ====----//switch (_cmd[i]) {case '1': cmd[j] = 7; i++; break;case 'J': case 'j': cmd[j] = 8; break;case 'Q': case 'q': cmd[j] = 9; break;case 'K': case 'k': cmd[j] = 10; break;case 'A': case 'a': cmd[j] = 11; break;case '2': cmd[j] = 12; break;case '-': cmd[j] = 13; break;case '=': cmd[j] = 14; break;default: CtoI = (int)_cmd[i]; if (CtoI > 50 && i < 58) cmd[j] = CtoI - 51; break;}i++, j++;}cmdNum = j; //单次出牌张数//------------============ 判断指令是否可执行 ============------------//// 特殊牌类型:0无效值,1单牌,2一对,3三张,4炸,5顺子,6连对,7飞机,8三带一,9三带一对,10四带二,11四带两对,12飞机带N,13飞机带N对,14王炸// 1. 判断是否为特殊牌型special = daipai(cmdNum, cmd); // 判断是否为带牌牌型if (!special) special = shunzi_liandui(cmdNum, cmd); // 判断是否为顺子、连对、飞机if (cmdNum == 2 && cmd[0] > 12 && cmd[1] > 12) special = 14; //出王炸不校验出牌顺序isTherePuker(special, cmdNum, cmd); // 判断手上是否有足够的牌// 2. 当1 ~ 4张牌时定义 special 类型if (!special && cmdNum > 0 && cmdNum < 5) {for (i = 0; i < cmdNum - 1; i++)if (cmd[i] != cmd[i + 1]) {PR("出牌不符合规则,请重新出牌!\n");PUS; return play();}special = cmdNum;}// 3. 判断出牌是否符合出牌规则if (!special) {PR("出牌不符合规则,请重新出牌!\n");PUS; return play();}if (pass.isPass[1] + pass.isPass[2] < 2 && pass.turn) { // 若其余玩家出过牌for (i = 0; i < 32; i++) if (pass.record[2][i] == EMPTY_CARD) {recNum = i;break;}if (!comparePuker(pass.special, special, recNum, cmdNum, pass.record[2], cmd)) {PR("出牌不能大过上家,请重新出牌%d!\n", special);PUS; return play();}}pass.special = special;if (pass.special == 4 || pass.special == 14) { pass.basis *= 2; gotoxy(HDO, 68, 0); PR("底分:%d", basis);gotoxy(HDO, 68, 1); PR("倍数:%d", pass.basis);}// 4. 抽出扑克牌,刷新页面for (i = 0; i < cmdNum; i++)for (j = 0; j < 20; j++)if (puker[player[0].card[j]].num == cmd[i]) {if (!i) pass.color = puker[player[0].card[j]].color;puker[player[0].card[j]].owner = UNDEALT_PUKER;player[0].card[j] = EMPTY_CARD;player[0].num--;break;}pass.who ++; //传递出牌权if (pass.isPass[1] + pass.isPass[2] == 2 || !pass.turn) pass.special = special; //告知本次牌型for (i = 0; i < 32; i++) pass.record[0][i] = cmd[i]; //传递存储 cmdprintPuker();printSmallPuker(0, cmdNum); pass.hasPrinted = 1; // 出牌指令挂起,防止误不出牌// 5. 判断牌是否出完if (!player[0].num){player[0].isOver = 1;Sleep(800);for(i = 0; i < 3; i++) {gotoxy(HDO, 42, 11); PR(" ");//Sleep(500);gotoxy(HDO, 42, 11); PR("地 主 胜 利");//Sleep(500);}gotoxy(HDO, 38, 12); PUS;over();} else return AIplay();//PR("%d ", pass.special);PUS;return home();
}
/************************ 判断玩家出牌是否合法 ******************************/
int comparePuker(int recSpecial, int special, int recN, int n, int rec[32], int cmd[32]) {if (special > 0 && special < 14 && special != 4) { // 当牌型不为炸时,只需判断第一张牌谁大谁小if (recSpecial == special && rec[0] < cmd[0]) return 1;} else if (special == 4) { // 当牌型为 4 炸时,判断谁大谁小if (recSpecial != 4 && recSpecial != 14 || recSpecial == 4 && rec[0] < cmd[0]) return 1;} else if (special == 14) return 1; // 王炸碾压芸芸众生!!return 0;
}
/******************* 判断是否有牌 **********************/
int isTherePuker(int special, int n, int cmd[32]) {int paiXu[32]; //临时牌序数组paiXu[]int i, j, temp;// 判断指令牌序if (special < 8) {for (i = 0; i < n - 1; i++) {if (cmd[i] > cmd[i + 1]) {PR("请按大小顺序出牌!\n"); PUS;return play();}}}// 3. 判断是否有牌//为避免带牌乱序,先排序 (临时牌序数组paiXu[])for (i = 0; i < 32; i++) paiXu[i] = EMPTY_CARD;for (i = 0; i < n; i++) paiXu[i] = cmd[i];for (i = 0; i < n; i++)for (j = 0; j < n - i; j++) // j开始等于0,if (paiXu[j] > paiXu[j + 1]) {temp = paiXu[j];paiXu[j] = paiXu[j + 1];paiXu[j + 1] = temp;}i = j = 0;while (i < n && j < 20)while (j < 20) {if (puker[player[0].card[j]].num == paiXu[i]) {i++, j++;break;}j++;}if (i < n) {PR("请在已有牌基础上出牌!\n");PUS;return play();}return 0;
}
/************************** 判断顺子、连对或飞机 **************************/
int shunzi_liandui(int n, int cmd[32]) {int k;int _bool = 0;for (k = 0; k < n - 1; k++) //非连续, 或存在 2 和 joker, 直接 returnif (cmd[k] + 1 < cmd[k + 1]) return 0;for (k = 0; k < n; k++)if (cmd[k] > 11) return 0;if (n >= 5) { //顺子, 判断方法:各相邻牌是否相差且都相差 1 。k = 0;while (k < n - 1)if (cmd[k] + 1 == cmd[k + 1]) k++;else break;if (k == n - 1) return 5;}if (n >= 6 && n % 2 == 0) { //连对, 判断方法:是否两两相连。k = 0;while (k < n - 2)if (cmd[k] == cmd[k + 1] && cmd[k] + 1 == cmd[k + 2]) k += 2;else break;if (k == n - 2) {if (cmd[k] == cmd[k + 1]) return 6;else return 0;}}if (n >= 6 && n % 3 == 0) { //飞机, 判断方法:是否三三相连。k = 0;while (k < n - 3)if (cmd[k] == cmd[k + 1] && cmd[k + 1] == cmd[k + 2] && cmd[k] + 1 == cmd[k + 3]) k += 3;else return 0;if (k == n - 3) {if (cmd[k] == cmd[k + 1] && cmd[k + 1] == cmd[k + 2]) return 7;else return 0;}}return 0;
}
/*********** 判断三带一,三带一对,四带二,四带两对,飞机带 N,飞机带 N 对 ***********/
int daipai(int n, int cmd[32]) {int i;if (cmd[0] == cmd[1] && cmd[1] == cmd[2]) { //先判断前三张是否一样if (n == 4 && cmd[2] != cmd[3]) return 8; //三带一if (n == 5 && cmd[3] == cmd[4]) return 9; //三带二if (cmd[2] == cmd[3]) { //若第四张和第三张一样则晋升为四张if (n == 6 && cmd[4] != cmd[5]) return 10; //四带一if (n == 8 && cmd[4] == cmd[5] && cmd[6] == cmd[7] && cmd[5] != cmd[6]) return 11;//四带二}if (n > 4 && n % 4 == 0) { //判断飞机带 N 张i = 3;while (i < n - n / 4) {if (cmd[i] < 12 && cmd[i] == cmd[i + 1] && cmd[i + 1] == cmd[i + 2] && cmd[i - 1] + 1 == cmd[i]) i += 3;else return 0;}if (i == n - n / 4) {if (cmd[i] == cmd[n - 1]) return 0;else return 12;} }if (n > 5 && n % 5 == 0) { //判断飞机带 N 对i = 3;while (i < n - 2 * (n / 5)) {if (cmd[i] < 12 && cmd[i] == cmd[i + 1] && cmd[i + 1] == cmd[i + 2] && cmd[i - 1] + 1 == cmd[i]) i += 3;else return 0;}if (i == n - 2 * (n / 5)) {while (i < n) {if (cmd[i] == cmd[i + 1] && cmd[i - 1] != cmd[i]) i += 2;else return 0;}}if (i == n) return 13;}}return 0;
}
/******************************* 打印出牌缩略图 *********************************/
void printSmallPuker(int who, int n) {int i, j, temp;char ch;char flower[4];int smallPuker[32];int borderY, borderX, contentX;//根据不同出牌者定义打印坐标//以下是显示配置,无太大借鉴意义sw
更多推荐
C语言实现斗地主游戏
发布评论