[C] 五子棋Gobang
DATE: 2018-10
PS: 之前的Git Page 在国内已经全面被墙了,因此迁移到这里
部分代码已遗失,sad~
C基础课外编程项目——五子棋
在控制台命令行实现五子棋
IDE Code::Block GCC 8.10 Win32
基础版命令行五子棋
- 命令行输入坐标下棋
- 命令行显示棋盘过程(刷新使用全局刷新cls)
- 能自动判断输赢
代码实现(分为三个部分)
- 棋盘类
- 裁判类
- 显示函数
头文件部分
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <stdlib.h>//用来调用system函数
棋盘类
typedef struct GobangBoard{//定义五子棋类
int Width,Height; //定义棋盘的长和宽
short Graph[maxSize][maxSize]; //棋盘图形
}Board;
void initBoard(Board * thisB,int Height,int Width);//定义初始化
void displayBoard(Board * thisB,int shiftSize); //打印棋盘,shiftSize棋盘向右偏移的位置,为了居中
裁判类
typedef struct autoReferee{//定义游戏裁判
int turn; //回合
short firstIs; //定义先下的一方,0是黑方,1是白方
short nowIs; //定义当前下的一方
int endX,endY; //最后一步的位置
}BoardRef;
void initRef(BoardRef * thisB); //初始化棋盘裁判
基础版完整实现代码
//定义变量temp开头为临时变量
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <stdlib.h>
#define maxSize 21 //棋盘最大的大小
#define defaultShiftSize 0 //输出右偏移的格数
typedef struct GobangBoard{//定义五子棋类
int Width,Height; //定义棋盘的长和宽
short Graph[maxSize][maxSize]; //棋盘图形
}Board;
typedef struct autoReferee{//定义游戏裁判
int turn; //回合
short firstIs; //定义先下的一方,0是黑方,1是白方
short nowIs; //定义当前下的一方
int endX,endY; //最后一步的位置
}BoardRef;
void echo(char str[],int shiftSize);//定义用以回显文字部分,加偏移值
void initBoard(Board * thisB,int Height,int Width);//定义初始化棋盘
void initRef(BoardRef * thisB); //初始化棋盘裁判
int checkVictoryRef(BoardRef * thisBr,Board * thisBo);//检查是否满足胜利条件,参数(裁判类,棋盘类)返回胜利的一方,否则返回-1
//显示函数
void displayBoard(Board * thisB,int shiftSize); //打印棋盘,shiftSize棋盘向右偏移的位置,为了居中
void displayMenu1(int shiftSize);//定义显示游戏主菜单
void displayTurn(BoardRef * thisBr,int shiftSize);//显示游戏回合
void displayWinner(int who,int shiftSize);//显示游戏获胜方
Board gameBoard; //游戏类变量
BoardRef gameRef;
int main(){
while (1){
system("cls");
displayMenu1(defaultShiftSize);
char tempChoice;
scanf("%c",&tempChoice);
while (getchar()!='\n');
if (tempChoice=='Y' || tempChoice =='y'){
initRef(&gameRef);
initBoard(&gameBoard,5,5);
while (1){ //游戏开始
system("cls");
displayBoard(&gameBoard,defaultShiftSize);
displayTurn(&gameRef,defaultShiftSize);
int tempX,tempY;
while (scanf("%d %d",&tempY,&tempX)==0 || tempY>=maxSize || tempX >=maxSize ||
tempY<=0 || tempX<=0 || gameBoard.Graph[tempY][tempX]<2){
setbuf(stdin,NULL); //用于去除数字以外的其它杂余字符的缓冲
system("cls");
displayBoard(&gameBoard,defaultShiftSize);
echo("非法输入!\n",defaultShiftSize);
displayTurn(&gameRef,defaultShiftSize);
};
//更新棋盘裁判
gameRef.endX=tempX,gameRef.endY=tempY;
gameBoard.Graph[tempY][tempX]=gameRef.nowIs;
int tempFlag=-1;//临时变量检查是否满足胜利条件
tempFlag=checkVictoryRef(&gameRef,&gameBoard);
if (tempFlag==-1 && gameRef.turn==gameBoard.Height*gameBoard.Width) tempFlag=-2;
if (tempFlag!=-1){
//显示胜利
system("cls");
displayBoard(&gameBoard,defaultShiftSize);
displayWinner(tempFlag,defaultShiftSize);
break;
};
//更新下一步
gameRef.turn++;
gameRef.nowIs^=1;
};
}else if (tempChoice=='Q' || tempChoice =='q'){//退出游戏
exit(0);
};
}
return 0;
}
void echo(char str[],int shiftSize){//用以回显文字部分,加偏移值
int tempi;
for (tempi=1;tempi<=shiftSize;++tempi) printf(" ");
printf("%s",str);
}
void initBoard(Board * thisB,int Height,int Width){
int tempX,tempY;
thisB->Height=Height; //设置初始宽和高
thisB->Width=Width;
for (tempY=1;tempY<=thisB->Height;++tempY){ //初始化空棋盘
for (tempX=1;tempX<=thisB->Width;++tempX){
if (tempX==1 && tempY==1){
thisB->Graph[tempY][tempX]=2; //定义符号┌
}else if (tempX==1 && tempY==thisB->Height){
thisB->Graph[tempY][tempX]=3; //定义符号└
}else if (tempX==1){
thisB->Graph[tempY][tempX]=4; //定义符号├
}else if (tempX==thisB->Width && tempY==1){
thisB->Graph[tempY][tempX]=5; //定义符号┐
}else if (tempX==thisB->Width && tempY==thisB->Height){
thisB->Graph[tempY][tempX]=6; //定义符号┘
}else if (tempX==thisB->Width){
thisB->Graph[tempY][tempX]=7; //定义符号┤
}else if (tempY==1){
thisB->Graph[tempY][tempX]=8; //定义符号┬
}else if (tempY==thisB->Height){
thisB->Graph[tempY][tempX]=9; //定义符号┴
}else{
thisB->Graph[tempY][tempX]=10; //定义符号┼
}
}
}
}
void displayBoard(Board * thisB,int shiftSize){ //打印棋盘,shiftSize棋盘向右偏移的位置,为了居中
int tempX,tempY,tempi;
for (tempY=1;tempY<=thisB->Height;++tempY){ //打印偏移空白
for (tempi=1;tempi<=shiftSize;++tempi){
printf(" ");
};
for (tempX=1;tempX<=thisB->Width;++tempX){
switch (thisB->Graph[tempY][tempX]){
case 0:printf("○");break;
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;
case 8:printf("┬");break;
case 9:printf("┴");break;
case 10:printf("┼");break;
default:printf(" ");break;
};
if (tempX<thisB->Width && thisB->Graph[tempY][tempX]>1){
if (thisB->Graph[tempY][tempX+1]<2)
printf(" ");
else
printf("─"); //为了棋盘美观
};
};
printf("\n");
}
}
void initRef(BoardRef * thisB){ //初始化棋盘裁判
thisB->turn=1; //初始化回合数
thisB->firstIs=0; //默认黑棋先
thisB->nowIs=thisB->firstIs;
}
int checkVictoryRef(BoardRef * thisBr,Board * thisBo){//检查是否满足胜利条件,返回胜利的一方,否则返回-1
int nowIs=thisBr->nowIs;
//检查横排是否五子
int tempX,tempY,count=1;
for (tempX=thisBr->endX-1;tempX>0 && thisBo->Graph[thisBr->endY][tempX]==nowIs;--tempX)++count;
for (tempX=thisBr->endX+1;tempX<=thisBo->Width && thisBo->Graph[thisBr->endY][tempX]==nowIs;++tempX)++count;
if (count>=5) return nowIs;
//检查竖排是否五子
count=1;
for (tempY=thisBr->endY-1;tempY>0 && thisBo->Graph[tempY][thisBr->endX]==nowIs;--tempY)++count;
for (tempY=thisBr->endY+1;tempY<=thisBo->Height && thisBo->Graph[tempY][thisBr->endX]==nowIs;++tempY)++count;
if (count>=5) return nowIs;
//检查正斜角线
count=1;
for (tempY=thisBr->endY-1,tempX=thisBr->endX-1;tempY>0 && tempX>0 && thisBo->Graph[tempY][tempX]==nowIs;--tempX,--tempY)++count;
for (tempY=thisBr->endY+1,tempX=thisBr->endX+1;tempY<=thisBo->Height &&
tempX<=thisBo->Width && thisBo->Graph[tempY][tempX]==nowIs;++tempX,++tempY)++count;
if (count>=5) return nowIs;
//检查反斜角线
count=1;
for (tempY=thisBr->endY-1,tempX=thisBr->endX+1;tempY>0 && tempX<=thisBo->Width && thisBo->Graph[tempY][tempX]==nowIs;++tempX,--tempY)++count;
for (tempY=thisBr->endY+1,tempX=thisBr->endX-1;tempY<=thisBo->Height &&
tempX>0 && thisBo->Graph[tempY][tempX]==nowIs;--tempX,++tempY)++count;
if (count>=5) return nowIs;
return -1;
}
void displayMenu1(int shiftSize){
echo("GoBang 五子棋简陋控制台版V0.1Beta\n\n\n",shiftSize);
echo(" 开始游戏(双人版)Y/Q(进入/退出) \n",shiftSize);
echo(" ",shiftSize);
}
void displayTurn(BoardRef * thisBr,int shiftSize){
if (thisBr->nowIs)
echo("现在由白方走棋.请输入棋子X Y坐标:",shiftSize);
else
echo("现在由黑方走棋.请输入棋子X Y坐标:",shiftSize);
}
void displayWinner(int who,int shiftSize){
switch (who){
case 0:echo("黑方获胜\n",shiftSize);break;
case 1:echo("白方获胜\n",shiftSize);break;
default:echo("平局\n",shiftSize);break;
}
system("pause >nul");
}
附加功能实现(进阶)
- 使用鼠标点击下棋(核心头文件windows.h>
- AI功能(极大极小函数以及五子棋术语分数实现)
- 禁手功能(即黑方先行一方禁止下三三和四四)
- 保存棋谱、记录棋谱、悔棋的实现
五子棋术语,分值定义
- 连五——五个棋子练成一线(优先级1)
- 活四——四个棋子练成一线,且两端无棋子(优先级2)
- 冲四——只有一端可以连五(优先级3)
- 死四——两端有子(优先级10)
- 活三——两端都没有棋子,再下一步可以是活四和冲四(优先级3)
- 跳活三——三颗棋子中间有一个空位(优先级4)
- 眠三——活三或跳活三中有一端有子(优先级5)
- 死三——两端都有子(优先级10)
- 活二——两端无子(优先级6)
- 跳二——中间格一个空子(优先级6)
- 大跳二——中间格两个空子(优先级7)
- 眠二——以上二一端有子(优先级8)
- 死二——两端都有子(优先级10)
- 单子(优先级9)
集成GoBang 头文件,方便调用(电脑方采用深度博弈暴力搜索)
#ifndef GoBang_H
#define GoBang_H
#ifndef MAX
#define MAX(a,b) (a>b?a:b)
#endif
#ifndef MIN
#define MIN(a,b) (a<b?a:b)
#endif
#ifndef ABS
#define ABS(a) (a>=0?a:-a)
#endif
#define GB_MAX_HEIGHT 15 /*棋盘最大的高*/
#define GB_MAX_WIDTH 15 /*棋盘最大的宽*/
#include <time.h> /* 调用系统时间库 */
#include <stdlib.h> /* 调用标准库 */
#include <limits.h> /* 调用常用常量库 */
#include <string.h>
/* 棋子类型 */
enum gb_chess_type {
gb_black,
gb_white,
gb_left_top,
gb_right_top,
gb_top,
gb_left,
gb_right,
gb_middle,
gb_left_bottom,
gb_right_bottom,
gb_bottom
};
/*模式类型*/
enum gb_mode_type {
gb_normal_mode,
gb_ban_mode
};
/*分数分值*/
enum gb_score_type {
gb_win5=10000000,
gb_alive4=1000000,
gb_along4=100000,
gb_die4=0,
gb_alive3=100000,
gb_along3=1000,
gb_die3=0,
gb_alive2=100,
gb_along2=10,
gb_die2=0,
gb_alive1=1
};
/* 坐标结构 */
struct gb_coord{
short x,y;
};
/* 常数组方向向量 */
const struct gb_coord gb_dir[8]={
{1,0},{-1,0},{0,1},{0,-1},{1,1},{-1,-1},{1,-1},{-1,1}
};
/* 定义棋盘类 */
struct gb_board{
char gb_title[80];
short gb_graph[GB_MAX_WIDTH+1][GB_MAX_HEIGHT+1];/*棋盘图形,起始(1,1)*/
short gb_width,gb_height; /*长和高*/
short gb_turn; /*回合数*/
short gb_cur_is;/* 当前下的一方 */
/* short gb_mode; 模式 */ /*禁手模式特殊情况很多,对禁手模式暂时不是很了解,暂时先保留 */
struct gb_coord gb_chess_manual[GB_MAX_HEIGHT*GB_MAX_WIDTH]; /*棋谱*/
};
/* void gb_set_mode(struct gb_board * cur_board,short mode){ 设置游戏模式
cur_board->gb_mode = mode;
}*/
/* 判断初始棋盘位置的类型 */
short gb_init_chess_judge(struct gb_board * cur_board,struct gb_coord * cur_pos){
short cur_type;
if (cur_pos->y==1) cur_type=gb_left_top; /* 顶部 */
else if (cur_pos->y==cur_board->gb_height) cur_type=gb_left_bottom; /* 底部 */
else cur_type=gb_left; /* 中间 */
if (cur_pos->x!=1){
if (cur_pos->x==cur_board->gb_width) cur_type++; /* 右 */
else cur_type+=2; /* 中间 */
}
return cur_type;
}
/*初始化棋盘*/
int gb_init_board(struct gb_board * cur_board,short width,short height){
memset(cur_board->gb_title,0,sizeof(cur_board));
cur_board->gb_height = height,cur_board->gb_width=width; /* 初始化高和宽 */
/* cur_board->gb_mode = gb_normal_mode; 初始化模式 */
cur_board->gb_cur_is = gb_white; /* 初始化第0回合 */
cur_board->gb_turn=0;
struct gb_coord pos;
for (pos.y=1;pos.y<=height;++pos.y){ /* 初始化棋盘图形 */
for (pos.x=1;pos.x<=width;++pos.x){
cur_board->gb_graph[pos.x][pos.y]=gb_init_chess_judge(cur_board,&pos);
}
}
return 0;
}
/* 判断点是否在符合棋盘范围 */
int gb_in_board_judge(struct gb_board * cur_board,struct gb_coord * cur_pos){
if (cur_pos->x<1 || cur_pos->y<1 || cur_pos->x > cur_board->gb_width || cur_pos->y > cur_board->gb_height)
return 0;
return 1;
}
/* 判断点是否在棋盘上放置 */
int gb_place_judge(struct gb_board * cur_board,struct gb_coord * cur_pos){
if (gb_in_board_judge(cur_board,cur_pos) && cur_board->gb_graph[cur_pos->x][cur_pos->y]>1){
return 1;
}
return 0;
}
/* 是否获胜判断,是返回gb_white或gb_black,平局返回-2,否则返回-1*/
int gb_win_judge(struct gb_board * cur_board){
short dir;
struct gb_coord pos;
int chess_count_alive3=0,chess_count_alive4=0;
for (dir=0;dir<8;dir+=2){
short chess_count=1,is_left_chess=0,is_right_chess=0; /* 计算单个方向的棋子数量,端点是否有对手棋子 */
pos.x=cur_board->gb_chess_manual[cur_board->gb_turn].x+gb_dir[dir].x,
pos.y=cur_board->gb_chess_manual[cur_board->gb_turn].y+gb_dir[dir].y;
while (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]==cur_board->gb_cur_is){
chess_count++;
pos.x+=gb_dir[dir].x,pos.y+=gb_dir[dir].y;
};
if (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]!=(cur_board->gb_cur_is^1)) is_left_chess=1; /* 左方向 */
pos.x=cur_board->gb_chess_manual[cur_board->gb_turn].x+gb_dir[dir+1].x,
pos.y=cur_board->gb_chess_manual[cur_board->gb_turn].y+gb_dir[dir+1].y;
while (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]==cur_board->gb_cur_is){
chess_count++;
pos.x+=gb_dir[dir+1].x,pos.y+=gb_dir[dir+1].y;
};
if (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]!=(cur_board->gb_cur_is^1)) is_right_chess=1; /* 右方向 */
/* if (cur_board->gb_mode==gb_ban_mode && cur_board->gb_cur_is==gb_black){ 禁手模式下计算活三、活四数量和长连情况
if (chess_count==3 && is_left_chess+is_right_chess==2) ++chess_count_alive3;
if (chess_count==4 && is_left_chess+is_right_chess==2) ++chess_count_alive4;
if (chess_count>5) {return gb_white;}
}*/
if (chess_count>=5) return cur_board->gb_cur_is;
}
/*if (cur_board->gb_mode==gb_ban_mode && cur_board->gb_cur_is==gb_black)
if (chess_count_alive3>1 || chess_count_alive4>1) return gb_white; 出现双活三及双活四以上的情况 */
if (cur_board->gb_turn==cur_board->gb_height*cur_board->gb_width) return -2; /* 平局情况 */
return -1;
}
/*获取这个点在棋盘上的评估分值 */
int gb_get_pos_score(struct gb_board * cur_board,struct gb_coord * cur_pos,short cur_type){
short dir;
int score_sum=0,chess_count_alive3=0,chess_count_alive4=0;/* 分数总值,连活三数量,连活四数量 */
struct gb_coord pos;
for (dir=0;dir<8;dir+=2){
short chess_count=1,is_left_chess=0,is_right_chess=0; /* 计算单个方向的棋子数量,端点是否有对手棋子 */
pos.x=cur_pos->x+gb_dir[dir].x,pos.y=cur_pos->y+gb_dir[dir].y;
while (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]==cur_type){
chess_count++;
pos.x+=gb_dir[dir].x,pos.y+=gb_dir[dir].y;
};
if (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]!=(cur_type^1)) is_left_chess=1; /* 左方向 */
pos.x=cur_pos->x+gb_dir[dir+1].x,pos.y=cur_pos->y+gb_dir[dir+1].y;
while (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]==cur_type){
chess_count++;
pos.x+=gb_dir[dir+1].x,pos.y+=gb_dir[dir+1].y;
};
if (gb_in_board_judge(cur_board,&pos) && cur_board->gb_graph[pos.x][pos.y]!=(cur_type^1)) is_right_chess=1; /* 右方向 */
/*if (cur_board->gb_mode==gb_ban_mode && cur_type==gb_black){ 禁手模式下计算活三、活四数量和长连情况
if (chess_count>5) {score_sum=0;break;}
if (chess_count==3 && is_left_chess+is_right_chess==2) ++chess_count_alive3;
if (chess_count==4 && is_left_chess+is_right_chess==2) ++chess_count_alive4;
}*/
if (chess_count>=5){ /* 获胜的分数直接退出 */
score_sum=gb_win5;break;
}
switch (chess_count){ /* 根据棋数和端点数目判断连活三、连活4等等情况 */
case 4:
switch (is_left_chess+is_right_chess){
case 2:score_sum+=gb_alive4;break;
case 1:score_sum+=gb_along4;break;
case 0:score_sum+=gb_die4;break;
}break;
case 3:
switch (is_left_chess+is_right_chess){
case 2:score_sum+=gb_alive3;break;
case 1:score_sum+=gb_along3;break;
case 0:score_sum+=gb_die3;break;
}break;
case 2:
switch (is_left_chess+is_right_chess){
case 2:score_sum+=gb_alive2;break;
case 1:score_sum+=gb_along2;break;
case 0:score_sum+=gb_die2;break;
}break;
case 1:
score_sum+=gb_alive1;break;
}
}
/* if (cur_board->gb_mode==gb_ban_mode && cur_type==gb_black)
if (chess_count_alive3>1 || chess_count_alive4>1) return 0;*/
return score_sum;
}
/* 更新棋盘下一步 */
int gb_nxt_turn(struct gb_board * cur_board,struct gb_coord * cur_pos){
if (cur_board->gb_turn+1>cur_board->gb_width*cur_board->gb_height) return 0;
cur_board->gb_turn++;
cur_board->gb_cur_is^=1;
cur_board->gb_chess_manual[cur_board->gb_turn]=*cur_pos;
cur_board->gb_graph[cur_pos->x][cur_pos->y]=cur_board->gb_cur_is;
return 1;
}
/* 回退棋谱,返回上一步 */
int gb_back_turn(struct gb_board * cur_board){
if (cur_board->gb_turn==0) return 0;
struct gb_coord pos=cur_board->gb_chess_manual[cur_board->gb_turn];
cur_board->gb_turn--;
cur_board->gb_cur_is^=1;
cur_board->gb_graph[pos.x][pos.y]=gb_init_chess_judge(cur_board,&pos);
return 1;
}
/* 深度优先搜索搜索最大利益值点 */
int gb_auto_nxt_turn(struct gb_board * cur_board,struct gb_coord * choose_pos,int depth){
srand(time(NULL));
struct gb_coord pos={8,8};
if (cur_board->gb_turn==0){
*choose_pos=pos;
return 0;
}
int max_score=0,pos_count=0;
for (pos.y=1;pos.y<=cur_board->gb_height;++pos.y){
for (pos.x=1;pos.x<=cur_board->gb_width;++pos.x){
if (cur_board->gb_graph[pos.x][pos.y]>1){
int score=0; /* 该点的分值 */
++pos_count;
score+=gb_get_pos_score(cur_board,&pos,cur_board->gb_cur_is^1);/* 假设自己下这个点获得的分值 */
score+=gb_get_pos_score(cur_board,&pos,cur_board->gb_cur_is)/2;/* 对方下这个点的分值 */
gb_nxt_turn(cur_board,&pos);
if (score>max_score && depth>1 && cur_board->gb_turn<cur_board->gb_height*cur_board->gb_width){ /* 向下层搜索 */
struct gb_coord nxt_pos;
score-=gb_auto_nxt_turn(cur_board,&nxt_pos,depth-1); /* 扣去对手可以获得的利益值 */
if (score<0) score=0;
}
if (score>max_score || (score==max_score && rand()%pos_count==1)){ /* 更新点 */
*choose_pos=pos;
max_score=score;
}
gb_back_turn(cur_board); /* 复原 */
}
}
}
return max_score;
}
#endif
PS:由于对禁手模式的误解导致很多情况没有考虑到,暂时先删去禁手模式
鼠标点击采用win控制台api实现控制台鼠标点击(单线程)集成CUI头文件
#ifndef CUI_H
#define CUI_H
#include <windows.h>
#include <stdio.h>
#include <ctype.h>
#define CONSOLE_WIDTH 80
#define CONSOLE_HEIGHT 25
struct cui_object{
HANDLE input,output; /* 控制台标准输入和输出句柄 */
int height,width; /* 控制台的高和宽 */
};
/* cui_object 新建初始化 */
void cui_new(struct cui_object * cur_cui,int height,int width){
cur_cui->height=height; /* 控制台高度 */
cur_cui->width=width; /* 控制台宽度 */
cur_cui->input=GetStdHandle(STD_INPUT_HANDLE); /* 控制台标准输入句柄 */
cur_cui->output=GetStdHandle(STD_OUTPUT_HANDLE); /* 控制台标准输出句柄 */
}
/* 隐藏cui的光标 */
void cui_hide_cursor(struct cui_object * cur_cui){
CONSOLE_CURSOR_INFO cursor_info = {1,0};
SetConsoleCursorInfo(cur_cui->output,&cursor_info);
}
/* cui_初始化 */
void cui_init(struct cui_object * cur_cui){
cui_hide_cursor(cur_cui); /* 隐藏光标 */
CONSOLE_SCREEN_BUFFER_INFO screen_buffer_info;
SMALL_RECT rect ={0,0,cur_cui->width,cur_cui->height};
SetConsoleWindowInfo(cur_cui->output,1,&rect); /* 设置控制台窗口大小 */
GetConsoleScreenBufferInfo(cur_cui->output,&screen_buffer_info);
rect=screen_buffer_info.srWindow;
COORD new_buffer_size={rect.Right+1,rect.Bottom+1}; /* 将缓冲区扩大以隐藏滚动条 */
SetConsoleScreenBufferSize(cur_cui->output,new_buffer_size);
system("cls"); /* 清屏缓冲区防止 闪屏 bug */
}
/* 获取CUI的鼠标读入 */
int cui_get_mouse_input(struct cui_object * cur_cui,COORD * mouse_pos){
DWORD fdw_mode;
fdw_mode = ENABLE_EXTENDED_FLAGS | ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT;
SetConsoleMode(cur_cui->input,fdw_mode); /* 允许窗口输入,鼠标输入 */
INPUT_RECORD input_record;
DWORD record_num;
ReadConsoleInput(cur_cui->input,&input_record,1,&record_num); /* 获取鼠标读入 */
if (input_record.EventType == MOUSE_EVENT){
switch (input_record.Event.MouseEvent.dwButtonState){
case FROM_LEFT_1ST_BUTTON_PRESSED: /* 鼠标左键点击 */
mouse_pos->X=input_record.Event.MouseEvent.dwMousePosition.X;
mouse_pos->Y=input_record.Event.MouseEvent.dwMousePosition.Y;
return 1;
case RIGHTMOST_BUTTON_PRESSED: /* 鼠标右键点击 */
mouse_pos->X=input_record.Event.MouseEvent.dwMousePosition.X;
mouse_pos->Y=input_record.Event.MouseEvent.dwMousePosition.Y;
return 2;
}
}
return 0;
}
/* 在CUI打印二维字符图形 */
void cui_print_graph(struct cui_object * cur_cui,char (* graph)[CONSOLE_WIDTH],short top,short left,short height,short width){
COORD cursor_pos;
int i,j;
for (i=0;i<height;++i){
for (j=0;j<width;++j){
cursor_pos.X=left+j;
cursor_pos.Y=top+i;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
if (graph[i][j]=='\0') break;
if (isascii(graph[i][j])){ /* 打印ASCII字符 */
printf("%c",graph[i][j]);
}else{ /* 打印中文字符、特殊字符,为兼容win10新版cmd正常显示位置 */
printf("%c%c",graph[i][j],graph[i][j+1]);
++j;
}
}
}
cursor_pos.X=0;cursor_pos.Y=0;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
}
/* cui绘制方框 */
void cui_printf_sqruare(struct cui_object * cur_cui,int top,int left,int height,int width){
COORD cursor_pos;
int i,j;
for (i=0;i<height;++i){
for (j=0;j<width;++j){
cursor_pos.Y=top+i;
cursor_pos.X=left+j;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
if (i==0){
if (j==0){
printf("┌");
}else if (j==width-1 || j==width-2){
printf("┐");
}else{
printf("─");
}
++j;
}else if (i==height-1){
if (j==0){
printf("└");
}else if (j==width-1 || j==width-2){
printf("┘");
}else{
printf("─");
}
++j;
}else{
if (j==0){
printf("│");++j;
}else if (j==width-1 || j==width-2){
printf("│");++j;
}else{
printf(" ");
}
}
}
}
cursor_pos.X=0;cursor_pos.Y=0;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
}
/* 打印字符选项,居中打印 */
void cui_print_menu(struct cui_object * cur_cui,char (* menu)[CONSOLE_WIDTH],int top,int left,int menu_tot,int width,int interval){
COORD cursor_pos;
int i;
for (i=0;i<menu_tot;++i){
cursor_pos.Y=top+i*interval+2; /* 扣去边框 */
int left_begin=(width-strlen(menu[i]))/2;
cursor_pos.X=left+left_begin;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
printf("%s",menu[i]);
}
cursor_pos.X=0;cursor_pos.Y=0;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
}
/* 判断鼠标选中的菜单的第几项 */
int cui_mouse_click_menu(COORD * mouse_pos,char (* menu)[CONSOLE_WIDTH],int top,int left,int menu_tot,int width,int interval){
COORD cursor_pos;
int i;
for (i=0;i<menu_tot;++i){
cursor_pos.Y=top+i*interval+2; /* 扣去边框 */
int len=strlen(menu[i]);
int left_begin=(width-len)/2;
cursor_pos.X=left+left_begin;
if (mouse_pos->Y==cursor_pos.Y && mouse_pos->X>=cursor_pos.X && mouse_pos->X<cursor_pos.X+len)
return i+1;
}
return 0;
}
/* 判断cui的鼠标点击是否在某个方形图形范围内 */
int cui_mouse_click_graph(COORD * mouse_pos,int top,int left,int height,int width){
if (mouse_pos->Y<top || mouse_pos->Y>=top+height) return 0;
if (mouse_pos->X<left || mouse_pos->X>=left+width) return 0;
return 1;
}
/* 局部删除图形 */
void cui_clear_graph(struct cui_object * cur_cui,int top,int left,int height,int width){
COORD cursor_pos;
int i,j;
for (i=0;i<height;++i) {
for (j = 0; j < width; ++j) {
cursor_pos.Y = top + i;
cursor_pos.X = left + j;
SetConsoleCursorPosition(cur_cui->output, cursor_pos);
printf(" ");
}
}
cursor_pos.X=0;cursor_pos.Y=0;
SetConsoleCursorPosition(cur_cui->output,cursor_pos);
}
#endif //CUI_H
控制台界面主函数代码
为了更好的实现图形化,因此采用最新版GTK库来做图形化界面
-
首先设置好编译环境(win10 64位 编译器Clion(cmake)---教育版免费使用)
-
用PS简单的做五子棋图片素材
棋盘400px*400px
黑棋23px*23px
白棋23px*23px
-
用gtk控件布置好五子棋界面(主界面代码)
#include <gtk/gtk.h>
#include "GoBang.h"
/* 棋子信息结构 */
struct chess_info{
short x,y;
GtkWidget * image;
};
/* 全局变量方便回调函数修改 */
struct chess_info chess_praph[16][16];
/* 状态栏 */
GtkWidget * status_label;
/* 判断是否可以开始游戏 */
gboolean is_start_game=TRUE;
#define CHESS_INFO(object) ((struct chess_info *)object)
struct gb_board game_gb;
static GtkWidget * get_chess(const gchar * filename){
GtkWidget * image = gtk_image_new_from_file(filename);
return image;
}
/* 改变棋子在图形界面的图像 */
static gboolean change_chess(short x,short y,int type){\
GtkImage * image;
image=GTK_IMAGE(chess_praph[x-1][y-1].image);
g_return_val_if_fail(image,FALSE);
switch (type){
case 1:
g_object_set(image,"file","./image/五子棋黑棋.png",NULL);
break;
case 0:
g_object_set(image,"file","./image/五子棋白棋.png",NULL);
break;
default:
g_object_set(image,"file","./image/五子棋空格.png",NULL);
break;
}
return TRUE;
}
/* 初始化 */
static void chess_init(struct gb_board * cur_board){
gb_init_board(cur_board,15,15);
int i,j;
for (i=1;i<=15;++i){
for (j=1;j<=15;++j){
change_chess(i,j,-1);
}
}
}
/* 显示获胜方 */
static gboolean winner_show(int flag){
if (flag==-1) return FALSE;
switch(flag){
case -2:
gtk_label_set_text(GTK_LABEL(status_label),"平局");
break;
case 1:
gtk_label_set_text(GTK_LABEL(status_label),"白棋胜");
break;
case 0:
gtk_label_set_text(GTK_LABEL(status_label),"黑棋胜");
break;
}
is_start_game=FALSE;
return TRUE;
}
/* 状态标签回调函数 */
static gboolean status_show(int flag){
switch(flag) {
case 0:
gtk_label_set_text(GTK_LABEL(status_label),"当前轮到白方下棋,电脑正在思考中...");
case 1:
gtk_label_set_text(GTK_LABEL(status_label),"当前轮到黑方下棋");
}
return TRUE;
}
/* 菜单重新开始回调函数 */
static gboolean chess_restart(GtkWidget *widget,gpointer data){
gtk_label_set_text(GTK_LABEL(status_label),"游戏开始");
chess_init(&game_gb);
is_start_game=TRUE;
return TRUE;
}
/* 悔棋回调函数 */
static gboolean chess_back(GtkWidget *widget,gpointer data){
if (!is_start_game || game_gb.gb_turn<1) return TRUE;
change_chess(game_gb.gb_chess_manual[game_gb.gb_turn].x,game_gb.gb_chess_manual[game_gb.gb_turn].y,-1);
gb_back_turn(&game_gb);
change_chess(game_gb.gb_chess_manual[game_gb.gb_turn].x,game_gb.gb_chess_manual[game_gb.gb_turn].y,-1);
gb_back_turn(&game_gb);
return TRUE;
}
/* 响应鼠标点击 */
static gboolean chess_click(GtkWidget *widget,GdkEventButton *event,gpointer data){
if (!is_start_game) return TRUE;
struct gb_coord chess_pos;
chess_pos.x=CHESS_INFO(data)->x,chess_pos.y=CHESS_INFO(data)->y;
if (gb_place_judge(&game_gb,&chess_pos)){
change_chess(chess_pos.x,chess_pos.y,game_gb.gb_cur_is);
gb_nxt_turn(&game_gb,&chess_pos);
if (winner_show(gb_win_judge(&game_gb))) return TRUE;
status_show(game_gb.gb_cur_is);
gb_auto_nxt_turn(&game_gb,&chess_pos,3);
change_chess(chess_pos.x,chess_pos.y,game_gb.gb_cur_is);
gb_nxt_turn(&game_gb,&chess_pos);
if (winner_show(gb_win_judge(&game_gb))) return TRUE;
status_show(game_gb.gb_cur_is);
}
return TRUE;
}
/* 应用窗口 */
static void activate(GtkApplication *app, gpointer user_data) {
g_print("看到此条信息证明控制台未关闭");
GtkWidget *window;
window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW (window), "五子棋");
gtk_widget_set_size_request(GTK_WIDGET(window),400,450);
gtk_window_set_resizable(GTK_WINDOW(window),gtk_false());
GtkWidget * chess_board;
chess_board = gtk_grid_new();
gtk_grid_set_column_spacing(GTK_GRID(chess_board),4);
gtk_grid_set_row_spacing(GTK_GRID(chess_board),4);
GtkWidget * fixed = gtk_fixed_new();
gtk_container_add(GTK_CONTAINER(window),fixed);
/* 初始化五子棋界面 */
GtkWidget * image_chess_board = gtk_image_new_from_file("./image/五子棋棋盘.png");
gint i,j;
for (i=0;i<15;++i){
for (j=0;j<15;++j){
GtkWidget * event_box=gtk_event_box_new();
chess_praph[i][j].y=j+1;
chess_praph[i][j].x=i+1;
chess_praph[i][j].image=get_chess("./image/五子棋空格.png");
gtk_container_add(GTK_CONTAINER(event_box),chess_praph[i][j].image);
gtk_grid_attach(GTK_GRID(chess_board),event_box,i,j,1,1);
g_signal_connect(G_OBJECT(event_box),"button_press_event",G_CALLBACK(chess_click),&chess_praph[i][j]);
}
}
/* 初始化 */
chess_init(&game_gb);
status_label = gtk_label_new("游戏开始");
gtk_fixed_put(GTK_FIXED(fixed),image_chess_board,0,25);
gtk_fixed_put(GTK_FIXED(fixed),chess_board,0,25);
gtk_fixed_put(GTK_FIXED(fixed),status_label,0,430);
/* 创建一个水平的box */
GtkWidget * menu_box = gtk_box_new(GTK_ORIENTATION_VERTICAL,0);
gtk_fixed_put(GTK_FIXED(fixed),menu_box,0,0);
GtkWidget * menu_bar,* menu_restart,* menu_back;
menu_bar=gtk_menu_bar_new();
gtk_widget_set_hexpand (menu_bar, TRUE);
gtk_box_pack_start (GTK_BOX (menu_box),menu_bar, FALSE, TRUE, 0);
menu_restart=gtk_menu_item_new_with_label("重新开始");
gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_restart);
menu_back = gtk_menu_item_new_with_label("悔棋");
gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_back);
/* 信号连接菜单 */
g_signal_connect(GTK_MENU_ITEM(menu_restart),"activate",G_CALLBACK(chess_restart),NULL);
g_signal_connect(GTK_MENU_ITEM(menu_back),"activate",G_CALLBACK(chess_back),NULL);
gtk_widget_show_all(window);
}
int main(int argc, char **argv) {
GtkApplication *app;
int status;
app = gtk_application_new("GoBang.app", G_APPLICATION_FLAGS_NONE);
g_signal_connect (app, "activate", G_CALLBACK(activate), NULL);
status = g_application_run(G_APPLICATION (app), argc, argv);
g_object_unref(app);
return status;
}