Gitee源码点击我
Part 1 项目分析
一、游戏介绍
游戏在九宫格的棋盘中进行,由两位玩家落子,任意横排,竖排,对角线连成三个即为获胜 对于三子棋游戏需要呈现的内容: ①需要呈现给玩家一个3*3的棋盘 ②区分两名玩家的棋子,本项目中为玩家和电脑对局。 ③根据落子情况判断是否由任意横排三个格子,竖排三个格子,对角线三个格子的落子都为同一玩家的落子。
二、项目结构
项目采用两个.c 文件和一个.h 文件的结构来编写项目。 test.c 文件中为整个游戏的框架 game.c 文件中将游戏中具体的不同的功能编写函数的具体内容 game.h 文件中进行函数声明,以及所需的库函数的调用
Part 2 功能的代码实现
一、主函数框架
首先我们先做出一个游戏大致的框架,从主菜单→用户选择→开始游戏或者退出游戏。
[test.c]
void menu()
{
printf("***********************\n");
printf("********1.play*********\n");
printf("********0.exit*********\n");
printf("***********************\n");
}
void game()
{
}
int main
{
int n = 0;
scanf("%d",&n);
do
{
menu();
switch(n)
{
case 1:
game();
break;
case 0:
printf("thanks");
default:
printf("输入有误,请重新输入!");
}
}while (n);
}
二、功能① —— 棋盘的呈现以及程序框架:
利用**下划线 ’ _ '和竖线 ’ | '**的组合,呈现一个3*3的棋盘。如下图
从用户角度来看,用户在某个格子落子后,该格子就会从空白的状态变为有棋子的状态。
若我们将棋盘和二维数组关联,数组的行和列对应棋盘的行和列。则整个游戏本质上就变成了对二维数组元素的操作,一个格子即对应一个数组元素,不落子时,所有的数组元素值都设置为空格符。玩家或电脑在某一格落子时,则将该格对应的数组元素的值替换成代表棋子的字符(设玩家棋子为"#“,电脑棋子为”*")
于是我们先创建一个和棋盘同规模的数组,并将其初始化
[game.h] 头文件中包含了项目需要的库函数,定义的全局变量以及函数声明。
#include<stdio.h>
#define ROW 3
#define COL 3
void InitBoard(char board[ROW][COL],int row,int col);
--------------------------------------------------------------------------
[game.c] game.c文件中包含游戏具体每个功能的函数实现
#include "game.h"
void InitBoard(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] = ' ';
}
}
}
--------------------------------------------------------------------------
[test.c]
#include "game.h"
void menu(){...}
void game()
{
char board[ROW][COL] = {0};
InitBoard(board[ROW][COL],ROW,COL);
}
int main(){...}
初始化完成后,便是打印棋盘的工作:
[game.h]
#include<stdio.h>
#define ROW 3
#define COL 3
void InitBoard(char board[ROW][COL],int row,int col);
void DisplayBoard(char board[ROW][COL],int row,int col);
--------------------------------------------------------------------------
[game.c]
#include "game.h"
void InitBoard(char board[ROW][COL],int row,int col){...}
void DisplayBoard(char board[ROW][COL],int row,int col)
{
int i = 0;
int j = 0;
printf(" ");
for (i = 1; i <= col; i++)
{
printf(" %d ", i);
}
printf("\n");
for (i = 0; i < row; i++)
{
printf("%d", i + 1);
for (j = 0; j < col; j++)
{
printf(" %c ",board[i][j]);
if (j < col - 1)
{
printf("|");
}
}
printf("\n");
printf(" ");
for (j = 0; j < col; j++)
{
if (i < row - 1)
{
printf("---");
if (j < col - 1)
{
printf("|");
}
}
}
printf("\n");
}
}
--------------------------------------------------------------------------
[test.c]
#include "game.h"
void menu(){...}
void game()
{
char board[ROW][COL] = {0};
InitBoard(board[ROW][COL],ROW,COL);
DisplayBoard(board[ROW][COL],ROW,COL);
}
int main(){...}
[解析] 棋盘分三行打印,每行的打印中包括两个模块 模块一:[空格符]+[数组元素占用]+空格符+’ | 由于最后一列不需要打印竖线,则竖线’ | '单独分出来加上前置条件打印
模块二:‘—’ +’ | ’ 由于最后一行不需要打印,所以加上前置条件再打印 [优化] 为了方便用户输入坐标,可以标明棋盘的横纵坐标 优化的代码也写入了上述代码中。
三、功能② —— 实现玩家输入坐标的落子功能
其实现思想为:玩家输入坐标,程序将该坐标对应到二维数组的元素,并将其替换成玩家的棋子。而在初始化棋盘的时候,我们已经将数组的每个元素初始化为空格符’ ‘。即实现过程为:玩家输入坐标→程序找到对应的数组元素→将空格符替换成玩家棋子**’ * '。**
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("请输入坐标落子:>");
scanf("%d %d", &x, &y);
board[x - 1][y - 1] = '*';
}
除了落子之外,我们还需要考虑下面两个问题:
- 如果玩家输入的坐标不在棋盘范围之内(即数组的越界访问),程序该做何回应
- 如果玩家输入的坐标处已经有棋子了,程序该作何回应
解决方案:
- 规定好玩家的输入范围,如果越界,则提示输入错误,并重新输入
- 输入坐标后,程序检查该坐标处对应的数组元素值是否为空格符,如果不为空格符,则表示该格已经落子
[game.h]
#include<stdio.h>
#define ROW 3
#define COL 3
void InitBoard(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);
--------------------------------------------------------------------------
[game.c]
#include "game.h"
void InitBoard(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)
{
int x = 0;
int y = 0;
printf("请输入坐标落子:>");
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("输入有误,请输入1-3之间的数字:>");
}
}
}
--------------------------------------------------------------------------
[test.c]
#include "game.h"
void menu(){...}
void game()
{
char board[ROW][COL] = {0};
InitBoard(board[ROW][COL],ROW,COL);
DisplayBoard(board[ROW][COL],ROW,COL);
while(1)
{
PlayerMove(board[ROW][COL],ROW,COL);
DisplayBoard(board, ROW, COL);
}
}
int main(){...}
[运行结果]
四、功能③ —— 电脑落子
玩家落子之后,轮到电脑落子。而电脑落子需要解决的问题:
- 随机性(由于是简单项目,不考虑电脑的智能下棋)
- 随机坐标不可越界
- 随机落子时需要检验该坐标下是否有棋子,若有棋子则需要再重新生成坐标
解决方案
- 利用随机函数rand和srand来实现随机坐标的生成
- 生成随机数%3,则得到的结果只能为0,1,2。解决了越界的问题
- 重复检测与玩家落子函数中的重复检测相同
[game.h]
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 3
#define COL 3
void InitBoard(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);
--------------------------------------------------------------------------
[game.c]
#include "game.h"
void InitBoard(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)
{
int x = rand() % ROW;
int y = rand() % COl;
while (1)
{
if (board[x][y] == ' ')
{
board[x][y] = '*';
break;
}
}
}
--------------------------------------------------------------------------
[test.c]
#include "game.h"
void menu(){...}
void game()
{
char board[ROW][COL] = {0};
InitBoard(board[ROW][COL],ROW,COL);
DisplayBoard(board[ROW][COL],ROW,COL);
while(1)
{
PlayerMove(board[ROW][COL],ROW,COL);
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
DisplayBoard(board, ROW, COL);
}
}
int main(){
srand((unsigned int)time(NULL));
...
}
[运行结果]
五、功能④ —— 判断输赢
判断输赢得逻辑:
- 若下棋过程中,任意一方的棋子在行/列/对角线占满时,则该方获胜
- 若所有格子都被占满时还未出现1中情况,则双方平局
具体实现方法:
- 玩家和电脑落子后,分别调用判断输赢函数进行判断,若有某一方获胜,则函数返回值为该方棋子对应的字符。即玩家获胜时,函数返回 ‘*’,游戏结束;或者电脑获胜时,函数返回’#',游戏结束
- 解决平局的情况需要定义一个判断棋盘是否已满的函数,每次落子除了判断输赢之外,再判断一次棋盘是否已满,棋盘满子时,返回1,游戏结束,宣布平局
- 如果没有平局或输赢的情况出现,则判断输赢的函数返回字符 ‘C’,游戏继续
[game.h]
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 3
#define COL 3
void InitBoard(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);
int IsFull(char board[ROW][COL], int row, int col);
char CheckWin(char board[ROW][COL], int row, int col);
--------------------------------------------------------------------------
[game.c]
#include "game.h"
void InitBoard(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){...}
int IsFull(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++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char CheckWin(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) == 1)
{
return 'Q';
}
return C;
}
--------------------------------------------------------------------------
[test.c]
#include "game.h"
void menu(){...}
void game()
{
char board[ROW][COL] = {0};
char ret = CheckWin;
InitBoard(board[ROW][COL],ROW,COL);
DisplayBoard(board[ROW][COL],ROW,COL);
while(1)
{
PlayerMove(board[ROW][COL],ROW,COL);
ret = checkwin(char board[ROW][COL], int row, int col);
if(ret != 'C')
{
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);
ret = checkwin(char board[ROW][COL], int row, int col);
if(ret != 'C')
{
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
DisplayBoard(board, ROW, COL);
printf("玩家获胜\n");
}
else if (ret == '#')
{
DisplayBoard(board, ROW, COL);
printf("电脑获胜\n");
}
else if (ret == 'Q')
{
DisplayBoard(board, ROW, COL);
printf("平分秋色\n");
}
}
int main(){...}
[运行结果]
Part 3 细节优化
完整的游戏逻辑实现之后,从运行结果会发现,棋盘一直在重复打印,导致程序运行出来的观感体验并不好。所以我们可以结合system()函数和Sleep()函数,来实现一个较为完善的游戏(代码见附录1下的完整代码),效果如下图
|