井字棋,英文名叫Tic-Tac-Toe,是一种在3*3格子上进行的连珠游戏,和五子棋类似,由于棋盘一般不画边框,格线排成井字故得名。游戏需要的工具仅为纸和笔,然后由分别代表O和X的两个游戏者轮流在格子里留下标记(一般来说先手者为X),任意三个标记形成一条直线,则为获胜。本文我们使用C语言来实现这个小游戏。
当玩家选择开始游戏之后,我们要实现一个棋盘,对于3* 3的棋盘我们可以用一个3* 3的数组来实现,同时我们要将棋盘初始化为接近真实棋盘的模样。如下所示
当棋盘初始化之后,我们就可以正式开始游戏,玩家根据棋盘当前的状态,可以输入他想要落子位置的坐标,如果这个坐标没有被占用,那么我们就在这个位置打印’X’代表此处已经被玩家落子。当玩家落子之后,轮到电脑落子,我们让电脑随机产生一个符合棋盘大小的坐标,并且如果坐标没有被占用,就在这个坐标打印一个’O’代表此处已被电脑落子。
输赢的判断有两种方式:
第一种,玩家或者电脑每走完一步,我们就判断一次是否产生赢家
第二种,当玩家或者电脑走了三步时,我们才开始判断是否产生赢家
首先我们创建一个头文件,用来声明我们所使用到的函数以及定义棋盘的大小。由于我们要让电脑随机产生一个坐标,因此我们需要用到srand,rand以及time函数,同时在游戏过程中还需要用到输入输出函数,因此我们在头文件中,将我们需要用到的标准库全都包含在内,这样在.c文件就只需要包含这一个头文件即可
//game.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
#define ROW 3 //棋盘行数
#define COL 3 //棋盘列数
void InitBoard(char arr[ROW][COL], int row, int col); //初始化棋盘
void PrintBoard(char arr[ROW][COL], int row, int col); //打印棋盘
void PlayerMove(char arr[ROW][COL], int row, int col); //玩家下棋
void CopMove(char arr[ROW][COL], int row, int col); //电脑下棋
char IsWin(char arr[ROW][COL], int row, int col); //判断结果
在main函数中,我们打印出游戏菜单,并让玩家进行选择,当玩家选择了开始游戏时,我们调用game函数。由于后面我们需要电脑产生随机的坐标,因此我们在main函数中使用srand初始化种子。对于srand和rand函数的使用方法在我的另一篇博客中进行了详细的介绍,有兴趣的读者可以作为参考。rand和srand函数用法
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//打印菜单
void menu()
{
printf("**********************************************\n");
printf("********请选择-> 1:开始游戏 0:结束游戏\n");
printf("**********************************************\n");
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏结束\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
当玩家选择了开始游戏之后,我们调用game函数进行游戏, 首先我们初始化棋盘,并将棋盘打印出来,之后让玩家先走,玩家或者电脑每走完一步都打印一次棋盘并判断是否产生赢家。
当游戏结束时有三种可能:
1、玩家胜利
2、电脑胜利
3、平局
因此我们用Winer函数来判断胜者是谁。X代表玩家赢,O代表电脑赢。
//判断胜者是谁
void Winer(char ret)
{
if (ret == 'X')
{
printf("恭喜你赢了\n");
}
else if (ret == 'O')
{
printf("很遗憾,你输了\n");
}
else
{
printf("平局\n");
}
}
// 游戏开始
void game()
{
char arr[ROW][COL] = {
0 };
InitBoard(arr, ROW, COL); //初始化棋盘
PrintBoard(arr, ROW, COL); //打印棋盘
while (1)
{
char ret = 0;
PlayerMove(arr, ROW, COL); //玩家下棋
PrintBoard(arr, ROW, COL); //打印棋盘
ret = IsWin(arr, ROW, COL); //判断输赢
if (ret != 'C') //当ret!='C'时,游戏结束(C代表continue)
{
Winer(ret); //判断胜者是谁
break;
}
CopMove(arr, ROW, COL); //电脑下棋
PrintBoard(arr, ROW, COL);
ret = IsWin(arr, ROW, COL);
if (ret != 'C')
{
Winer(ret);
break;
}
}
}
在玩家落子前,我们先将棋盘初始化空
//初始化棋盘
void InitBoard(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
for (j = 0; j < col; j++)
arr[i][j] = ' ';
}
由于我们所希望的棋盘是这样的:
为了实现类似的棋盘,我们需要使用|以及— 来作为分隔符,并且注意到,我们所希望的棋盘最后一行和最后一列都没有分隔符,因此我们在代码中要进行一个if语句的判断
//打印棋盘
void PrintBoard(char arr[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 ", arr[i][j]);
if (j < col - 1)
printf("|"); //不是最后一列,打印 | 作为分隔符
}
printf("\n");
if (i < row - 1)
for (j = 0; j < col; j++) //不是最后一行,打印--- --- --- 作为分隔符
printf("--- ");
printf("\n");
}
}
我们令棋盘上的坐标从1开始,因此对应到数组的下标就要减1,并且需要判断玩家当前输入的坐标是否已经被占用,如果没被占用则赋值为X。
//玩家下棋
void PlayerMove(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
printf("玩家下棋,请输入落子的坐标:\n");
while (1)
{
scanf("%d%d", &i, &j);
if (i <= 0 || j <= 0 || i > ROW || j > COL) //超出棋盘范围
printf("坐标输入错误,请重新输入\n");
else
{
if (arr[i - 1][j - 1] == ' ') //坐标未被占用
{
arr[i - 1][j - 1] = 'X'; //当前坐标赋值为X
break;
}
else printf("此处已经被占用,请重新输入\n");
}
}
}
电脑下棋时,我们需要电脑随机生成一个[0,row]的行坐标和一个[0,col]的列坐标,用srand来实现随机的坐标。只要产生的坐标没被占用,就将其赋值为O,不需要打印提示信息。
//电脑下棋
void CopMove(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
printf("电脑下棋:\n");
while (1)
{
i = rand() % row;
j = rand() % col;
if (arr[i][j] == ' ')
{
arr[i][j] = 'O';
break;
}
}
}
想要判断输赢,我们必须要检查每一行,每一列以及两个对角线是否有连续三个相同的符号,并且这个符号不为空(因为初始化的时候我们将其置为了空)。当三个位置符号相同时,我们只需要将这个符号返回,然后在Winer函数中判断胜者到底是谁。当没有胜者产生时,我们还要判断棋盘是否落满,如果落满则表明和棋。当没有胜者并且棋盘也没有落满时,游戏继续。
//判断棋盘是否满
int IsFull(char arr[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 (arr[i][j] == ' ') return 0; //如果有一个位置为空,表明棋盘还没落满,返回0
return 1; //棋盘全部落满,返回1
}
//3*3 输赢判断
char IsWin(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++) //判断行是否相等
{
if (arr[i][0] == arr[i][1] && arr[i][0] == arr[i][2] && arr[i][0] != ' ')
return arr[i][0]; //玩家胜利则arr[i][0]是X,电脑胜利则是O
}
for (i = 0; i < col; i++) //判断列是否相等
{
if (arr[0][i] == arr[1][i] && arr[0][i] == arr[2][i] && arr[0][i] != ' ')
return arr[0][i];
}
//判断主对角线是否相等
if (arr[0][0] == arr[1][1] && arr[0][0] == arr[2][2] && arr[0][0] != ' ')
return arr[0][0];
//判断副对角线是否相等
if (arr[0][2] == arr[1][1] && arr[1][1] == arr[2][0] && arr[1][1] != ' ')
return arr[1][1];
//判断棋盘是否落满
int ret = IsFull(arr, row, col);
if (ret) return 'F'; //如果ret为1,则棋盘落满,返回'F'
else return 'C'; //程序运行到这,表明没有胜者并且棋盘不为空
//(如果不满足这几个条件,在前面函数就已经返回,不会运行到这一步)
//返回C则继续游戏
}
上面的代码只能在3*3的棋盘上判断输赢,无法对更大的棋盘进行判断,对此作出如下改进:
行判断:第 i 行连续三个位置是否相同,同时保证不超过棋盘的范围
列判断:第 j 列连续三个位置是否相同,同时保证不超过棋盘的范围
主对角线判断:连续三个主对角线位置是否相同,即arr[i][j],arr[i+1][j+1],arr[i+2][j+2]是否相同,同时保证不超过棋盘范围
副对角线判断:连续三个副对角线位置是否相同,即arr[i][j],arr[i+1][j-1],arr[i+2][j-2]是否相同,同时保证不超过棋盘范围
因为主对角线判断时,行号和列号不断增大,因此i和j从0开始,而副对角线判断时,行号不断增大,列号不断减小,因此i从0开始,j从col-1开始。
//判断任意大小棋盘输赢
char IsWin(char arr[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判断行
for (i = 0; i < row; i++) //判断第i行连续3个位置是否相等
{
for (j = 0; j < col - 2; j++) //不能超出棋盘范围,因此j < col - 2
{
if (arr[i][j] == arr[i][j + 1] && arr[i][j] == arr[i][j + 2] && arr[i][j] != ' ')
return arr[i][j];
}
}
//判断列
for (j = 0; j < col; j++) //判断第j列连续3个位置是否相等
{
for (i = 0; i < row - 2; i++) //不能超出棋盘范围,因此i < row - 2
{
if (arr[i][j] == arr[i + 1][j] && arr[i][j] == arr[i + 2][j] && arr[i][j] != ' ')
return arr[i][j];
}
}
//判断对角线
for (i = 0; i < row - 2; i++) //主对角线判断
{
for (j = 0; j < col - 2; j++)
{
if (arr[i][j] == arr[i + 1][j + 1] && arr[i][j] == arr[i + 2][j + 2] && arr[i][j] != ' ')
return arr[i][j];
}
}
for (i = 0; i < row - 2; i++) //副对角线判断
{
for (j = col - 1; j >= 2; j--)
{
if (arr[i][j] == arr[i + 1][j - 1] && arr[i][j] == arr[i + 2][j - 2] && arr[i][j] != ' ')
return arr[i][j];
}
}
int ret = IsFull(arr, row, col);
if (ret) return 'F';
return 'C';
}