三子棋
1. 介绍
今天就带大家用C语言实现一下简洁版本的三子棋,三子棋也称为井字棋。
我们先看看三子棋的预期效果:
这里*代表玩家的棋子,#代表电脑的棋子。
我们通过观察发现,打印出的棋盘是一片空白,表面上看着没有数据,实际上打印的是字符数组,字符中存放的是空格。
注意:当我们想要完成一个比较大的项目时,可采取模块化处理。即:将函数的声明放在头文件中。将函数的定义和实现放在源文件里,不论该函数有多么小。
2. 准备工作
作者在写三子棋时将整个工程分为了三个部分:
- test.c
- game.c
- game.h
- test.c源文件用于测试三子棋代码,程序从test.c文件开始执行。
- game.c文件中存放与游戏实现相关函数的定义和实现
- game.h文件中存放与游戏实现相关的函数的声明、
3. 棋盘相关操作
通过前面我们了解到,所谓的下棋操作本质上是对字符数组中的元素进行操作,通过改变字符数组中的元素的值再将其打印出来,达到下棋效果。但是棋盘是含有横纵坐标的。如果我们定义一维字符数组,则无法方便的表示棋盘中的横纵坐标。综合考虑,二维字符数组比较符合要求。
因为棋盘是三行三列的布局,所以先创建一个二维字符数组:
char board[3][3] = {0};
但是经过思索,这里创建数组时,直接指定数组的行数和列数是不太稳妥的,因为万一我想实现一个6*6或其他参数类型的棋盘,再从这里修改就不太方便。所以,我们可以使用#define定义常量来控制数组的行数和列数,这样我们就可以很方便的修改棋盘的参数了。如下:
#define ROW 3//行
#define COL 3//列
char board[ROW][COL] = {0};//创建一个3*3的数组
通常而言,#define 定义常量这种语句一般放置于头文件中。
棋盘创建好了之后,首先将其每个元素初始化成空格。初始化成功之后,最好将棋盘打印出来,看是否初始化成功。
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
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++)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("%c", board[i][j]);
}
printf("\n");
}
}
但是这种方式的效果不直观,一片空白。我们可以为棋盘设计一个轮廓,使其可视化。先来看看棋盘的规律:
先看每一行的打印,整个棋盘有三行,每一行的打印是一组,整个棋盘按行就被分为了三组,每一组可用绿色和橙色部分表示,只不过最后一组的橙色部分没有打印而已。所以通过分析可以通过数组的行数控制分组数。
先对绿色部分进行分析:绿色部分先打印三个空格,再打印一个竖线,继续打印三个空格,再打印一个竖线,继续打印三个空格,最后一个空格不打印。
橙色部分:先打印三个减号,在打印一个竖线,继续打印三个减号,再打印一个竖线,继续打印三个减号,最后一个竖线不打印。
注意:每一组打印完之后要记得换行。
代码实现:
//打印棋盘 版本二
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)//控制行数
{
printf(" %c | %c | %c ", board[i][0], board[i][1], board[i][2]);
printf("\n");
if (i < row - 1)
{
printf("---|---|---");
printf("\n");
}
}
}
但是这样就算成功了吗?
我们将ROW和COL改成5试试呢:
我们发现,棋盘虽然变成5行,但是列数并没有发生改变。
同理,在之前的基础上,我们也可以对列数进行分析:
整个棋盘按每一行分,也能将每一行的打印分为三部分,每部分由绿色和橙色组成。因为这个棋盘有三列,所以每一行的打印可以分为三组,假设是五列,就可以分为五组,那么每一行的打印就可通过列数来控制了。
通过观察可以发现:每一行要打印的绿色部分(三个空格和三个减号)的次数就是该棋盘的列数,每一行要打印的橙色部分(竖线部分)的次数就是该棋盘的列数-1次。所以我们可以将代码再改进:
//打印棋盘 版本三
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)//控制行数
{
//每一行的打印 %c | %c | %c
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)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
4.下棋流程
棋盘打印好了就可以开始下棋了。
【玩家下棋】
首先让玩家下棋,玩家输入落子的坐标,再判断该坐标是否符合要求,符合要求就落子,若不符合要求就重新输入坐标。
注意:在玩家下棋时,由于不确定玩家是不是程序员,所以就不知道横纵坐标实际上是从0开始的。我们就设计成横纵坐标从1开始,在判断合法性时,稍作处理就好。在本游戏中x,y分别代表行号和列号,并不是严格的数学上的横纵坐标,例如3,6代表的是3行6列。
//玩家下棋
void Player(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("请玩家输入你的落子坐标>:(使用空格隔开)\n");
while (1)
{
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");
continue;
}
}
}
【电脑下棋】
电脑下棋同理,让电脑生成相应数组下标范围内的随机数,再判断该下标的元素是否为空格(即未被占用),为空格则在该坐标落子,若不为空格,重新生成随机数,直到生成的随机数满足要求为止。
void Computer(char board[ROW][COL], int row, int col)//电脑下棋
{
int x = 0;
int y = 0;
while (1)
{
x = rand() % 3;
y = rand() % 3;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
5. 判断胜负
在每一次下完棋之后,系统应该判断是否分出了胜负,所以在每次的下棋函数之后再写一个判断胜负的函数。
【判断胜负】
整个三子棋的游戏结局包含4中情况,分别是;
- 电脑获胜(返回#)
- 玩家获证(返回*)
- 平局(返回’Q’)
- 游戏继续(返回’C’)
【玩家/电脑获胜】
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];
}
列:
//判断列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
return board[1][i];
}
2.再判断是否有斜着连着三个字的情况。(当然也可以先判断斜着再判断行和列)
//判断斜着
if (board[0][0] == board[1][1] && board[1][1] == board[2][2]&&board[1][1]!=' ')
return board[1][1];
if (board[1][1] == board[0][2] && board[1][1] == board[2][0]&&board[1][1]!=' ')
return board[1][1];
【判断是否为平局】
若电脑和玩家都没有获胜,此时就要根据棋盘上是否还有空格(即未落子区域),若还有空格,则游戏继续,若没有空格了,则达成平局。
int IsFull(char board[ROW][COL], int row, int col)//判断棋盘是不是已经满了
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')//有空格说明棋盘没满 返回0
return 0;
}
}
return 1;
}
//判断是否为平局
if (IsFull(board, row, col) == 1)//等于1则说明平局
{
return 'Q';
}
else
return 'C';
6.test.c
再来看test.c函数的设计
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void game()
{
char board[ROW][COL] = {0};//创建一个3*3的数组
InitBoard(board, ROW, COL);//初始化棋盘初始化成空格
DisplayBoard(board, ROW, COL);//打印棋盘
char ret = 0;
while (1)
{
Player(board, ROW, COL);
ret = IsWin(board, ROW, COL);//判断是否分出了胜负
if (ret != 'C')
break;
DisplayBoard(board, ROW, COL);//打印棋盘
printf("\n");
Computer(board, ROW, COL);//电脑下棋
DisplayBoard(board, ROW, COL);//打印棋盘
ret = IsWin(board, ROW, COL);//判断是否分出了胜负
if (ret != 'C')
break;
}
if (ret == '*')
{
printf("玩家胜利!\n");
}
else if (ret == '#')
{
printf("电脑胜利!\n");
}
else
{
printf("平局!\n");
}
}
void menu()
{
printf("*******************\n");
printf("******1. Play******\n");
printf("******0. Exit******\n");
printf("*******************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//调用随机数种子
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;
}
当代码来到22行时,有两种情况,一种是某一方获胜,一种是达成平局,所以我们只需对ret的值进行判断就可以分辨出游戏最终结果。
7. game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
int IsFull(char board[ROW][COL], int row, int col)//判断棋盘是不是已经满了
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')//有空格说明棋盘没满 返回0
return 0;
}
}
return 1;
}
void InitBoard(char board[ROW][COL], int row, int col)//初始化棋盘
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
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++)
// {
// int j = 0;
// for (j = 0; j < col; j++)
// {
// printf("%c", board[i][j]);
// }
// printf("\n");
// }
//}
//打印棋盘 版本二 让棋盘可视化
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
// int i = 0;
// for (i = 0; i < row; i++)//控制行数
// {
// printf(" %c | %c | %c ", board[i][0], board[i][1], board[i][2]);
// printf("\n");
// if (i < row - 1)
// {
// printf("---|---|---");
// printf("\n");
// }
// }
//}
//打印棋盘 版本三 让棋盘可视化,实现通用版
void DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)//控制行数
{
//每一行的打印 %c | %c | %c
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)
{
int j = 0;
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
}
void Player(char board[ROW][COL], int row, int col)//玩家下棋
{
int x = 0;
int y = 0;
printf("请玩家输入你的落子坐标>:(使用空格隔开)\n");
while (1)
{
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");
continue;
}
}
}
void Computer(char board[ROW][COL], int row, int col)//电脑下棋
{
int x = 0;
int y = 0;
while (1)
{
x = rand() % 3;
y = rand() % 3;
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];
}
//判断列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
return board[1][i];
}
//判断斜着
if (board[0][0] == board[1][1] && board[1][1] == board[2][2]&&board[1][1]!=' ')
return board[1][1];
if (board[1][1] == board[0][2] && board[1][1] == board[2][0]&&board[1][1]!=' ')
return board[1][1];
//判断是否为平局
if (IsFull(board, row, col) == 1)//等于1则说明平局
{
return 'Q';
}
else
return 'C';
}
8. game.h
#pragma once
#define ROW 3//行
#define COL 3//列
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void InitBoard(char board[ROW][COL],int row,int col);//初始化棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);//打印棋盘
void Player(char board[ROW][COL], int row, int col);//玩家下棋
void Computer(char board[ROW][COL], int row, int col);//电脑下棋
//玩家赢了返回 '*'
//电脑赢了返回 '#'
//平局返回 ‘Q'
//继续游戏返回 'C'
char IsWin(char board[ROW][COL], int row, int col);//判断是否分出了胜负
9. 完结
本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!