问题提出
学C语言不久,尝试做三子棋,苦恼于获胜条件,胜利条件该怎么写才能在改变棋盘大小的情况下不影响胜利条件,例如在5*5的棋盘上仍是3子获胜。
一开始写三子棋的我就是是简单的罗列出在3*3棋盘三子棋获胜的所有条件进行判断,但是一旦改变了棋盘的大小或者胜利的条件,代码就完全废除了。
查阅了别人的文章也没有好的解决方法,大部分都是局限于3*3的棋盘,或者就是获胜条件不能更改。
最终潜力爆发,想出来解决方法,但是仍有改进空间!
问题概述
程序主体
关于三子棋的整体逻辑我就不赘述了,稍微简单介绍一下。
主函数用来调出菜单,并判断是否进入游戏:
#define _CRT_SECURE_NO_WARNINGS 1 #include"Sanziqi.h" int main() { int input = 0; do { menu(); printf("请选择(0或1):"); scanf("%d", &input); srand((unsigned int)time(NULL)); switch(input) { case 1: system("cls");//清屏 Rule();//跳出规则,如果改变棋盘大小和胜利条件,此处也应该修改 Game();//游戏的主体函数 break; case 0: printf("退出游戏\n"); break; default: printf("输入错误,请重新输入\n"); break; } } while (input); return 0; }
将所有的函数声明都放入在"Sanziqi.h"中 。
想要实现改变获胜条件,最最最重要的则是Tiaojian的定义,通过改变Tiaojian则可以改变获胜条件,如改成4则会变为四子棋,实现的步骤请看下文。
#define _CRT_SECURE_NO_WARNINGS 1 #define ROW 3 #define COLUMN 3 //改变ROW COLUMN就可以改变棋盘大小 #define Tiaojian 3 #include<stdio.h> #include<stdlib.h> #include<time.h> void menu(); void Rule(); void Game(); void Initialize_Board(char board[ROW][COLUMN]); void Display_Board(char board[ROW][COLUMN]); void Player_change_Board(char board[ROW][COLUMN]); char Judge(char board[ROW][COLUMN]); void Computer_change_Board(char board[ROW][COLUMN]); int Isfull(char board[ROW][COLUMN]);
以下是此次游戏的逻辑,初始化棋盘,展示棋盘
{玩家输入棋子坐标,展示棋盘,判断是否获胜,电脑输入棋子坐标,展示棋盘,判断是否获胜}
直到{}内的步骤确定玩家获胜还是电脑获胜,结束此次游戏,回到菜单。
补充一点,由于棋盘是固定的,所以传参只要根据头文件中定义的ROW和COLUMN,也就是棋盘的行和列为已知的,所以只需要输入数组名就可以改变棋盘内容,判断胜负。
void Game() { char board[ROW][COLUMN] = { 0 }; Initialize_Board(board); Display_Board(board); char tmp; while (1) { Player_change_Board(board); Display_Board(board); tmp = Judge(board); if (tmp != 'c')break; Computer_change_Board(board); Display_Board(board); tmp = Judge(board); if (tmp != 'c')break; } if (tmp == '*') { printf("你赢了\n"); } else if (tmp == '#') { printf("你输了\n"); } else { printf("平局\n"); } }
接下来是除了Judge函数(也就是判断胜负的函数)的简析
可以跳至Judge
函数简析
初始化棋盘(Initialize_Board)
将二维数组board所有中的元素初始化为空格
void Initialize_Board(char board[ROW][COLUMN]) { int i; int j; for (i = 0; i < ROW; i++) { for (j = 0; j < COLUMN; j++) { board[i][j] = ' '; } } }
展示棋盘(Display_Board)
简单来说就是打印二维数组。
棋盘的设计个人看来无关紧要,甚至不需要棋盘,只需要能将二维数组board每个元素依次打印出来即可,每个格子可大可小,怎么美观怎么来
如果不追求程序的美观,代码就会看起来简洁美观哈哈哈
void Display_Board(char board[ROW][COLUMN]) { int i = 0; int j = 0; for (i = 0; i < ROW; i++) { for (j = 0; j < COLUMN; j++) { if (j < COLUMN - 1)printf(" %c |", board[i][j]); else printf(" %c \n", board[i][j]); } if(i<ROW-1) { for (j = 0; j < COLUMN; j++) { if (j < COLUMN - 1)printf("---|"); else printf("---\n"); } } } }
玩家下棋(Player_change_Board)
这块函数无非就是判断输入的坐标(x1,y1)有没有超过(ROW,COLUMN),或者有没有小于(1,1),或者这个坐标上是不是已经有棋子了
由于人与机器识别有差别,第一个点一般来说是(1,1),但是考虑的数组的下标从零开始,所以将x1和y1都减一,复合人的习惯。
简单的循环判断:
void Player_change_Board(char board[ROW][COLUMN]) { printf("请输入你要下的位置:"); while (1) { int x1, y1; scanf("%d %d", &x1, &y1); if (x1 > ROW || y1 > COLUMN || x1 <= 0 || y1 <= 0) { printf("输入值错误,重新输入:"); } else { if (board[x1 - 1][y1 - 1] != ' ') { printf("此处已有棋子重新输入:"); } else { board[x1 - 1][y1 - 1] = '*'; break; } } } }
电脑下棋( Computer_change_Board)
电脑下棋采用了随机的方法,这样子的电脑比较笨,但是赢得几率并不是0%(%d写多了,都快忘记百分之零是%0还是0%了)。
没有找到空的元素就一直随机,知道下出'#':
void Computer_change_Board(char board[ROW][COLUMN]) { printf("电脑下棋:\n"); int i, j; while (1) { i = rand() % ROW; j = rand() % COLUMN; if (board[i][j] == ' ')break; } board[i][j] = '#'; }
判断棋盘是否满载( Isfull)
这里的返回值用int是因为只有两种情况,可以方便选择语句或者循环语句判断。
如果棋盘满了就爆炸,输出1:
int Isfull(char board[ROW][COLUMN]) { int i, j; for (i = 0; i < ROW; i++) { for (j = 0; j < COLUMN; j++) { if (board[i][j] == ' ')return 0; } } return 1; }
判断胜利条件(Judge)
Judge()主体
不可避免的我们需要判断胜利的条件,也将这个函数单独拎出来,原因也是这个函数前前后后一直再改,终于改成了一个较为通用的函数,能够应对所有棋盘以及不同的获胜条件:
char Judge(char board[ROW][COLUMN]) { int i, j; for (i = 0; i < ROW; i++) { int tmp = 0; for (j = 0; j <COLUMN-1; j++) { if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[i][j]; } } for (i = 0; i < COLUMN; i++) { int tmp = 0; for (j = 0; j < ROW - 1; j++) { if (board[j][i] == board[j+1][i] && board[j][i] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[j][i]; } } for (i = 0; i <=ROW-Tiaojian; i++) { int tmp = 0; for (j = 0; j <= COLUMN - Tiaojian; j++) { int k = 1; while ((i + k) < ROW && (j + k) < COLUMN) { if (board[i][j] == board[i + k][j + k] && board[i][j] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[i][j]; k++; } } } for (i = 0; i <=ROW-Tiaojian; i++) { int tmp = 0; for (j = COLUMN-1; j >=Tiaojian-1; j--) { int k = 1; while ((i + k) < ROW && (j - k) >=0) { if (board[i][j] == board[i + k][j - k] && board[i][j] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[i][j]; k++; } } } if(Isfull(board))return 'q'; return 'c'; }
定义获胜条件以及棋盘大小
代码很长,由我慢慢说明 。
之前所有的函数都以ROW和COLUMN作为参数进行运算,也就是改变了这了两个的值就能够改变上述函数的结果。
如打印出的棋盘大小和数组board的元素个数。
而获胜条件则用Tiaojian代表,它的意思是需要多少个棋子才能够胜利:
#define ROW 3 #define COLUMN 3 #define Tiaojian 3
行和列的判断
行与列的判断较为简单,i的在它的第一个for循环中代表着行数。
三子棋由于棋盘只有3*3所以本来不用考虑是否连续,但是如果在一个更大的棋盘,就得考虑一行有三个棋子但是中间不能有其他的棋子参杂。
board[i][j] == board[i][j + 1]此时j代表着列数,行数不变,列数加一。
所以用tmp表示连续且相同的次数,如果是三子那么就是需要两次,如果是四子那么就是需要连续判断三次。
i在它的第二个for循环代表着列数,但是原理相同,就不赘述了:
for (i = 0; i < ROW; i++) { int tmp = 0; for (j = 0; j <COLUMN-1; j++) { if (board[i][j] == board[i][j + 1] && board[i][j] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[i][j]; } } for (i = 0; i < COLUMN; i++) { int tmp = 0; for (j = 0; j < ROW - 1; j++) { if (board[j][i] == board[j+1][i] && board[j][i] != ' ')tmp++; else tmp = 0; if (tmp == Tiaojian - 1)return board[j][i]; } }
斜线的判断
在我看来对角线的判断无疑是最困难的,再扩大棋盘时尤为复杂。
我们只需要看斜线的第一个点,能不能斜着延申出去,并且连续Tiaojian个。
如果斜线向右,数组元素就会从(0,0)开始查找,这条斜线的第一个点的行数(列数)下标不能大于棋盘行数(列数)减去Tiaojian,否则就会超出棋盘,也就是不成立获胜条件。
继续引入n和m变量,比较的两个数组元素,它们的行数和列数都要进行加,这样子才能保证它们时连续的
斜线向左的情况则大同小异,行数与上面一样的递增,列数则是从最后一列递减:
for (i = 0; i <=ROW-Tiaojian; i++) { int tmp1 = 0; for (j = 0; j <= COLUMN - Tiaojian; j++) { int n1 = 0; int m1 = 1; while ((i + m1) < ROW && (j + m1) < COLUMN) { if (board[i+n1][j+n1] == board[i + m1][j + m1] && board[i+n1][j+n1] != ' ')tmp1++; else tmp1 = 0; if (tmp1 == Tiaojian - 1)return board[i][j]; m1++; n1++; } } } for (i = 0; i <=ROW-Tiaojian; i++) { int tmp2 = 0; for (j = COLUMN-1; j >=Tiaojian-1; j--) { int n2 = 0; int m2 = 1; while ((i + m2) < ROW && (j - m2) >=0) { if (board[i+n2][j-n2] == board[i + m2][j - m2] && board[i+n2][j-n2] != ' ')tmp2++; else tmp2 = 0; if (tmp2 == Tiaojian - 1)return board[i][j]; m2++; n2++; } } }
结语(后续优化)
真的是一场酣畅淋漓的实战应用,结合了我这段时间所有学习的知识,更是感慨想这么简单的小游戏都要如此绞尽脑汁。
目前也不知道是否有bug存在,也希望能有大佬指导,感觉自己的水平真的差得不行。
而且肉眼可见的程序还可以进行优化,比如说棋盘变大了,电脑如果还是随机下,那么电脑会显得更加笨蛋。
以及Judge的判断,每次都是将整个棋盘的数据进行判断。可以根据每次下的棋子来判断是否满足胜利条件,这样子就不用每次都将整个棋盘判断。
如果有问题!欢迎指出!!!