小游戏类项目 —— 五子棋游戏

📅 2026/8/13 16:41:11
小游戏类项目 —— 五子棋游戏
目录第一步编写界面1.构思2.功能的实现第二步编写游戏内容1.游戏的框架2.功能的实现第三步编写电脑玩家1.分类2.输出顺序总结第一步编写界面1.构思我们平时玩游戏并非打开它就直接开始游戏打开游戏后最先印入眼帘的往往是游戏菜单而菜单中必不可少的则是游戏的 ‘开始’ 与 ‘退出’ 而开始与退出的简易界面我们设一个函数来处理//菜单 void menu() { printf(***********************************\n); printf(*********** 1.Play ************\n); printf(*********** 0.Exit ************\n); printf(***********************************\n); }这样我们就拥有了一个简易的菜单了2.功能的实现有了菜单接下来我们就要来实现菜单的功能 Play 和 Exit 我在这里选择的是switch语句switch(整型)恰好对应我们的1.Play和0.Exit在这里我们需要思考当玩家输入的不是1或0时系统无法给出反馈这时我们就要再给出一种情况default使得在系统接收到一个其他内容时可以做出反馈。当然如果玩家想要多次游戏利用do while()语句就能够轻松解决。小结利用switch进行判断玩家的输入1 - play0 - exitdefault - 输入有误。int input 0; do { menu(); printf(请选择:); scanf(%d, input); switch(input) { case 1: { printf(游戏开始!\n); Game(); //游戏的实现 break; } case 0: { printf(退出游戏!\n); break; } default: { printf(输入错误请重新输入!\n\n); } } } while (input);第二步编写游戏内容1.游戏的框架我们进入游戏之后必然不能是空空荡荡的下棋必然要有棋盘有了棋盘再去落子博弈必然也少不了对手所以我们从这三个点构建框架。首先我们要再创建两个文件第一个是头文件(用于函数的声明)第二个是源文件(用于函数的实现)。在头文件中我们先定义行和列方便后面的修改。//头文件名为“五子棋声明” #include stdio.h #define ROM 3 //行 #define COL 3 //列#include 五子棋声明.h//在源文件中对头文件进行声明1.打印棋盘2.进行对局3.分出胜负由此我们设一个Game函数//放于第一个源文件中主函数也在第一个源文件源文件可以自行取名 void Game() { //储存数据 char Board[ROM][COL] {0}; //打印棋盘 DisplayBoard(Board, ROM, COL); //对局 (包含玩家下棋与电脑下棋) Match(Board, ROM, COL); }2.功能的实现一.打印棋盘for (int i 0; i ROM; i) { printf( | | \n); //里面为三个空格 if(i ROM) { printf(---|---|---\n); } }写到此处可能就会有疑问了这样写不就把代码写死了吗以后改成五子棋的多行和多列不就很复杂了吗是的这样写确实弊端很大正如我在大标题下写的代码是改出来的在改代码之前一定要把这个构思实现出来修改时才会有清晰的思路否则让一个初学者写上上千行的代码想必很多人刚开始就懵了。通过不断的修改最基本的代码一步一步的扩展逐渐你就会发现你慢慢的实现了一个自己感觉都不可能完成的事情。就像每天树立一个小目标实现起来并不困难只要坚持便会收获一份大的成果。接下来让我们对这个代码进行修改打印完棋盘后我们发现没有办法在棋盘上进行下棋这时就要用到我们的二维数组了通过二维数组在棋盘上进行坐标的输出。但我们的二维数组里面是空的所以我们首先要做的是对数组进行初始化将每个元素都初始化为空格。这时我们将再数组加进去就达到了我们想要的效果。//全部在头文件中进行声明 //初始化棋盘 - 初始化空格 void InitBoard(char Board[ROM][COL], int rom, int col); //打印棋盘 void DisplayBoard(char Board[ROM][COL], int rom, int col);//函数内容放在第二个源文件中 void InitBoard(char Board[ROM][COL], int rom, int col) { for (int i 0; i rom; i) { for (int j 0; j col; j) { Board[i][j] ; } } }//函数内容放在第二个源文件中 void DisplayBoard(char Board[ROM][COL]) { for (int i 0; i ROM; i) { printf( %c | %c | %c \n, Board[i][0], Board[i][1], Board[i][2]); if(i ROM) { printf(---|---|---\n); } } }接下来对打印行与列的方式进行修改通过对输出内容的拆分寻找其中的规律利用循环进行输出。这里将‘空格’ 与‘’进行拆分‘---’与‘’进行拆分 ‘’每行的输出都少一个从而建立循环用if()语句进行判断使得‘’的输出比‘ ’和‘---’少一次。同理‘---|---|---’也是如此用if()语句进行判断使得他的输出比‘ ’少一次。这样我们通过修改ROM和COL的值就可以打印出19路五子棋了。//函数内容放在第二个源文件中 void DisplayBoard(char Board[ROM][COL]) { //循环输出通过更改ROM和COL可以输出自己需要的行数 for (int i 0; i ROM; i) { //输出第一行 for (int j 0; j COL; j) { printf( %c , Board[i][j]); if (j COL - 1) //最后一个不要 { printf(|); } } printf(\n); //printf( | | \n); //输出第二行 if (i ROM - 1) //最后一次输出不要 { for (int j 0; j COL; j) { printf(---); if (j ROM - 1) //最后一个不要 { printf(|); } } printf(\n); } //printf(---|---|---\n); } }发现问题解决问题费劲千辛万苦终于把棋盘打印出来了但打印出来之后你会发现你在下棋的时候是需要通过输入坐标的输入来下棋的而棋盘空空荡荡下起棋来十分不方便这时你就要再次对自己的代码进行修改。下面的增加坐标的方式不唯一我这肯定不是最好的方法没有办法做到一劳永逸此处仅供开阔思路吧//函数内容放在第二个源文件中 void DisplayBoard(char Board[ROM][COL], int rom, int col)// { //此处为横向坐标的添加 printf(坐标 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19\n); for (int i 0; i rom; i) { //此处为纵向坐标的添加 /*-------------------------------*/ if (i 9) { printf( %d , i 1); } else if (i 9) { printf( %d, i 1); } /*-------------------------------*/ for (int j 0; j col; j) { printf( %c ,Board[i][j]); if (j col - 1) { printf(|); } } printf(\n ); //此处做了改动 if (i rom - 1) { for (int j 0; j col; j) { printf(---); if (j col - 1) { printf(|); } } printf(\n); } } printf(\n); }二.进行对局1.玩家下棋//函数内容放在第二个源文件中 void PlayerMove(char Board[ROM][COL]) { printf(玩家的坐标:); scanf(%d%d, x, y); printf(\n); Board[x - 1][y - 1] *; }2.电脑下棋这里我们先制作一个会落子的电脑玩家//在头文件中引入函数用于产生随机数在此先不细讲了 #include stdlib.h #include time.hsrand((int)time(NULL));//放于main函数里的开头位置用于产生随机数void ComputerMoves(char Board[ROM][COL]) { x rand()%ROM;//产生纵向随机数0 ~ (ROM - 1) y rand()%COL;//产生横向随机数0 ~ (COL - 1) Board[x][y] #; printf(电脑的坐标:%d %d\n\n, x, y); }发现问题运行后你会发现什么都没有打印这时你就要再次数将原先的打印棋盘的函数家在此处。你再次运行程序后会发现你的棋子可以覆盖掉电脑的棋子反之电脑也可以覆盖你的棋子这时我们就要针对这个问题再进行完善。解决问题在这里我们需要再加一个判断条件和一个循环语句当判断此处已经落子后可以让我们从新输入。而电脑玩家此处只需要加上一个判断就可以了。完善后的代码如下//玩家下棋 void PlayerMove(char Board[ROM][COL]) { int x, y; printf(玩家的回合!\n); while (1) { printf(玩家的坐标:); scanf(%d%d, x, y); printf(\n); if (Board[y - 1][x - 1] ) // 判断此处是否已经落子 { Board[y - 1][x - 1] *; break; } else { printf(该坐标已落子!\n); printf(请重新输入!\n); } } DisplayBoard(Board, ROM, COL); // 输入成功后打印棋盘 } //电脑玩家下棋 void ComputerMove(char Board[ROM][COL]) { int x, y; while (1) { x rand()%ROM; y rand()%COL; if (Board[x][y] ) { Board[x][y] #; printf(电脑的回合!\n); printf(电脑的坐标:%d %d\n\n, y, x); // 这里输出时不要搞反 break; } } DisplayBoard(Board, ROM, COL); }3.对局我们将前面的内容整合一下就可以得到下面的代码void Match(char Board[ROM][COL], int rom, int col) { while (1) { //玩家下棋 PlayerMove(Board); count; if (count rom*col) // 判断棋盘是否已经占满棋盘格子的数目为(ROM * COL)为单数所以放在中间 { printf(平局!); break; } //电脑下棋 ComputerMove(Board); count; } }三.分出胜负五子棋判断输赢无非就是某一方的棋在一条直线上连出5个棋子由此我们可以分成以下四种情况1.纵向连成5个棋子2.横向连成5个棋子3.斜率 1时连成五个棋子4.斜率 -1时连成五个棋子1.纵向判断在这里我使用的是纵向遍历的方法最开始纵向的五个值沿着纵向进行遍历。void Win1(char Board[ROM][COL], int rom, int col, char *Ret) { int x 0; int y 0; for (y 0; y col; y *Ret ! W) { for (x 0; x rom - 5; x) //(rom - 5)是指后面的四个数不需要在遍历Board[x 4]已经将其覆盖了 { if (Board[x][y] Board[x 1][y] Board[x 1][y] Board[x 2][y] Board[x 2][y] Board[x 3][y] Board[x 3][y] Board[x 4][y] Board[x][y] ! ) // 这里的方法很笨直接判断5个值知否相等 { *Ret W; //这里的指针用于直接改变结果不需要再返回值 break; } } } }2.横向判断与纵向判断一样最开始横向的五个值沿着横向进行遍历。void Win2(char Board[ROM][COL], int rom, int col, char *Ret) { int x 0; int y 0; for (x 0; x rom; x *Ret ! W) { for (y 0; y col - 5; y) { if (Board[x][y] Board[x][y 1] Board[x][y 1] Board[x][y 2] Board[x][y 2] Board[x][y 3] Board[x][y 3] Board[x][y 4] Board[x][y] ! ‘ ’) { *Ret W; break; } } } }3.斜率 1方向判断方法与前面一样只不过变成了倾斜的方向起始点为左上角(11)从红线开始遍历沿着斜率 -1的白线到方向一条路向左下遍历到5个红点的位置另一条路向右上遍历到仅容纳5个点的右上角。每向 (白线)- 1方向(上/下)挪动时都会少遍历一格直至仅有五格的时候停止遍历因此在循环的时候ROM和COL均减去5。void Win3(char Board[ROM][COL], int rom, int col, char *Ret) { int x 0; int y 0; int xs 0; int ys 0; for (xs 0; xs rom - 5 *Ret ! W; xs)//沿-1方向向下遍历 { x xs; for (y 0; y col - 5 x rom - 5; y, x)// { if (Board[x][y] Board[x 1][y 1] Board[x 1][y 1] Board[x 2][y 2] Board[x 2][y 2] Board[x 3][y 3] Board[x 3][y 3] Board[x 4][y 4] Board[x][y] ! ) { *Ret W; break; } } } for (ys 0; ys col - 5 *Ret ! W; ys)//沿-1方向向上遍历 { y ys; for (x 0; y col - 5 x rom - 5; y, x) { if (Board[x][y] Board[x 1][y 1] Board[x 1][y 1] Board[x 2][y 2] Board[x 2][y 2] Board[x 3][y 3] Board[x 3][y 3] Board[x 4][y 4] Board[x][y] ! ) { *Ret W; break; } } } }4.斜率 -1方向方法与斜率 1相同不同点在于改变了起始点为(COL - 1)这次是从白线开始遍历沿着斜率 1红线的方向分为两条路第一条为左上第二条为右下遍历到仅容纳5个点的位置停止遍历。//赢法四(判断斜率 -1时) void Win4(char Board[ROM][COL], int rom, int col, char *Ret) { int x 0; int y 0; int xs 0; int ys col - 1; for (xs 0; xs rom - 5 *Ret ! W; xs) { x xs; for (y col - 1; y 4 x rom - 5; y--, x) { if (Board[x][y] Board[x 1][y - 1] Board[x 1][y - 1] Board[x 2][y - 2] Board[x 2][y - 2] Board[x 3][y - 3] Board[x 3][y - 3] Board[x 4][y - 4] (Board[x][y] # || Board[x][y] *)) { *Ret W; break; } } } for (ys col - 1;ys 4 *Ret ! W; ys--) { y ys; for (x 0; y 4 x rom - 5; y--, x) { if (Board[x][y] Board[x 1][y - 1] Board[x 1][y - 1] Board[x 2][y - 2] Board[x 2][y - 2] Board[x 3][y - 3] Board[x 3][y - 3] Board[x 4][y - 4] (Board[x][y] # || Board[x][y] *)) { *Ret W; break; } } } }根据以上四种判断方法我们就可以判断谁胜谁负了但在此之前还是要把他们整理到一起//判断赢得对局 char WinMatch(char Board[ROM][COL]) { char Ret F; //Ret W;判断出了结果的同时也不会再跑完其他的函数 Win1(Board, ROM, COL, Ret); Win2(Board, ROM, COL, Ret); Win3(Board, ROM, COL, Ret); Win4(Board, ROM, COL, Ret); return Ret; }优化和完善一下我们的对局//对局 void Match(char Board[ROM][COL], int rom, int col) { int OrWin F; // 接收返回的Ret结果 int count 0; while (1) { //玩家下棋 printf(第 %d 回合!\n,count 1); PlayerMove(Board); OrWin WinMatch(Board);//判断对局是否赢了 count; //记录回合数 if (OrWin W) { printf(恭喜你赢了!\n); printf(玩家胜利!\n\n); break; } //判断回合是否已满 else if (count rom * col) { printf(平局!\n\n); break; } //电脑下棋 printf(第 %d 回合!\n,count 1); ComputerMoves(Board); OrWin WinMatch(Board);//判断对局是否赢了 count; //记录回合数 if (OrWin W) { printf(很遗憾你输了!\n); printf(电脑胜利!\n); break; } } }头文件整理#include stdio.h #include stdlib.h #include time.h #define ROM 19 #define COL 19 //菜单 void menu(); //五子棋主函数 void Game(); //主函数(内部) //初始化棋盘 - 初始化空格 void InitBoard(char Board[ROM][COL], int rom, int col); //打印棋盘 void DisplayBoard(char Board[ROM][COL], int rom, int col); //玩家下棋 void PlayerMove(char Board[ROM][COL]); //电脑(简单)算法下棋 void ComputerMoves(char Board[ROM][COL]); //对局 void Match(char Board[ROM][COL], int rom, int col); //判断赢得对局 char WinMatch(char Board[ROM][COL]); //判断赢得对局(内部) //赢法一(判断列) void Win1(char Board[ROM][COL], int rom, int col, char *Ret); //赢法二(判断行) void Win2(char Board[ROM][COL], int rom, int col, char *Ret); //赢法三(判断斜率 1时) void Win3(char Board[ROM][COL], int rom, int col, char *Ret); //赢法四(判断斜率 -1时) void Win4(char Board[ROM][COL], int rom, int col, char *Ret);Game函数void Game() { //储存数据 - 二维数组 char Board[ROM][COL] {0}; //初始化棋盘 - 初始化空格 InitBoard(Board, ROM, COL); //打印棋盘 DisplayBoard(Board, ROM, COL); //对局 Match(Board, ROM, COL); }除Game函数以外我将其他的函数都放到了源文件(2)并将其命名为Game这样我们就成功完成了我们的五子棋小游戏第三步编写电脑玩家由于我水平确实很有限就电脑玩家写了就有1000行真就是技术不够代码来凑啊...所以在这里我只给大家分享一下思路也欢迎各位的指点交流1.分类//电脑算法头文件 #include stdio.h #include stdlib.h #include time.h #define ROM 19 #define COL 19 //电脑(简单)算法下棋(内部) void ComputerMain (char Board[ROM][COL], int *x, int *y, int *stop); //算法(初始点设定) void ComputerC1(char Board[ROM][COL], int *px, int *py, int *stop); //算法(随机点产生) void ComputerC2(char Board[ROM][COL], int *px, int *py, int *stop); //连珠算法常规 //算法一(纵向) void ComputerM1(char Board[ROM][COL],int *px, int *py, int *stop, int N); //算法二(横向) void ComputerM2(char Board[ROM][COL], int *px, int *py, int *stop, int N); //算法三(斜率 1方向) void ComputerM3(char Board[ROM][COL], int *px, int *py, int *stop, int N); //算法四(斜率 -1方向) void ComputerM4(char Board[ROM][COL], int *px, int *py, int *stop, int N); //算法一选择 void ComputerM1_1(char Board[ROM][COL],int x, int y, int *px, int *py, int *stop, int N); //算法二选择 void ComputerM2_1(char Board[ROM][COL],int x, int y, int *px, int *py, int *stop, int N); //算法三选择 void ComputerM3_1(char Board[ROM][COL],int x, int y, int *px, int *py, int *stop, int N); //算法四选择 void ComputerM4_1(char Board[ROM][COL],int x, int y, int *px, int *py, int *stop, int N); //一字缺口算法 //缺口算法一(纵向) void ComputerG1(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法二(横向) void ComputerG2(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法三(斜率 1方向) void ComputerG3(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法四(斜率 -1方向) void ComputerG4(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法五纵向 void ComputerG5(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法六(横向) void ComputerG6(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法七(斜率 1方向) void ComputerG7(char Board[ROM][COL], int *px, int *py, int *stop); //缺口算法八(斜率 -1方向) void ComputerG8(char Board[ROM][COL], int *px, int *py, int *stop); //回字缺口算法 //缺口算法九(空心三乘三) void ComputerG9(char Board[ROM][COL], int rom, int col, int *px, int *py, int *stop); //缺口算法十(空心七乘七) void ComputerG10(char Board[ROM][COL], int rom, int col, int *px, int *py, int *stop); //缺口算法十一(空心九乘九) void ComputerG11(char Board[ROM][COL], int rom, int col, int *px, int *py, int *stop); //缺口算法(回字三乘三)四向 void ComputerG9_1(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字七乘七)纵向 void ComputerG10_1(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字七乘七)横向 void ComputerG10_2(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字七乘七)斜率 1方向 void ComputerG10_3(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字七乘七)斜率 -1方向 void ComputerG10_4(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字九乘九)纵向 void ComputerG11_1(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字九乘九)横向 void ComputerG11_2(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字九乘九)斜率 1方向 void ComputerG11_3(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(回字九乘九)斜率 -1方向 void ComputerG11_4(char Board[ROM][COL], int x, int y, int *ret1, int *ret2); //缺口算法(选择输出) void Cret(char Board[ROM][COL], int x, int y, int *ret1, int *ret2);2.输出顺序下面的注释是对一个标题的整体解释四个为一组的标题依然是按照 纵向、横向、k 1、k -1 来编写的。我方永远在敌方前面判断如果我方不符合在判断敌方是否构成威胁。void ComputerMain (char Board[ROM][COL],int *x, int *y, int *stop) { //五子缺一子 ComputerG8(Board, x, y, stop); //五子缺一子说白了就是连成了4个棋子 ComputerG7(Board, x, y, stop); //我方:下到这一步直接胜利 ComputerG6(Board, x, y, stop); //敌方:必须封住着一颗棋子否则输 ComputerG5(Board, x, y, stop); //缺少的是中间三颗棋子的其中一颗 //四子连珠(一空/两空) ComputerM4(Board, x, y, stop,2); //四颗棋子连在一起两边有空或者一边有空 ComputerM3(Board, x, y, stop,2); //我方:下到这一步直接胜利 ComputerM2(Board, x, y, stop,2); //敌方:必须封住着一颗棋子否则输也有可能是必输 ComputerM1(Board, x, y, stop,2); //四子缺一子 ComputerG4(Board, x, y, stop); //判断四颗棋子中间两个子棋缺少任意一个 ComputerG3(Board, x, y, stop); ComputerG2(Board, x, y, stop); ComputerG1(Board, x, y, stop); //回字缺口(九乘九) ComputerG11(Board, ROM, COL, x, y, stop); //用于判断九成九的格子内的交叉棋并封堵 //建议11*11更全面我这里有一种情况会误判正在改ing //三子连珠(一空/两空)(敌方两空) ComputerM4(Board, x, y, stop,1); //三个棋子连在一起两边都有空或一边有空 ComputerM3(Board, x, y, stop,1); //我方:直接下一空拖一手两空有机会赢 ComputerM2(Board, x, y, stop,1); //敌方:一空就不用管了两空肯定要封 ComputerM1(Board, x, y, stop,1); //回字缺口(七乘七) ComputerG10(Board, ROM, COL, x, y, stop); //这里要注意这是两子和三子的交叉棋 //一个方向两子加三子的情况之和就有5种 //交叉方式更多所以要格外注意目前我这里没发现漏洞 //回字缺口(三乘三) ComputerG9(Board, ROM, COL, x, y, stop); //这里就是十字棋的情况了优先级比较低但是也够用 //电脑二子连珠(两空) ComputerM4(Board, x, y, stop,0); //这里就是我方的下棋方法了我偏向于斜着下 ComputerM3(Board, x, y, stop,0); //这里面是很值得研究的但是我下棋本就没啥章法 ComputerM2(Board, x, y, stop,0); //所以这块我也没有什么路数等Bug修完打算再研究 ComputerM1(Board, x, y, stop,0); //如果是敌方:我直接就没管他 //电脑的初始点 ComputerC1(Board, x, y, stop); //初始点我设置了几个我自认为毕竟好的开局点位我防止电脑开局乱下 //电脑的随机点 ComputerC2(Board, x, y, stop); //我设置了两个随机点 //第一个我以中心为范围的7*7随机点注意不要死循环 //第二个我设的全局随机点防止7*7范围下满 }我的思路可能不全打算等在学一段时间之后又能好的方法可以将我原先的代码更替掉然后研究一下大佬们的思路。也慢慢的去学习如何做到高内聚低耦合现在感觉还是多少又一些困难再接再厉吧