文章目录
三(多)子棋
1. 实现思路
- 显示选择菜单(玩家选择开始游戏/结束游戏)
- 玩家进行选择(1为开始游戏,0为退出游戏)
- 创建棋盘用于存放玩家/电脑所下的棋子
- 初始化棋盘,未下子之前,棋盘应为空棋盘(当棋盘中的子都为’空格‘时,主观上我们认为这个棋盘是空棋盘)
- 打印棋盘
- 下棋
- 玩家下棋:设玩家的棋子为’*‘
- 电脑下棋:设电脑的棋子为’#‘
- 判断输赢:赢(一行相等、一列相等、主次对角线相等)、平局(棋盘放满,且无一方赢)
- 再次显示菜单,玩家选择再来一局或者结束游戏
2. 模块化编程
模块化是指解决一个复杂的问题时,我们可以将其自顶向下逐层把系统划分成若干模块的过程。
我们可以将上述需要实现的功能逐一分成一个个的函数去进行实现,然后再使用主函数去调用测试这个函数实现是否正确。
当程序出现问题时,可以直观的找到问题出现在哪个模块。
再将实现功能的函数放在实现文件game.c中,将测试功能的主函数放在测试文件test.c中,将程序需要调用的库函数、外部函数的声明存放在game.h中避免重复调用。
每一个函数只实现一个功能,每一个文件中存放相同作用的程序;模块化编程可以提高代码的可维护性、可移植性、可阅读性、可测试性;
3. 功能实现
3.1 菜单
当我们打开游戏的时候,最先与游戏进行互动的界面就是菜单栏,通过菜单栏,我们可以进行开始游戏,结束游戏,设置等等
你可以根据你自己的喜好设置菜单栏的样子,只需杨将其打印出来即可,让玩家可以知道,他们可以做什么选择。
void Menu()
{
printf("*********************************\n");
printf("****** *****\n");
printf("****** 1.play 0.exit *****\n");
printf("****** *****\n");
printf("*********************************\n");
}
3.2 创建棋盘
下棋之前,我们需要先有一个棋盘,我们都见过真正的棋盘,通过行线和列线来确定棋盘的大小,和棋盘中有多少可以下子的格子。如此我们知道,可以将棋子存放在一个二维数组中,二维数组的行数和列数就代表着棋盘的行数和列数。
如果我们只是单纯的想创建一个3*3的棋盘即三子棋,那么我们可以直接创建一个3行3列的数组;但是这样就把整个程序写死了;之后我们如果想写一个五子棋甚至更多,需要将整个程序进行大改,所以我们可以使用ROW和COL分别代表行和列,创建
checkerboard[ROW][COL];
这样的数组,但是有些编译器又不支持这样的变长数组,那我们怎么办呢?此时我们可以使用宏定义
#define
,其功能就是将标识符定义为其后的常量
#define ROW 3
#define COL 3
char checkerboard[ROW][COL];
如上,此时ROW和COL就分别代表其后面的数字3,如果需要更改棋盘(数组)的大小,只需要将定义的宏后的常量进行更改即可。
3.3 初始化棋盘
棋盘内部最初存放的是空格,给玩家一种棋盘是空的视觉感受。
实现相当于将一个二维数组中的所有元素均置为空格
函数声明
void InitBoard(char checkerboard[ROW][COL], int row, int col);
函数定义
void InitBoard(char checkerboard[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
checkerboard[i][j] = ' ';
}
}
}
函数调用
InitBoard(checkerboard, ROW, COL);
3.4 打印棋盘
如果只是单纯的打印数组(棋盘),那么我们打印的效果只能是一片空白,玩家看不到棋盘在哪
void PrintBoard(char checkerboard[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 ", checkerboard[i][j]);
}
printf("\n");
}
}
所以我们需要设计一个棋盘,使得其打印出来之后,玩家可以直观的看到这个棋盘的存在,例如下面这种
使用竖杠和下横线将整个数组(棋盘)的每个元素进行分割
棋盘第一行的为: | | 棋盘第二行的为:—|—|—
void PrintBoard(char checkerboard[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 ",checkerboard[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");
}
}
3.5 下棋
就如同五子棋中的黑白子,双方玩家执不同棋子;所以在游玩之前,我们需要先为玩家设定不同的棋子,以进行分辨和后续的输赢判定。
例如:我们可以设玩家下棋为
*
;电脑下棋为#
;
3.5.1 玩家下棋
玩家下棋时,我们需要让玩家输入其要下的坐标,而玩家输入的坐标和我们程序中的坐标是不同的,玩家的坐标从1开始,程序中的坐标从0开始,毕竟不是每个玩家都是程序员。在玩家输入坐标之后,程序需要进行判断,玩家输入的坐标是否可以正常下子,坐标是否超出棋盘的范围,坐标是否已经被占用是否重复落子,只有玩家输入的坐标满足上述条件时才能正常落子,否则需要玩家重新输入。
void PlayMove(char checkerboard[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入您要下的坐标:");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (checkerboard[x - 1][y - 1] != ' ')
{
printf("您输入的坐标已被占用,请重新输入!\n");
}
else
{
checkerboard[x-1][y-1] = '*';
break;
}
}
else
{
printf("您输入的坐标非法请重新输入!\n");
}
}
}
3.5.2 电脑下棋
电脑下棋,也需要输入坐标,那么电脑输入的坐标怎么产生呢?此时我们就需要用到随机数,使用随机函数生成两个指定范围内的随机数,然后这两个随机数分别代表横纵坐标,之后进行判断这个坐标是否在棋盘范围内,是否重复落子,如果出现上述问题则重新生成随机数,否则就落子。
生成随机数
首先使用srand()函数,将电脑固定的随机数种子进行重新初始化;然后使用rand()函数生成指定范围内的数字。
rand()%10
此程序的意思是生成0~9之间的随机数字,包括0和9
void ComputerMove(char checkerboard[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (checkerboard[x][y] == ' ')
{
checkerboard[x][y] = '#';
break;
}
}
}
3.6 判断输赢
在函数内部判断输赢,然后返回一个字符。返回*则代表玩家赢,返回#代表电脑赢,然后d代表平局,返回g代表继续游戏。
测试文件中,只需要在调用判断输赢函数之后,接收返回值,判断返回值为什么,即可决定游戏结束还是继续下棋。
输赢的判定标准:一行全相等、一列全相等、主对角线相等、次对角线相等。
3.6.1 固定3*3判断输赢
对于固定行和列的棋盘,判断输赢,只需要直接写出每个坐标,然后判断是否相同,即可;判断满,只需要遍历整个数组,然后判断是否还存在空格
//遍历棋盘,判断是否存在空格
int IsFull(char checkerboard[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 (checkerboard[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char WinLose(char checkerboard[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
//判断行
for (i = 0; i < row; i++)
{
if (checkerboard[i][0] == checkerboard[i][1] && checkerboard[i][1] == checkerboard[i][2] && checkerboard[i][1] != ' ')
{
return checkerboard[i][1];
}
}
//判断列
for (i = 0; i < col; i++)
{
if (checkerboard[0][i] == checkerboard[1][i] && checkerboard[1][i] == checkerboard[2][i] && checkerboard[1][i] != ' ')
{
return checkerboard[1][i];
}
}
//判断对角线
if (checkerboard[0][0] == checkerboard[1][1] && checkerboard[1][1] == checkerboard[2][2] && checkerboard[1][1] != ' ')
{
return checkerboard[1][1];
}
if (checkerboard[0][2] == checkerboard[1][1] && checkerboard[1][1] == checkerboard[2][0] && checkerboard[1][1] != ' ')
{
return checkerboard[1][1];
}
//判断满棋盘
if (IsFull(checkerboard, row, col))
{
return 'd';
}
return 'g';
}
3.6.2 动态判断多行多列输赢
int IsFull(char checkerboard[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 (checkerboard[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char WinLose(char checkerboard[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int k = 0;
//判断行
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//用每行的第一个与第二个数进行比较,如果相同并且不是空格,则用第一个与第三个进行比较,知道整行比较完成,或者碰到不相等的
while (checkerboard[i][j] == checkerboard[i][j + k] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i][j + k]&&checkerboard[i][j]!=' ')
{
return checkerboard[i][j];
}
}
}
//判断列
k = 0;
for (i = 0; i < col; i++)
{
for (j = 0; j < row; j++)
{
while (checkerboard[i][j] == checkerboard[i + k][j] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i + k][j] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
}
}
//判断对角线
//主对角线
//主对角线坐标,x=y,00、11、22、33、44、55
//用角坐标比较
i = 0;
j = 0;
k = 0;
while (checkerboard[i][j] == checkerboard[i + k][j + k] && checkerboard[i][j] != ' ' && k < row-1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i + k][j + k] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
//次对角线
i = row-1;
j = 0;
k = 0;
while (checkerboard[i][j] == checkerboard[i - k][j + k] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i - k][j + k] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
//判断满棋盘
if (IsFull(checkerboard, row, col))
{
return 'd';
}
return 'g';
}
3.7 测试调用
#include"game.h"
void Menu()
{
printf("*********************************\n");
printf("****** *****\n");
printf("****** 1.play 0.exit *****\n");
printf("****** *****\n");
printf("*********************************\n");
}
void StartGame()
{
//创建一个ROW行COL列的二维数组,用于存放棋子
char checkerboard[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(checkerboard, ROW, COL);
//打印棋盘,设计棋盘的样式,使得棋盘更具可观性
PrintBoard(checkerboard, ROW, COL);
//前期准备工作完成,开始下棋,当一方胜利,或者平局时,游戏结束,跳出循环。
//例如,三子棋,只有当一方三子连着的时候,才能判定输赢,所以当小于3次下棋的时候不需要进行输赢判断
//判断满足下棋次数之后再判断输赢,可以减少时间上的浪费,以及函数调用次数
int play_count = 0;
int computer_count = 0;
char ret = 0;
while (1)
{
//玩家下棋
PlayMove(checkerboard, ROW, COL);
//下棋之后,打印棋盘
PrintBoard(checkerboard, ROW, COL);
play_count++;
if (play_count >= ROW)
{
//判断输赢
ret = WinLose(checkerboard, ROW, COL);
if (ret != 'g')//当返回值不是g的时候说明游戏有人赢了或者满了,则游戏结束不再下棋
{
break;
}
}
//电脑下棋
ComputerMove(checkerboard, ROW, COL);
//打印棋盘
PrintBoard(checkerboard, ROW, COL);
computer_count++;
if (computer_count >= ROW)
{
//判断输赢
ret = WinLose(checkerboard, ROW, COL);
if (ret != 'g')
{
break;
}
}
}
if (ret == '*')
{
printf("恭喜玩家获得了胜利!!!!\n");
}
else if (ret == '#')
{
printf("恭喜电脑获得了胜利!!!\n");
}
else
{
printf("游戏平局!!!\n");
}
PrintBoard(checkerboard, ROW, COL);
}
//主函数
int main()
{
int select = 0;
srand((unsigned int)time(NULL));
//循环,先执行一次,然后让用户进行选择
do
{
Menu();//打印菜单
printf("请选择(0/1):");
scanf("%d", &select);//用户输入选择
//switch语句,根据用户的选择进行执行
switch (select)
{
case 0:
printf("游戏结束!!!\n");
break;
case 1:
StartGame();//开始游戏,进入游戏执行内部
//printf("游戏开始!!!\n");
break;
default:
printf("选择错误,请重新选择!!!\n");
break;
}
} while (select);
return 0;
}
5. 代码实现
代码仓库,可以直接下载
[三子棋实现 - Gitee.com](https://gitee.com/OrangeYcc/C_Stage/tree/master/Game/Tic Tac Toe 2/Tic Tac Toe 2)
game.h
#define _CRT_SECURE_NO_WARNINGS 1
#define ROW 5 //行数
#define COL 5 //列数
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
//初始化棋盘
void InitBoard(char checkerboard[ROW][COL], int row, int col);
//打印棋盘
void PrintBoard(char checkerboard[ROW][COL], int row, int col);
//玩家下棋
void PlayMove(char checkerboard[ROW][COL], int row, int col);
//电脑下棋
void ComputerMove(char checkerboard[ROW][COL], int row, int col);
//判断输赢/平局
char WinLose(char checkerboard[ROW][COL], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
//将棋盘初始化为空格,打印时给玩家视觉感受为棋盘为空的
void InitBoard(char checkerboard[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
checkerboard[i][j] = ' ';
}
}
}
//打印棋盘
//棋盘初始为空格,下棋之后空格转换为*/#
// 为了便于观看,使用横线将每个棋子隔开,构成一个九宫格
// 棋盘样式:
// # | # | #
// ---|---|---
// # | # | #
// ---|---|---
// # | # | #
//棋盘第一行的为: | |
//棋盘第二行的为:---|---|---
//随着ROW和COL的改变,棋盘的大小也会改变,而不是只能打印3*3
void PrintBoard(char checkerboard[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 ",checkerboard[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");
}
}
//下棋步骤:
//玩家下期坐标从1~3;程序棋盘坐标为0~2
//判断下棋区域是否为空格,不是空格说明已经被占用
//判断玩家所输入的坐标是否为范围内
//玩家下棋 *
void PlayMove(char checkerboard[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("请输入您要下的坐标:");
scanf("%d %d", &x, &y);
if (x > 0 && x <= row && y > 0 && y <= col)
{
if (checkerboard[x - 1][y - 1] != ' ')
{
printf("您输入的坐标已被占用,请重新输入!\n");
}
else
{
checkerboard[x-1][y-1] = '*';
break;
}
}
else
{
printf("您输入的坐标非法请重新输入!\n");
}
}
}
//电脑下棋 #
//电脑下棋需要生成两个个在棋盘大小范围内的随机数,用于构成坐标
//当生成的随机数构成的坐标已被占用时,需要重新生成
//生成随机数需要使用rand()函数,srand()函数
void ComputerMove(char checkerboard[ROW][COL], int row, int col)
{
printf("电脑下棋:\n");
while (1)
{
int x = rand() % row;
int y = rand() % col;
if (checkerboard[x][y] == ' ')
{
checkerboard[x][y] = '#';
break;
}
}
}
//判断输赢
//玩家赢,返回*
//电脑赢,返回#
//平局,返回d
//继续,返回g
//行相同、列相同、主次对角线相同则赢得比赛
//无上述情况且棋盘满为平局
//否则继续游戏
int IsFull(char checkerboard[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 (checkerboard[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
//固定判断3*3棋盘输赢
//char WinLose(char checkerboard[ROW][COL], int row, int col)
//{
// int i = 0;
// int j = 0;
//
// //判断行
// for (i = 0; i < row; i++)
// {
// if (checkerboard[i][0] == checkerboard[i][1] && checkerboard[i][1] == checkerboard[i][2] && checkerboard[i][1] != ' ')
// {
// return checkerboard[i][1];
// }
// }
//
//
// //判断列
// for (i = 0; i < col; i++)
// {
// if (checkerboard[0][i] == checkerboard[1][i] && checkerboard[1][i] == checkerboard[2][i] && checkerboard[1][i] != ' ')
// {
// return checkerboard[1][i];
// }
// }
//
// //判断对角线
// if (checkerboard[0][0] == checkerboard[1][1] && checkerboard[1][1] == checkerboard[2][2] && checkerboard[1][1] != ' ')
// {
// return checkerboard[1][1];
// }
// if (checkerboard[0][2] == checkerboard[1][1] && checkerboard[1][1] == checkerboard[2][0] && checkerboard[1][1] != ' ')
// {
// return checkerboard[1][1];
// }
//
// //判断满棋盘
// if (IsFull(checkerboard, row, col))
// {
// return 'd';
// }
//
// return 'g';
//}
//动态判断ROW*COL棋盘输赢
//多行多列判断思路:一直用第一个与后面的相比较,
//直到此行/列比较完毕或者出现不同时结束比较,然后使用第一个和结束比较时的坐标上的字符进行比较,如果相同则表示此行/相同。
char WinLose(char checkerboard[ROW][COL], int row, int col)
{
int i = 0;
int j = 0;
int k = 0;
//判断行
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//用每行的第一个与第二个数进行比较,如果相同并且不是空格,则用第一个与第三个进行比较,知道整行比较完成,或者碰到不相等的
while (checkerboard[i][j] == checkerboard[i][j + k] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i][j + k]&&checkerboard[i][j]!=' ')
{
return checkerboard[i][j];
}
}
}
//判断列
k = 0;
for (i = 0; i < col; i++)
{
for (j = 0; j < row; j++)
{
while (checkerboard[i][j] == checkerboard[i + k][j] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i + k][j] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
}
}
//判断对角线
//主对角线
//主对角线坐标,x=y,00、11、22、33、44、55
//用角坐标比较
i = 0;
j = 0;
k = 0;
while (checkerboard[i][j] == checkerboard[i + k][j + k] && checkerboard[i][j] != ' ' && k < row-1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i + k][j + k] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
//次对角线
i = row-1;
j = 0;
k = 0;
while (checkerboard[i][j] == checkerboard[i - k][j + k] && checkerboard[i][j] != ' ' && k < row - 1)
{
k++;
}
if (checkerboard[i][j] == checkerboard[i - k][j + k] && checkerboard[i][j] != ' ')
{
return checkerboard[i][j];
}
//判断满棋盘
if (IsFull(checkerboard, row, col))
{
return 'd';
}
return 'g';
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void Menu()
{
printf("*********************************\n");
printf("****** *****\n");
printf("****** 1.play 0.exit *****\n");
printf("****** *****\n");
printf("*********************************\n");
}
void StartGame()
{
//创建一个ROW行COL列的二维数组,用于存放棋子
char checkerboard[ROW][COL] = { 0 };
//初始化棋盘
InitBoard(checkerboard, ROW, COL);
//打印棋盘,设计棋盘的样式,使得棋盘更具可观性
PrintBoard(checkerboard, ROW, COL);
//前期准备工作完成,开始下棋,当一方胜利,或者平局时,游戏结束,跳出循环。
//例如,三子棋,只有当一方三子连着的时候,才能判定输赢,所以当小于3次下棋的时候不需要进行输赢判断
//判断满足下棋次数之后再判断输赢,可以减少时间上的浪费,以及函数调用次数
int play_count = 0;
int computer_count = 0;
char ret = 0;
while (1)
{
//玩家下棋
PlayMove(checkerboard, ROW, COL);
//下棋之后,打印棋盘
PrintBoard(checkerboard, ROW, COL);
play_count++;
if (play_count >= ROW)
{
//判断输赢
ret = WinLose(checkerboard, ROW, COL);
if (ret != 'g')//当返回值不是g的时候说明游戏有人赢了或者满了,则游戏结束不再下棋
{
break;
}
}
//电脑下棋
ComputerMove(checkerboard, ROW, COL);
//打印棋盘
PrintBoard(checkerboard, ROW, COL);
computer_count++;
if (computer_count >= ROW)
{
//判断输赢
ret = WinLose(checkerboard, ROW, COL);
if (ret != 'g')
{
break;
}
}
}
if (ret == '*')
{
printf("恭喜玩家获得了胜利!!!!\n");
}
else if (ret == '#')
{
printf("恭喜电脑获得了胜利!!!\n");
}
else
{
printf("游戏平局!!!\n");
}
PrintBoard(checkerboard, ROW, COL);
}
int main()
{
int select = 0;
srand((unsigned int)time(NULL));
do
{
Menu();
printf("请选择(0/1):");
scanf("%d", &select);
switch (select)
{
case 0:
printf("游戏结束!!!\n");
break;
case 1:
StartGame();
/*printf("游戏开始!!!\n");*/
break;
default:
printf("选择错误,请重新选择!!!\n");
break;
}
} while (select);
return 0;
}