前段时间用C语言和小组成员一起写了一个五子棋的小项目
我写的代码基本被毙了 惨 但也学到很多
总结一下
五子棋项目简要介绍:
1.用光标控制棋子运动及下棋
2.控制台输出 没用gui
3.有悔棋功能
4.只有人人对战 无ai 人机对战(之后可能会升级 并加入联机操作)
5.其他和一般五子棋一样
6.采用简单的栈进行棋子数据的存储,实现下棋,悔棋功能
代码分析:
大体分几个模块:
一个欢迎界面
一个游戏界面
游戏模块:下棋悔棋
判断胜负模块
main函数
每个模块含义一些细小的控制细节
1.棋子 定义为结构体
typedef struct {
int x;
int y;
int type;//棋子颜色 黑或白
}ChessMan;
棋盘:二维数组 直接用中文“十”填充
//初始化棋盘
void InitChessBoard() {
for(int i = 0; i < BOARD_SIZE ; i ++) {
for(int j = 0; j < BOARD_SIZE; j ++) {
ChessBoard[i][j] = "十";
}
}
}
下棋即用黑子或白子直接覆盖即可
例:
ChessBoard[chessArray[i].y][chessArray[i].x] = "●";
2.用链栈存储棋子数据 实现下棋 悔棋
下棋即入栈 悔棋即出栈
一开始我没找到棋子类型与光标相互匹配的方法,因为用光标控制,之前没尝试过,搜到的光标控制方法有设置横纵坐标,与棋子横纵坐标有些重复,后来队友用了更好的光标控制方法解决此问题。
光标控制方法:
此控制方法也有借鉴网上找到的控制方法
用到API函数:
SetConsoleCursorPosition 以此设置光标位置
具体使用方法详见:https://blog.csdn.net/xiexievv/article/details/7475848
https://blog.csdn.net/qq_38241045/article/details/69941464
/**设置游戏光标*/
void SetPosition(int x,int y)
{
HANDLE winHandle;//句柄 ?存疑
COORD pos={x,y};//坐标x,y
winHandle=GetStdHandle(STD_OUTPUT_HANDLE);调用函数
//设置光标的坐标
SetConsoleCursorPosition(winHandle,pos);//调用函数
}
下棋模块代码:
具体见注释
int counts = 0;//记录落子数 步数
int i = 0;
void Play() {
InitChessBoard();//初始化棋盘
ShowGameInterface();
HANDLE hwnd = GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
coord.X = 41;
coord.Y = 9;//设置初始光标位置
SetConsoleCursorPosition(hwnd,coord);
while (1) {//进入下棋
switch(getch() ) {
case 27://esc 退出 回到欢迎界面
return 0;
break;
case 32: {
loopout(hwnd, coord);
int X=coord.X-27,Y=coord.Y-2;
counts++;
int n=225;//总步数
ChessMan chessArray[n];//棋子结构数组
chessArray[i].x = X/2; //棋子坐标 横坐标变化两个单位 棋盘上移动一格
chessArray[i].y =Y;
chessArray[i].type = counts%2;//先手为甲方 //mod2结果只能是0或1
LinkedStack * linkedStack =(LinkedStack *)malloc(sizeof(ChessMan));//链栈
PushLinkedStack(linkedStack,chessArray[i]);
//将棋子压入栈
//根据棋子类型修改棋盘
//下子
if(strcmp(ChessBoard[Y][X/2],"十")==0){ //非重子 可下棋
if(chessArray[i].type == BLACK) {
ChessBoard[chessArray[i].y][chessArray[i].x] = "●";
if(judge_win("●",chessArray[i].y,chessArray[i].x)) {//判断是否胜利,目前只会响铃一下
printf("\a");
winner = 0;//不同值 不同状态 0 黑棋 1 白棋 2 和棋 3 重棋 -1 初始值
}
if(counts == 225) { //判断是否和棋
winner = 2;
}
} else {
ChessBoard[chessArray[i].y][chessArray[i].x] = "○";
if(judge_win("○",chessArray[i].y,chessArray[i].x)) {
printf("\a");
winner = 1;
}
}
} else { **//重子**
f_print(ChessBoard[Y][X/2],Y,X/2,1,1);
winner = 3; //重子状态
ShowGameInterface(counts-1,winner);
counts--;
winner = -1;
break;
}
i++;//循环
system("cls");//清屏 及时清屏 清屏后要再显示游戏界面
ShowGameInterface(counts,winner);/**显示游戏界面 注意此函数 不同的参数表示不同状态 详见此函数实现*/
int button= getch();//悔棋,退出操作
if(button==98) { //98:b键
//悔棋即出栈,再次用中文“十”代替即可
while(linkedStack->top ) {
ChessMan currChess;
PopLinkedStack(linkedStack,&currChess);
ChessBoard[currChess.y][currChess.x] = "十";
system("cls");
counts--;//悔棋要减步数 且悔棋者要再下一子 只可悔一次
//ptr_num = &counts;
ShowGameInterface(counts,winner);
break;
}
}
}
case 0xE0://光标操作上下左右移动 且要判断是否出界并将其移进棋盘中
switch (getch()) {
case 72://上
if(coord.Y<3)//判断边界
coord.Y=16;
else {
coord.Y--;
}
loopout(hwnd, coord);
break;
case 80://下
if(coord.Y>15)//判断边界
coord.Y=1;
else {
coord.Y++;
loopout(hwnd, coord);
}
break;
case 75://左
if(coord.X<=28)//判断边界
coord.X=53;
else {
coord.X-=2;
loopout(hwnd, coord);
break;
}
case 77://右
if(coord.X>=54)//判断边界
coord.X=25;
else {
coord.X+=2;
loopout(hwnd, coord);
break;
}
}
}
}
getchar();
return ;
}
判断胜负与写入文件模块:
创建data.txt文件记录棋子的运行过程 查看当前状态 输出棋盘及相关判断棋子信息 用于debug
int judge_win(char *now_chess,int x,int y) { //now_chess 当前棋子,x、y 坐标
int x_temp,y_temp,flag;//x,y,替代值,flag为相同棋子判断值
int count = 0;//同向棋子个数
int k;
f_print(now_chess,x,y,1,counts);//data文件输出棋子,及当前棋盘
for(k=0; k<4; k++) {//四方向
x_temp = x;
y_temp = y;//重置
flag = 1;
count = 0;
while(flag) {//正向遍历 右,下,右下,右上 在下子哪里传入的是y,x这里直接用x,y即可
x_temp +=dx[k];//
y_temp+=dy[k];//
f_print(ChessBoard[x_temp][y_temp],x_temp,y_temp,0,0);//**输出当前判断棋子信息**
if(ChessBoard[x_temp][y_temp] == now_chess) {//相同棋子判断
if(x_temp>=0&&x_temp<15&&y_temp>=0&&y_temp<15) {//范围判断
count++;
} else {
flag = 0;
}
} else {
flag = 0;
}
}
flag = 1;
x_temp = x;
y_temp = y;//重置
while(flag) {//反向遍历
x_temp-=dx[k];
y_temp-=dy[k];
f_print(ChessBoard[x_temp][y_temp],x_temp,y_temp,0,0);//
if(ChessBoard[x_temp][y_temp] == now_chess) {
if(x_temp>=0&&x_temp<15&&y_temp>=0&&y_temp<15) {
count++;
} else {
flag = 0;
}
} else {
flag = 0;
}
}
if(count>=4)//如果同行有4个了,加上本体就是5个
return 1;
}
return 0;
}
void f_print(char *temp,int x,int y,int flag,int counts) {//temp: 当前棋子,x,y坐标,flag是否输出棋盘,counts,落子数
FILE *fp;
fp = fopen("data.txt","a");
if(flag) {
fprintf(fp,"第%d次落子,%s棋。 位置x: %d y: %d\n",counts,temp,x,y);
for(int i=0; i<15; i++) {
for(int j=0; j<15; j++) {
fprintf(fp,"%s",ChessBoard[i][j]);
}
fprintf(fp,"\n");
}
fprintf(fp,"\n");
} else {
fprintf(fp,"\t %s %d %d\n",temp,x,y);
}
return ;
}
main函数:
int main() {
SetTitle("DAWN小组五子棋项目");
system("color 37");//设置界面及字体颜色
system("mode con cols=120 lines=46");
while(1) {
ShowFirstPage();
ShowEnterOrQuit();
while(now_title) {
now_title = Play();
}
now_title = 1;//重置各个变量
i = 1;
counts = 0;
winner = -1;
system("cls");
}
return 0;
}
一些全局变量:
const int dx[4] = {1,0,1,1};//遍历方向
const int dy[4] = {0,1,1,-1};
int judge_win(char *now_chess,int x,int y); //判断胜负,就两个函数,先放到主函数里建了
void f_print(char *temp,int x,int y,int flag,int counts);//输出棋盘数据
int counts = 0;//记录落子数
int Play();//落子、悔棋功能
void InitLinkedStack(LinkedStack * linkedStack);
//光标移动函数
void PutDown();
void ShowGameInterface();
int i = 1;
int now_title = 1;
int winner = -1;
int temp_x[225],temp_y[225];
int a = 0;
总结:
胜负判断:
传入当前棋子类型(黑 白 无),当前坐标
设置坐标替代变量 同向棋子数
调用文件函数 输出此时棋盘情况
在四个方向上遍历 判断是否有四个以上同向同类型棋子 设置信号变量(主要用于判断坐标是否超出范围及用于判断文件是否输出情况,初始值为1)
整个项目不大,结构也不复杂,但是有些变量的设置还挺巧妙
比如显示轮到甲方,乙方下棋,胜利显示,ESC退出到main函数的几个变量设置
重子操作 用到简单的strcmp()函数即解决
分非重子
重子 两种情况
非重子才可进行下棋等操作
重子只需winner增加一个状态即可。
还有胜负判断中 几个方向上的移动 用数组来表示
胜负判断和文件虽然不难 但是值得学习