五子棋项目 --C语言

前段时间用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增加一个状态即可。

还有胜负判断中 几个方向上的移动 用数组来表示
胜负判断和文件虽然不难 但是值得学习

效果图:
五子棋项目 --C语言_第1张图片
五子棋项目 --C语言_第2张图片
五子棋项目 --C语言_第3张图片
再次感谢所有小组成员

你可能感兴趣的:(C语言)