前言
三子棋设计是对C语言循环、判断语句、数组以及函数等知识点的综合应用,通过独立完成三子棋项目的编写,相信你对C语言的基础知识掌握会有一个大的提升。
一、游戏规则
三子棋游戏的玩法类似于我们小时候玩的井字棋游戏,棋盘大小为3×3,对战双方轮流下棋,当一方3枚棋子落于同行、同列或两个对角线上时,该方取胜;当棋盘下满仍没分出胜负时,判为平局;系统默认为玩家先下。
二、设计三子棋
1.创建文件
创建一个头文件,命名为game.h
创建两个源文件,分别命名为game.c和test.c
(1)game.h中存放所有头文件、define定义的宏常量以及声明所有用到的函数
(2)game.c中集中存放函数
(3)test.c源文件则存放程序的主体代码
2.思路与实现
(1)游戏菜单
要创建一个游戏菜单界面,至少有两个选项——开始游戏和退出游戏
1.使用menu函数封装菜单
封装内容即为游戏菜单的界面,界面较为简单,可直接用printf语句打印
void menu() {
printf("**********************\n");
printf("**** 1.play 2.exit****\n");
printf("**********************\n");
}
2.使用do-while循环和switch语句选择
在main主函数内使用do-while循环和switch语句完成对游戏界面选项的选择:即在请选择:> 后面输入 1 即跳转至进行游戏环节,输入 2 即退出游戏,其它数字会显示输入错误,并重新打印出游戏界面,重新输入数字
int main() {
int input = 0;
do
{
menu();//打印菜单
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏\n");
system("pause");
return 0;
default:
printf("选择错误\n");
break;
}
} while(input);
return 0;
}
注:在case 2 : 退出游戏阶段,我使用了System(“pause”) 和 return 0 语句来实现真正的退出游戏即退出程序,建议大家设计时,也使用这两行代码
到这里,第一步游戏菜单的代码就书写完毕,接下来是进入游戏环节
(2)棋盘的设计
三子棋进行游戏必须建立在棋盘上进行,接下来我们就开始设计棋盘
1.封装game函数
首先我们要先封装一个game函数,该函数中存放进行游戏的一系列操作,在用户在菜单界面输入1后,即进入game函数内执行操作
2.实现棋盘
根据棋盘模样,可将棋盘当做一个3×3的二维数组,行列均为3,所以我们可以在game函数内初始化一个char类型二维数组(因为棋子的模型为*和#,为char类型),以及存放下过的棋
char board[ROW][COL] = { 0 };//ROW COL在game.h中已经宏定义
为后期书写方便,我们可以把该二维数组的行与列在game.h头文件中宏定义一下
#define ROW 3
#define COL 3
接下来要对此二维数组初始化,保证该数组模样为"九宫格"
//棋盘初始化代码
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] = ' ';
}
}
}
再对九宫格内部进行详细设计,第一种方法是按图索骥,但不推荐这种方法,因为不适用四子棋及其以上的棋盘游戏
//第一个版本
void displayboard(char board[row][col], int row, int col)
{
int i = 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 和 col 均为5,则数组就会变为5×5的格式,从而避免大规模改动代码
//版本二 打印棋盘的样子
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//优化做法
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)
{
//printf("---|---|---\n");优化做法
int j = 0;
for (j = 0; j < COL; j++)
{
printf("---");
if(j<COL-1)
printf("|");//保证行的最后一格最后没有|
}
printf("\n");
}
}
}
(3)玩家下棋
实现棋盘的设计后,接下来就是进行下棋环节
1.将玩家下棋操作步骤封装进PlayerMove函数中
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
while(1)//while循环确保玩家下了棋
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//坐标范围合法进入判断
{
if (board[x - 1][y - 1] == ' ')//x-1,y-1后使其都在数组的下标范围内
{
board[x - 1][y - 1] = '*';//若此处数组存发的为空格,即将*放入其中,代表玩家下的棋
break;//下完棋之后,交给对手下棋
}
else
{
printf("坐标被占用,重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
2.再次调用DisplayBoard函数,将玩家下好的棋盘展示出来
PlayerMove(board, ROW, COL);//玩家下棋
DisplayBoard(board, ROW, COL);//展示玩家下好后的棋盘
(4)电脑下棋
玩家下好棋后,轮到电脑下棋,注意:电脑下棋是随机性的,所以首先要引入随机函数。
1.需要在game.h头文件中添加如下三条预处理指令:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
其中#include <stdio.h>在头文件提前添加好,在源文件中直接引用就可以省去#include <stdio.h>在源文件中的再次书写
#include "game.h"
见下图,直接引用即可
2.在main主函数添加随机数种子
三条预处理指令添加完后,需要在main主函数添加随机数种子
//随机数函数引入
srand((unsigned int)time(NULL));
3.用ComputerBoard函数封装电脑下棋操作
注意:调用rand函数来实现电脑随机下棋,rand() % a 代表电脑随机生成一个0~a-1的整数数值
//实现电脑下棋
//找没有下棋的位置随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
printf("电脑下棋:>\n");
int x = 0;
int y = 0;
while(1)//确保电脑下棋到正确位置上
{
x = rand() % ROW;//0~2的随机值
y = rand() % COL;//0~2的随机值
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
4.再次调用DisPlayerBoard函数,展示电脑下完棋后的棋盘
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//优化做法
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)
{
//printf("---|---|---\n");优化做法
int j = 0;
for (j = 0; j < COL; j++)
{
printf("---");
if(j<COL-1)
printf("|");
}
printf("\n");
}
}
}
(5)判定游戏结束条件
用Iswin函数封装游戏结束条件,结束条件分为以下几种:
1.一方胜利
胜利条件又分为某一行相等、某一列相等、两个对角线相等三种情况;返回*为玩家赢 返回#为电脑赢
某一行相等:
其中 board[i][1] != ’ ’ 的意义在于排除均为空格的情况
int i = 0;
for (i = 0; i < ROW; i++)
{
//行相等
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ')//排除为空格的情况
{
return board[i][0];//返回符号即可 返回*为玩家赢 返回#为电脑赢
}
}
某一列相等:
其中 board[i][1] != ’ ’ 的意义在于排除均为空格的情况
int j = 0;
for (j = 0; j < COL; j++)
{
//列相等
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')//列相等
{
return board[0][j];//返回符号即可 返回*为玩家赢 返回#为电脑赢
}
}
两个对角线相等:
其中 board[1][1] != ’ ’ 的意义在于排除均为空格的情况
//从左上到右下对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
//另一条对角线
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
{
return board[1][1];
}
2.平局
如果棋盘下满未分出胜负,则出现平局情况,返回Q代表游戏平局
//没有人赢,判断平局
if (IsFull(board,ROW,COL))//判断棋盘是否满的函数IsFull()
{
return 'Q';//因为该函数返回值类型为char,所以不能写打印值
}
封装一个IsFull函数判断九宫格是否下满棋,如果未下满则返回0,如果棋盘满则返回1,打印Q代表游戏平局
//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col)//相当于对二维数组的一个遍历
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
return 0;
break;
}
}
}
return 1;
}
3.继续游戏
上述情况都遍历完毕后,返回C代表游戏继续
//游戏继续
return 'C';
(6)while循环实现轮流下棋,并判断游戏是否结束
每次当玩家或者电脑下完棋之后,就判断是否满足结束条件,再打印此时的棋盘情况(ps:这里打印棋盘情况也可以放在判断条件之前,只不过差别在于最后会多打印一次棋盘)
注意:进入while循环之前要提前定义好char类型的ret,并将其初始化
char ret = 0;//ret接受结果
ret == C 代表棋未下完、未分出胜负,继续执行while循环
ret != C 代表达到游戏结束条件,但不知道胜负还是平局,需要继续进行if判断
进入if判断:
如果 ret == * 玩家胜利
如果 ret == # 电脑胜利
否则就是平局
while (1) {
PlayerMove(board, ROW, COL);//玩家下棋
//判断输赢
ret = Iswin(board, ROW, COL);
if (ret != 'C')
{
printf("游戏结束\n");
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);//电脑下棋
//判断输赢
ret = Iswin(board, ROW, COL);
if (ret != 'C')
{
printf("游戏结束\n");
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家胜利\n");
printf("棋盘最后的样子为:\n");
}
else if (ret == '#')
{
printf("电脑胜利\n");
printf("棋盘最后的样子为:\n");
}
else
{
printf("平局\n");
printf("棋盘最后的样子为:\n");
}
DisplayBoard(board, ROW, COL);//打印游戏结束时的情况
system("pause");//确保最后游戏结束不会出现菜单
}
(7)结束游戏
最后打印一次结束时的棋盘模样,游戏结束
从这两张图中可看出 有无system(“pause”)的区别
所以我个人建议带上system(“pause”),这样才是真正实现游戏的结束!!
四、代码完整实现
1.头文件game.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#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);
//判断状态
char Iswin(char board[ROW][COL], int row, int col);//四种状态 玩家赢-'*' 电脑赢-'#' 平局-'Q' 继续-'C'
//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col);
2.源文件game.h
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu() {
printf("**********************\n");
printf("**** 1.play 2.exit****\n");
printf("**********************\n");
}
void game() {
char ret = 0;//ret接受结果
char board[ROW][COL] = { 0 };//ROW COL在game.h中已经宏定义
//游戏之前要初始化棋盘,要写一个棋盘的函数
InitBoard(board, ROW, COL);//棋盘初始化
DisplayBoard(board, ROW, COL);//构建完整九宫格棋盘
//进行下棋步骤
while (1) {
PlayerMove(board, ROW, COL);//玩家下棋
//判断输赢
ret = Iswin(board, ROW, COL);
if (ret != 'C')
{
printf("游戏结束\n");
break;
}
DisplayBoard(board, ROW, COL);
ComputerMove(board, ROW, COL);//电脑下棋
//判断输赢
ret = Iswin(board, ROW, COL);
if (ret != 'C')
{
printf("游戏结束\n");
break;
}
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家胜利\n");
printf("棋盘最后的样子为:\n");
}
else if (ret == '#')
{
printf("电脑胜利\n");
printf("棋盘最后的样子为:\n");
}
else
{
printf("平局\n");
printf("棋盘最后的样子为:\n");
}
DisplayBoard(board, ROW, COL);//打印游戏结束时的情况
system("pause");//确保最后游戏结束不会出现菜单
}
//棋盘初始化代码
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] = ' ';
}
}
}
//打印棋盘的样子
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
//printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//优化做法
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)
{
//printf("---|---|---\n");优化做法
int j = 0;
for (j = 0; j < COL; j++)
{
printf("---");
if(j<COL-1)
printf("|");
}
printf("\n");
}
}
}
//实现玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("玩家下棋:>\n");
while(1)
{
printf("请输入坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= ROW && y >= 1 && y <= COL)//坐标范围合法进入判断
{
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;
int y = 0;
while(1)//确保电脑下棋到正确位置上
{
x = rand() % ROW;//0~2的随机值
y = rand() % COL;//0~2的随机值
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
//判断谁胜利
char Iswin(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][1] != ' ')//排除为空格的情况
{
return board[i][0];//返回符号即可 返回*为玩家赢 返回#为电脑赢
}
}
int j = 0;
for (j = 0; j < COL; j++)
{
//列相等
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')//列相等
{
return board[0][j];//返回符号即可 返回*为玩家赢 返回#为电脑赢
}
}
//从左上到右下对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
{
return board[1][1];
}
//另一条对角线
if (board[2][0] == board[1][1] && board[1][1] == board[0][2] && board[1][1] != ' ')
{
return board[1][1];
}
//没有人赢,判断平局
if (IsFull(board,ROW,COL))//判断棋盘是否满的函数IsFull()
{
return 'Q';//因为该函数返回值类型为char,所以不能写打印值
}
//游戏继续
return 'C';
}
//判断棋盘是否满的函数,满了返回1,不满返回0
int IsFull(char board[ROW][COL], int row, int col)//相当于对二维数组的一个遍历
{
for (int i = 0; i < ROW; i++)
{
for (int j = 0; j < COL; j++)
{
if (board[i][j] == ' ')
{
return 0;
break;
}
}
}
return 1;
}
3.源文件test.h
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int main() {
//随机数函数引入
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();//打印菜单
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏\n");
system("pause");
return 0;
default:
printf("选择错误,请重新输入\n");
break;
}
} while(input);
return 0;
}
五、总结
三子棋游戏的设计是比较考验大家对数组、循环语句、函数以及判断语句等知识点的综合应用,我在刚开始的学习过程中也遇到很多想不到的地方,通过课下反复的思考、反复的敲代码,现在对三子棋程序已具有了一个较为完整的思路框架,我认为其中的棋盘打印的优化、胜负条件的判断、如何判断棋盘已满这几个方面是思考的难点,希望我的文章对大家的学习有所帮助哦。。。
本篇文章,博主大概花了三个小时为大家精心整理,同时也是博主的第一篇博客哦,希望喜欢的uu们给博主点点关注,一键三连一下,未来我会为大家带来更多精彩的博客哦,同时也希望大家在学习编程的道路上都能够学有所成,早日实现自己进大厂的梦想!!!