目录
第一步编写界面
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 – play,0 – exit,default – 输入有误。
int input = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch(input){case 1:{printf("游戏开始!n");Game(); //游戏的实现break;}case 0:{printf("退出游戏!n");break;}default:{printf("输入错误,请重新输入!nn");}}}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 19n");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.h>
srand((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 %dnn", 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 %dnn", 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方向
判断方法与前面一样,只不过变成了倾斜的方向,起始点为左上角(1,1)
从红线开始遍历,沿着斜率 == -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("玩家胜利!nn");break;}//判断回合是否已满else if (count == rom * col){printf("平局!nn");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范围下满
}
我的思路可能不全,打算等在学一段时间之后,又能好的方法可以将我原先的代码更替掉,然后研究一下大佬们的思路。也慢慢的去学习如何做到高内聚低耦合,现在感觉还是多少又一些困难再接再厉吧!