首先分析三子棋代码的写作步骤,
大体如下:
一、开始菜单
二、棋盘
三、玩家下棋
四、电脑下棋
五、胜利判断
以及实现这几个部分需要用到的函数
1、初始化棋盘 InitBoard
2、打印棋盘 DisplayBoard
3、玩家下棋 PlayerMove
4、电脑下棋 ComputerMove
5、玩家胜利 PlayerWins
6、电脑胜利 ComputerWins
接下来我们一一实现。
一、开始菜单
首先打印菜单,由游玩者输入1或者0选择是否玩游戏。
利用switch语句选择。
这里直接通过input的值判断是否结束循环,较为简便:
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//要在主函数中
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("请输入1或者0\n");
break;
}
} while (input);
return 0;
}
但如果这样使用,注意input需定义在循环之外。
Tips:写逻辑较长的代码时,养成逐步测试的习惯,便于出现问题时快速定位。
如:
这里用“三子棋”代替实际的游戏函数,测试当前代码是否有问题
二、棋盘
通过三子棋棋盘的形状,容易联想到二维数组,便于理解,于是使用二维数组来作为棋盘格子。
通过#define定义行列,这样便于后续修改,
同时所有有关行列的代码都要注意,应面向ROW与COL写代码,而不是“3”.
函数1、初始化棋盘 InitBoard
使用for循环对二维数组进行赋值,十分简易,不多赘述。
也可以在创建数组时直接初始化为空格。
void InitBoard(char Board[ROW][COL]) { int i = 0; for (i = 0; i < ROW; i++) { int j = 0; for (j = 0; j < COL; j++) { Board[i][j] = ' '; } } }
初始化为空格是为了打印棋盘后的美观
函数 2、打印棋盘 DisplayBoard
void DisplayBoard(char Board[ROW][COL])
{
int i = 0;
for (i = 0; i < ROW; i++)
{
int j = 0;
for (j = 0; j < COL; j++)
{
printf(" %c ", Board[i][j]);
if (j < COL - 1)
{
printf("|");
}
}
printf("\n");
if (i < ROW - 1)
{
int j = 0;
for (j = 0; j < COL; j++)
{
printf("---");
if (j < COL - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
逻辑较为简单,但初见可能不好理清棋盘分割线的打印,理解如下:
![]()
此处棋盘分割线的打印,依照右图分为蓝色部分与红色部分,分别打印
三、玩家下棋
玩家输入坐标,将数组坐标处的空格赋值为*,视为玩家下的棋子:
void PlayerMove(char Board[ROW][COL])
{
printf("请玩家输入坐标,用空格分隔:>");
int row = 0;
int col = 0;
while (1)
{
scanf("%d%d", &row, &col);
if (row >= 1 && row <= ROW && col >= 1 && col <= COL)
{
if (Board[row - 1][col - 1] == ' ')
{
Board[row - 1][col - 1] = '*';
break;
}
else
{
printf("请重新输入:>");
}
}
else
{
printf("请重新输入:>");
}
}
}
下过棋之后再次调用DisplayBoard打印棋盘,直观的展现给玩家棋盘情况。
同样,使用while(1)测试代码可行性
此部分有几个地方需要注意:
第一,玩家若输入坐标 1 1,实际在数组中的位置应该为Board[0][0],故而赋值时应注意减一。
第二,除了正确输入坐标外,还存在输入的坐标超过棋盘格子范围,或是该格子已经被下过了的情况,需要考虑到。
第三,写成死循环,保证玩家最终能够正确的下完这一步棋。
四、电脑下棋
从简单的做起,不考虑电脑下棋的智能,先让电脑随机下棋;
代码实现如下:
void ComputerMove(char Board[ROW][COL])
{
int row = 0;
int col = 0;
while (1)
{
row = rand() % ROW;
col = rand() % COL;
if (Board[row][col] == ' ')
{
Board[row][col] = '#';
break;
}
}
printf("电脑下棋:>%d %d\n", row + 1, col + 1);
}
同样需要注意电脑不能选择已经下过的格子下棋
五、胜利判断
此部分较其他内容较为复杂:
其一,
玩家的胜利与电脑的胜利理论上说应该是同样的逻辑,
但共用一个函数,就意味着会在一方下棋后判断另一方胜利与否,实属不必,故分为两个函数。
其二,
需要注意,在3X3的棋盘上,率先3个棋子连成一线的一方胜利,仅仅是井字棋的玩法;
井字棋的胜利方式除了横线与竖线,就只有对角线了;
而但凡修改棋盘大小,就会多出许多斜着的胜利方法,更别提修改胜利的棋子数了。
所以不能简单的按三子棋来写胜利判断。
胜利条件应该是:
对于行列为ROW与COL的棋盘,一方的棋子在行,列,斜线上连续排列WIN个棋子。
首先是行与列的判断:
int count = 0;
int row = 0;
int col = 0;
for (row = 0; row < ROW; row++)//判断行
{
for (col = 0; col < COL; col++)
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
if (count == WIN)
{
return 1;
}
}
}
count = 0;
for (col = 0; col < COL; col++)//同样逻辑,判断列
{
for (row = 0; row < ROW; row++)
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
if (count == WIN)
{
return 1;
}
}
}
count = 0;
逻辑基本一致 ,需要注意的是如果按本文逻辑写胜利判断,那么在行或列的判断结束后,必须将count重新赋值为0,避免影响后续判断。
接着是最为复杂的斜线部分,
这里分为与棋盘上边缘相交的斜线,以及与棋盘下边缘相交的斜线。
上边缘部分:
for (col = 0; col < COL; col++)
{
int tmp = col;
row = 0;
while ((row >= 0) && (row < ROW) && (col >= 0) && (col < COL))
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
row++;
col--;
if (count == WIN)
{
return 1;
}
}
row = 0;
col = tmp;
count = 0;
while ((row >= 0) && (row < ROW) && (col >= 0) && (col < COL))
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
row++;
col++;
if (count == WIN)
{
return 1;
}
}
col = tmp;
count = 0;
}
下边缘部分:
for (col = 0; col < COL; col++)
{
int tmp = col;
row = ROW - 1;
while ((row >= 0) && (row < ROW) && (col >= 0) && (col < COL))
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
row--;
col--;
if (count == WIN)
{
return 1;
}
}
row = ROW - 1;
col = tmp;
count = 0;
while ((row >= 0) && (row < ROW) && (col >= 0) && (col < COL))
{
if (Board[row][col] == '*')
{
count++;
}
else
{
count = 0;
}
row--;
col++;
if (count == WIN)
{
return 1;
}
}
col = tmp;
count = 0;
}
此处需要注意的地方容易导致函数崩坏:
第一,每条“线”(包括横线,竖线,斜线)判断结束后,要将count重新赋值为0;
第二,因判断过程中会改变row与col的值,故需要在下次使用前将row与col还原成修改前的值。
此节上述代码仅为PlayerWins的逻辑;
ComputerWins与其几乎完全相同,只需将六处‘*’改为‘#’。
以及,这种胜利判断的方式简单粗暴,也许还有更好更简便的方式,不过我没有想到:(
零、智能提升
如果只需让电脑随机选择空格下棋,实现起来非常简单,可以考虑尝试增加电脑下棋的智能性,为电脑下棋设置一些优先级,例如:
1、如果电脑快要取胜,则优先胜利
2、如果玩家快要取胜,则优先阻止
3、1优先于2
等等等等......
鉴于此部分代码的编写较前文难度高得多,笔者能力有限,故不予实现。
至此,通关N子棋。
作为我第一个实现的超微型小游戏,还是有点点成就感的。
希望能再接再厉。
以上。