你好哦,这里是云切斩月(Echo_Fish),记录一下新学的东西,写的不好,佬们轻点喷!如果有错误的话还请犇和佬们不吝赐教😗。
首先需要知道一个完整的三子棋程序流程分为几步
使用二维数组存放代表正方与反方的棋子,该数组初始化时将其全部初始化为空格,以便打印的时候进行观察。
打印棋盘。
定义一个接口用于编写正方(玩家)落子逻辑。
定义一个接口用于编写反方(电脑)落子逻辑。
定义一个接口用于判断哪方胜利,或者平局,或者游戏胜利。
明晰了程序框架之后,写程序就方便许多了。在写程序的时候一定要注意程序的复用性和维护性,也就是说——一个函数只有一个功能。将一个大集合拆成多个小部分,日后在需要相同功能的函数的时候直接复制粘贴即可。
首先创建一个.h后缀的表头文件,存放我们之后会用到的头文件的引用,函数的声明与宏定义。
代码如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:4996)
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//符号定义
#define ROW 3
#define COL 3
//函数声明
//初始化棋盘
void InitBoard(char board[][COL],int row, int col);
//打印棋盘
void showBoard(char board[][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);
//判断输赢平局 或继续
char isWin(char board[ROW][COL], int row, int col);
在使用自定义文件的时候,其他源文件引用时需要修改为如下形式:
#include<stdio.h> //这是标准头文件
#include "game1.h" //这是自定义头文件
回到正题,main函数编写为以下形式:
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//随机数 需要#include<time.h>该头文件
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩三子棋游戏\n");
game(); //在该函数中嵌套调用各功能接口
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
return 0;
}
这里switch 使用0来进入退出程序的分支是有意义的,因为0代表着假,while()中的input如果为0,就停止循环。可以退出整个程序。
注:菜单写不写其实无所谓。
接着开始进入主要功能的编写,新创建一个源文件单独用于存放实现功能的接口。
首先是初始化棋盘:只要将在 game() 接口中定义好的数组与上文使用的宏定义传入即可,将二维数组中所有元素全部初始化为空格。
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0,
j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
接着是打印棋盘功能:参数与初始化棋盘的函数相同,打印这点没什么难的,如果你想让棋盘好看一点也可以自己设计设计,代码如下:
void showBoard(char board[][COL], int row, int col)
{
int i = 0,
j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
printf(" %c", board[i][j]);
if(j < col-1) //在最后一列时不打印'|'
printf("|", board[i][j]);
}
printf("\n"); //开始第二行 划分棋盘
if (i != 2)
{
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
{
printf("|"); //在最后一列时不打印'|'
}
}
}
printf("\n");
}
}
效果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/dfb57d5a11f9d2464bdb96754720d401.png)
然后是正方落子逻辑,正方的子以‘*’表示。这里我们需要思考几点
玩家可能不是程序员,不了解第0行与第0列的概念,例如他想要在0 0行落子,实际上输入的是
1 1这两个数字,所以我们就需要对程序进行处理。
b.玩家可能会越界落子,同样需要进行限制,越界落子就需要通知他,并且在落子正确之前不断循环。
c.若原位置不为空格,同样不能落子,需要进行限制。
清楚这两点之后就可以写出如下代码:
void playerMove(char board[ROW][COL], int row, int col) //玩家落子
{
printf("请输入坐标:>");
while (1)
{
int x = 0,
y = 0;
scanf("%d %d", &x, &y);
if (x <= ROW && x >= 1 && y <= COL && y >= 1)
{
if (board[x - 1][y - 1] == ' ')//落子位置处理
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("重新输入,坐标有误\n");
}
}
else
{
printf("重新输入,坐标有误\n");
}
}
}
之后是反方(电脑)落子逻辑,反方落子以‘#’表示。这个就好写很多了,因为电脑可不会乱来,是你让他干什么他就干什么,不会像人一样难以控制。
只需要简简单单的随机数函数即可,并且不断循环,落子判断条件同正方,只是不用写越界的限制了。
void computerMove(char board[ROW][COL], int row, int col)
{
printf("电脑落子\n");
//电脑可不用担心会溢出 说多少就多少
//检测当前位置是否有子 若有子则返回
int x = 0,
y = 0;
while (1)
{
x = rand() % row,//0-2
y = rand() % col;//0-2
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
到了这里,三子棋的编写已经进入了尾声,我们仅仅缺少判断赢、平局或者继续的功能,再之后是将之前编写的东西进行整合即可。
判断某方赢的条件:横竖三行,正反对角线为同一符号,正方为'*' 反方为'#'。如果未达成条件就继续游戏,如果正方胜出就返回'*',反方胜出就返回'#'。
判断平局的条件:数组不存在空格并且还未有一方胜出,返回E(equal)。
判断继续条件:未有一方胜出,未平局,返回C(continue)。
代码如下:
char isWin(char board[ROW][COL], int row, int col)
{
int i;
//玩家一行三个
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] == '*')
{
return '*';
}
}
//玩家一列三个
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] == '*')
{
return '*';
}
}
//玩家对角线三个
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] == '*')
{
return '*';
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] == '*')
{
return '*';
}
//电脑一行三个
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] == '#')
{
return '#';
}
}
//电脑一列三个
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] == '#')
{
return '#';
}
}
//电脑对角线三个
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] == '#')
{
return '#';
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] == '#')
{
return '#';
}
//平局
int count = 0;
for (int i = 0; i < row; i++)
{
for (int j = 0; j < row; j++)
{
if (board[i][j] != ' ')
{
count++;
}
}
}
if (count == row * col)
{
return 'E';
}
//继续
else
{
return 'C';
}
}
在判断平局条件时可以用一个计数器的方法,二维数组中非空格的元素就让计数器加一,直到count变量等于二维数组的总元素个数时,代表着棋盘满了。而走到这一步,程序还没有返回‘*’或者‘#’,代表着平局。
平局条件也可以引申出继续的条件,如果不是平局就继续。直到棋盘落满子或者有某一方胜出。
在IsWin函数返回时,定义一个变量接收即可。如果是‘*’就打印棋盘,并且宣布正方胜利。
如果是‘#’同样打印棋盘,同时宣布反方胜利。
如果是‘E’打印棋盘,宣布平局。
如果是‘C’打印棋盘,不结束游戏,继续下棋逻辑。(这代表着我们需要循环)
综上所述,在整合的时候进行如下处理。
void game() //游戏相关逻辑
{
//存储数据 - 二维数组
char board[ROW][COL];
//初始化棋盘
InitBoard(board, ROW, COL);
//打印棋盘
showBoard(board, ROW, COL);
char ret = '0';
while (1)//死循环 直到有一方胜出或者平局再跳出
{
//玩家落子
playerMove(board, ROW, COL);
//落子后展示当前棋盘
showBoard(board, ROW, COL);
ret = isWin(board, COL, COL);
if (ret != 'C')//继续
break;
//电脑落子
computerMove(board, ROW, COL);
//落子后展示当前棋盘
showBoard(board, ROW, COL);
ret = isWin(board, COL, COL);
if (ret != 'C')//继续
break;
}
//跳出循环后的判断
if (ret == '*')
{
printf("玩家赢\n");
}
else if (ret == '#')
{
printf("电脑赢\n");
}
else
{
printf("平局\n");
}
//游戏结束后展示棋盘
showBoard(board, ROW, COL);
}
以上,就是本次三子棋程序的全部内容😆