欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

五子棋项目 --C语言

程序员文章站 2022-05-25 13:56:21
...

前段时间用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语言
五子棋项目 --C语言
五子棋项目 --C语言
再次感谢所有小组成员

相关标签: 五子棋 C语言