三子棋简介:
三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏分为双方对战,双方依次在9宫格棋盘上摆放棋子,率先将自己的三个棋子走成一条线就视为胜利,而对方就算输了。
编写三子棋,可以让我们学习如何组织一个较大的代码。
我们将代码分为三个模块:
test.c---用来测试游戏逻辑
game.c---函数的实现
game.h---函数的声明
其中game.c和game.h为游戏的模块
代码实现思路:
首先,我们要在屏幕上打印一个菜单,让玩家选择1进入游戏,选择0退出游戏,选择不是1不是0就告诉玩家选择错误,请重新选择。于是在测试模块test.c中:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"//包含自己的头文件要用""
void menu()
{
printf("***************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
}
int main()
{
int input = 0;
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:
game();//game函数即实现三子棋
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
}while (input);//设置选择1进入游戏,0退出游戏的便捷之处在于:若选择1(或不是0的整数),
//input为真,继续进入循环,若选择0,为假则打印"退出游戏"后退出循环。
return 0;
}
printf,scanf函数需要引头文件,所以我们在game.h中:
到这里我们可以运行一下代码,检查一下是否出现问题,在编写较大的代码时,要养成这个好习惯,不然到最后代码出了问题就不好发现是哪个地方错误了。
好的,接下来我们要做的就是对game()函数的实现了。
想要下棋,首先我们要有一个棋盘,并且还要记录下来玩家和电脑下棋的位置,我们可以在电脑上输出这样一个简易的棋盘:
| |
---|---|---
| |
---|---|---
| |
这样一个三行三列的棋盘和二维数组非常相似,所以我们可以创建一个3*3的二维数组来记录棋子,考虑到以后可能我们要玩更大棋盘的游戏,这样的话所有涉及到二维数组大小的值都要进行更改,为了方便我们在game.h中定义一个行(row)和列(column)
注意!#define 定义的符号都是大写的。未来我们只需要在头文件中更改行列就可以了,接下来,我们要对棋盘进行初始化和打印,我们先在测试逻辑模块test.c中写出我们需要的功能:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("***************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
}
void game()
{
//存放数据需要一个3*3的二维数组
char board[ROW][COL] = { 0 };
//初始化(initialization)棋盘
Initboard(board, ROW, COL);
//显示棋盘
DisplayBoard(board, ROW, COL);
}
int main()
{
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.h中进行声明:
#pragma once
//头文件的包含
#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中进行编写实现:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
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, j = 0;
for (i = 0; i < row; i++)//共打印row行
{
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");
}
}
}
到这里我们又可以运行一下,检查自己写的棋盘是否正确打印出来。如果一切顺利那接下来我们就要考虑开始下棋了。首先玩家下棋,玩家输入要落子的坐标,这里我们要注意,二维数组第一个元素的坐标应该是[0][0],但玩家可能不是程序员,在玩家的眼里第一个元素的坐标就是[1][1],所以x的范围应该是1~ROW,y的范围应该是1~COL。玩家落子后,要打印一下棋盘,让玩家看到自己下棋的位置,然后轮到电脑落子,电脑落子是随机的,但不能在已经落过子的地方下棋,玩家也是一样,当电脑落子后应打印一下棋盘,让玩家看到当前棋盘落子情况。如此循环,直到有一方胜利或者出现平局,所以在每次落子后,我们都要判断一下是否出现胜负或平局。按照这个思路,我们在逻辑测试代码模块test.c中:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("***************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
}
void game()
{
//存放数据需要一个3*3的二维数组
char board[ROW][COL] = { 0 };
//初始化棋盘
Initboard(board, ROW, COL);
//显示棋盘
DisplayBoard(board, ROW, COL);
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
//电脑下棋
ComputerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
}
}
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;
}
在游戏实现模块game.c中:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
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, 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, 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][y]
{
board[x - 1][y - 1] = '*';//假设玩家的棋子是‘*’
break;
}
else//不能在已落子的地方下棋
printf("坐标被占用,请重新输入\n");
}
else//坐标非法,玩家输入坐标超出范围
printf("坐标非法,请重新输入\n");
}
}
//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
printf("电脑下棋:\n");
while (1)//只有电脑落子成功,方可跳出循环
{
x = rand() % row;
//调用rand()函数生成一个随机数,再模上一个行,
//得到一个随机的行的坐标,比如row=3,rand()%3=0、1、2
y = rand() % col;//同上
//要使用rand()函数生成随机数,需要调用srand()函数来设置随机数的生成器,
//且整个工程中srand()只需要调用一次即可(在test.c中调用)
//这些函数需要的头文件在game.h中包含
if (board[x][y] == ' ')//判断生成的坐标[x][y]是否为空
{
board[x][y] = '#';//不为空,电脑落子
break;//落子成功,跳出循环
}
//若[x][y]不为空,再次进入循环生成随机坐标
}
}
在函数声明模块game.h中:
#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);
写到这里,我们可以运行一下代码,如果没有错误的话,现在已经可以实现玩家和电脑下棋的交互了
接下来我们要做的就是判断每次落子后的输赢情况,我们在代码逻辑测试模块test.c中写下IsWin()函数,用来判断棋盘的三行,三列,对角线是否相同,或者未出现相同而且棋盘已满的情况,为了维护游戏的继续与终止,IsWin()函数要告诉我们四种情况:
1、玩家赢;
2、电脑赢;
3、平局;
4、游戏继续;
//判断输赢
//玩家赢--返回'*'
//电脑赢--返回'#'
//平局 --返回'Q'
//继续 --返回'C'
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))//设计一个IsFull函数来判断是否平局
//若平局则返回1,进入if语句,若没有平局返回0
{
return 'Q';
}
//游戏继续
return 'C';//若没有一方赢,也没有平局,则代码走到这里,游戏继续
}
这个是我们写出来的IsWin()函数,要注意的是这个函数的返回值设计,我们在比较完行、列、对角线后若发现有一方胜利,我们无需判断相同的符号是'#'还是'*',而是直接返回相同的那个符号,再在逻辑测试模块中进行分析。
到这里,我们已经基本完成了所有的代码设计,最后只需要在test.c中分析每次落子后的棋盘胜负情况了,于是我们要接受IsWin函数的返回值并进行分析游戏的进行与终止,而电脑玩家轮流落子的过程是一个循环,只有游戏出现结果才会终止,跳出循环,让我们来看一下完整的代码。
这是test.c中的代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("***************************\n");
printf("***** 1.play *****\n");
printf("***** 0.exit *****\n");
printf("***************************\n");
}
void game()
{
//存放数据需要一个3*3的二维数组
char board[ROW][COL] = { 0 };
//初始化棋盘
Initboard(board, ROW, COL);
//显示棋盘
DisplayBoard(board, ROW, COL);
int ret = 0;
while (1)
{
//玩家下棋
PlayerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if ('C' != ret)
{
break;//若返回值不是'C',则必为平局、玩家赢、电脑赢
//其中一种,而这三种无论哪一种都意味着游戏结束。
//跳出循环,宣布结果
}
//电脑下棋
ComputerMove(board, ROW, COL);
//打印棋盘
DisplayBoard(board, ROW, COL);
//判断输赢
ret = IsWin(board, ROW, COL);
if ('C' != ret)//电脑落完子也要判断输赢
{
break;
}
}
//宣布结果
if ('*' == ret)
{
printf("玩家赢\n");
}
else if ('#' == ret)
{
printf("电脑赢\n");
}
else
{
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;
}
这是game.c中的代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
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, 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, 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][y]
{
board[x - 1][y - 1] = '*';//假设玩家的棋子是‘*’
break;
}
else//不能在已落子的地方下棋
printf("坐标被占用,请重新输入\n");
}
else//坐标非法,玩家输入坐标超出范围
printf("坐标非法,请重新输入\n");
}
}
//电脑随机下棋
void ComputerMove(char board[ROW][COL], int row, int col)
{
int x = 0, y = 0;
printf("电脑下棋:\n");
while (1)//只有电脑落子成功,方可跳出循环
{
x = rand() % row;
//调用rand()函数生成一个随机数,再模上一个行,
//得到一个随机的行的坐标,比如row=3,rand()%3=0、1、2
y = rand() % col;//同上
//要使用rand()函数生成随机数,需要调用srand()函数来设置随机数的生成器,
//且整个工程中srand()只需要调用一次即可(在test.c中调用)
//这些函数需要的头文件在game.h中包含
if (board[x][y] == ' ')//判断生成的坐标[x][y]是否为空
{
board[x][y] = '#';
break;//落子成功,跳出循环
}
//若[x][y]不为空,再次进入循环生成随机坐标
}
}
int IsFull(char board[ROW][COL], int row, int col)
{
int i = 0, j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')//找到空格,即棋盘没满
return 0;
}
}
return 1;//未找到' '跳出两层for循环后来到这里,即棋盘满了。
}
//判断输赢
//玩家赢--返回'*'
//电脑赢--返回'#'
//平局 --返回'Q'
//继续 --返回'C'
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))
{
return 'Q';
}
//游戏继续
return 'C';
}
这是game.h中的代码:
#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);
结束语:
运行无误后,我们便可以玩一下我们写出来的三子棋小游戏,这个版本玩起来玩家赢并不难,难的是怎么让电脑赢,哈哈哈,快来试一试吧!
代码中有不足,或者不懂的地方,欢迎在评论区指正或提问哦!