一、前言
今天我来演示下如何用C语言实现三子棋。
先认识下三子棋:双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利。
游戏规则很简单,在这篇文章中我会先讲述游戏代码实现的流程,然后给出代码并在注释中对其讲解。
二、流程
1.构建代码的大致框架
我们的代码分为两个框架:测试模块和游戏模块,因此可以创建test.c来测试游戏逻辑,创建game.c和game.h,分别来实现实现函数和进行函数的声明以及头文件的包含。
2.菜单
首先,做一个菜单,这个菜单可以指导玩家如何进入和退出游戏,因为我们是在c语言上操作,玩家通过输入字符来进行操作的,他们可能输入菜单上以外的字符,所以我们也要考虑的这种情况。
3.游戏的实现
在现实生活中我们玩三子棋,感觉这游戏再简单不过,可是要到代码上实现它,难免摸不着头脑:格子怎么用代码表示?落子怎么用代码表示?又要用怎么判断胜负?我为游戏的实现分装了五个功能,初始化棋盘,显示棋盘,玩家下棋,电脑下棋,判断输赢。
(1)初始化棋盘
构建个二维数组,将其元素全部初始化为空格,来表示最开始一个子都没下的棋盘。
(2)显示棋盘
三子棋的格子如同井字,想要用代码表示这个井,我们可以将井字拆解成三个循环,
| | 和---|---|---,在最后一次循环中控制条件省略掉---|---|---,就可以构建出我们的格子了,注意看会发现---|---|---也是这个道理,---|循环,在最后一次循环中省略|,等会代码演示更清晰明白。
(3)下棋
先说玩家下棋,下棋有三种可能,一、该位置空着,二、该位置已被下过,三、玩家输入了非法坐标,我们要考虑的这三种情况。然后电脑下棋,因为我们用c语言操作,电脑没办法那么智能,只能用随机的方式让它下棋了,我们考虑用rand和srand函数,来替电脑随机决定坐标。
(4)判断输赢
在每次玩家下棋和电脑下棋后我们都要判断下当前的局势。局势有四种可能,电脑赢,玩家赢,平局,还未结束。
三.代码
1.game.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define COL 3
#define ROW 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);
//因为在其他文件中都会包含game.h所以我们在game.h包含我们所需要的所有头文件,声明,和宏的定义
2.game.c
#define _CRT_SECURE_NO_WARNINGS
#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] = ' ';
}
}
}//利用两个变量和两个循环将棋盘中的元素全部初始化为空格,也可以用memset函数实现,如下memset(&board[0][0],' ',col*row*sizeof(board[0][0]);
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");
}
}
}//这里的显示棋盘的关键就是如何将格子打印出来,思路上面已经说过了,大家可以好好品品代码。
void PlayerMove(char board[ROW][COL], int row, int col)//想要玩家下棋需要二维数组的地址,和行列大小
{
{
int x = 0;
int y = 0;
printf("玩家下棋\n");
while (1)//一直循环直到玩家成功落子
{
printf("请输入下棋坐标\n");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= 3 && y >= 1 && y <= 3)//只有输入正确坐标才能下棋,因为玩家一般认为行列从1开始,所以这里范围是1到3
{
if (board[x - 1][y - 1] == ' ')//正确坐标上不能被占用,并且玩家输入的坐标对应到我们代码上x和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;
y = rand() % col;//%3是为了使生成的随机数的范围再0到2之间
if (board[x][y] == ' ')
{
board[x][y] = '#';//用#作为电脑的棋子
break;
}
}
}
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;//遍历整个数组如果有空格说明没满返回0,反之满了,返回1
}
}
return 1;
}
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][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))//判断是否平局,若平局则返回Q
return 'Q';
return 'C';//若前面的if都不成立,说明游戏未结束,则返回C
}
3.test.c
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
printf("******************************\n");
printf("******************************\n");
printf("************1.play************\n");
printf("************0.exit************\n");
printf("******************************\n");
printf("******************************\n");
}//简单的不能再简单的菜单
void game()
{
char board[ROW][COL] = { 0 };//创建二维数组作为棋盘
char ret = 0;
InitBoard(board, ROW, COL);//初始化棋盘
DisplayBoard(board, ROW, COL);//显示棋盘
while (1)//一直循环,直到一方胜利或者平局再跳出循环
{
PlayerMove(board, ROW, COL);//玩家移动
DisplayBoard(board, ROW,COL);//显示棋盘
ret=IsWin(board,ROW,COL);//判断输赢
if (ret != 'C')//如果返回不是继续的标志,说明游戏结束,跳出循环
break;
ComputerMove(board, ROW, COL);//电脑移动
DisplayBoard(board, ROW, COL);//显示棋盘
ret=IsWin(board,ROW,COL);//如果不是继续的标志跳出循环
if (ret != 'C')
break;
}
if (ret == '*')//如果返回的是玩家的棋子,则玩家胜利
printf("游戏结束,玩家胜利\n");
if (ret == '#')//如果返回的是电脑的棋子,则电脑胜利
printf("游戏结束,电脑胜利\n");
if (ret == 'Q')//如果返回的是平局的标志,则平局
printf("游戏结束,平局\n");
}
int main()
{
srand((unsigned int)time(NULL));//srand设置产生伪随机数发生器的起始点,而time是时间戳可以让起始点不断变化,产生类似随机的效果
int input = 0;//玩家的输入
do
{
menu();
printf("请选择:->");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);//巧妙地将do while和switch结构结合起来,在输入0的时候因为0为假便退出了循环,而输入1时候的时候先进行游戏,然后1为真再进入循环,再次打印菜单询问玩家是否要进行游戏,这样可以让玩家想玩多就玩多久,而不是玩一次就退出游戏,而输入1和0以外的数字,就会提醒玩家错误,并且再次打印菜单让玩家输入,直到正确。菜单虽小,应有尽有。
return 0;
}
四、结束语
想要讲好一个代码只用文字比较难,因为代码的构建是动态的,是一步一步来的,所以代码只看不写是学不好的,若我的文章对你有所启发的,快快打起代码吧,这样才能真正的学到知识,谢谢观看!