主程序梳理
主干思路
一个三子棋游戏首先需要的是一个棋盘,然后这个棋盘要被玩家看到,接下来是玩家行动,判断一次输赢,电脑行动判断一次输赢,以此往复。这样就可以保证程序能正常结束,不会出现棋盘占满了还是没有结束这种事。
注意:在判断输赢中不止要判断玩家还是电脑胜利,还要判断是否出现平局,防止出现死循环。
在这一次的游戏编写中行数比较多,所以我们添加一个头文件(.h),在头文件中放入各种函数的初始化,这样在主程序中我们就只用包含这个头文件就可以使用各种函数了。
头文件设计如下
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 3
#define COL 3
//初始化棋盘
void Initinalboard(char board[ROW][COL], int row, int col);
//使棋盘显现出来
void Displayboard(char board[ROW][COL], int row, int col);
//玩家行动
void Playermove(char board[ROW][COL], int row, int col);
//电脑行动
void Computermove(char board[ROW][COL], int row, int col);
//判断输赢
void Judge(char board[ROW][COL], int row, int col);
需函数做的事
棋盘初始化
初始化数组
在还没有开始游戏之前,我们让数组中的所有元素都等于‘ ’,一个一个赋值的话效率太低了,所以我们直接使用两个for循环来解决这个问题,代码如下
void Initinalboard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i <= row; i++)
{
for (j = 0; j <= col; j++)
board[i][j] = ' ';
}
}
注意:1.这一段代码中的两个变量i,j都是局部变量,一旦出了这个函数就无法使用了,所以后续函数中可以将变量名再次起名为i和j。
2.当两个for循环嵌套的时候,先进入第一个循环然后再进入第二个循环,执行完一遍第二个循环后,如果还满足第一个循环就会再执行一遍第二个循环,j变量也会重新变为0。
建立棋盘的基本形状
建立棋盘的基本形状相对比较简单,只用我们先打印数据行再打印分隔行就行了。行和列的初始化在头文件中就已经完成。既然已经初始化好了棋盘,那么,接下来就是要将棋盘打印出来,让玩家能看到棋盘。实现这一目的的代码如下
void Dispalyboard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
//先定义好数据
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//再打印分段
if(i < row - 1)
printf("----|----|----\n");
}
}
但这个代码有一个不足之处——当我们更改row的值的时候不能继续将棋盘扩充为正方型,而是一个三列的长方形。效果如下
为了使程序更严谨和方便,我们将代码进一步完善如下
void Displayboard(char board[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
//先定义好数据
for (j = 0; j < col; j++);
{
printf("%c", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//再打印分段
if (i < row - 1)
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
效果如下(将ROW改为5)
这使得后续修改更方便,也使代码更严谨。
对于玩家
选择游戏开始
要开始游戏了,我们先呈现出一个让玩家明白有什么操作的菜单,这一函数也要在头文件中包含定义。代码如下
void menu()
{
printf("**************************\n");
printf("******* 1. game *******\n");
printf("******* 2. exit *******\n");
printf("**************************\n");
}
给出了菜单也要让玩家能够进行选择,上代码
int main()
{
int input = 0;
do
{
again:
menu();
printf("请选择:\n");
scanf("%d", &input);
switch(input)
{
case 1:
game();
break;
case 2:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
goto again;
}
break;
} while (input);
return 0;
这样的代码可以让玩家出现逆反心理时(比如非要选一个3试试)让代码回到初始位置再让玩家选择,选择do循环的原因是do循环至少执行一遍,完美满足我们的需要。
在选择开始游戏之后棋盘出现在我们的电脑上,我们也要让玩家开始落子和电脑进行博弈了
如何落子
在最开始编译程序的时候我们就初始化了一个数组,接下来就是数组发挥作用的时候了,我们将空缺出来的,被玩家选中的数组元素(”空格“)更改为一个特定的玩家棋子(*),这样就算落下一子了。实现代码如下
void Playermove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("请玩家落子:\n");
while (1)
{
scanf("%d%d", &x, &y);
if (x > 0 && x <= row && y >0 && y <= col)
{
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("这一格已被占用,请选择别的格");
}
}
else
{
printf("您选的格子不存在,请重新选择\n");
}
}
}
检测落子位是否被占用
实现检测的代码也在上面一段中,所谓检测其实就是检查所选中的数组元素是否为空,如果为空,那么就进行落子操作,如果不为空,那就说已被占用。
注意:1.玩家不是程序员所以不会知道数组下标是从0开始的,要将这一细节考虑进我们写的代码中,也就是将玩家输入的横竖坐标分别-1,这样就可以让代码正常运行了。
2.玩家有可能输入超出数组设置好的长宽,所以在执行代码前要先检验玩家输入的坐标是否在合理范围内。
对于电脑
如何落子
电脑的落子和玩家很相似,只是少了一步输入,并且不用对横竖坐标分别-1。代码如下
void Computermove(char board[ROW][COL], int row, int col)
{
printf("电脑落子\n");
while (1)
{
int x = 0;
int y = 0;
x = rand() & row;
y = rand() & col;
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '#';
break;
}
}
}
由于我的技术目前有限,所以我只能做到让电脑随机落子
随机生成数字一般用时间戳来实现。要用时间戳就要包含头文件<time.h>
并且在main函数前列写上如下代码
srand((unsigned int)time(NULL));
这会使生成的数完全随机,而不是之前的伪随机。
检测落子位是否被占用
实现这一目的的代码就是上上条代码中的判断语句,它的目的就是让数组中的空变为电脑落下的子。
判断输赢,结束游戏
最后终于到了判断谁输谁赢的时间,代码如下
//玩家赢“*”
//电脑赢“#”
//平局“P”
//继续游戏“Q”
char Judge(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')//判断一横行是否一样
{
return board[i][0];//返回一样的符号
}
}
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')//判断一竖行是否一样
{
return board[0][i];//返回一样的符号
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')//判断\是否一样
{
return board[1][1];//返回一样的符号
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')//判断/是否一样
{
return board[1][1];//返回一样的符号
}
//判断平局
if (Isfull(board, row, col))
{
return 'P';
}
return 'Q';
}
用for循环代替一部分重复的劳动,我们列出四种胜利的条件并进行判断。
注意:每当玩家或电脑走一步之后就要进行一次判断,这样才不会出现漏判。