目录
前言
不少小伙伴刚刚学完数组就想来练练手,那么《三子棋》项目是个不错的选择,话不多说,我们开始吧!
1. 文件准备
①test.c :存放main函数,测试代码的主要流程。
②game.c : 存放游戏的主要逻辑,以及关于游戏函数的完整实现。
③game.h : 存放游戏函数的定义,包含所有头文件的引用,以及宏定义。
test.c和game.c只需要引入game.h即可。
2. 程序流程编写
2.1 主函数大体逻辑
test.c : main()
①程序一旦运行,我们需要显示菜单,所以定义 showMenu()方法显示菜单。
②根据用户的输入不同我们可以进入游戏或者退出游戏。
int main()
{
int input = 0;
do
{
showMenu();
printf("请选择>");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏退出!\n");
break;
default:
printf("输入错误!请重新选择!\n");
}
} while (input);
}
test.c : showMenu()
void showMenu()
{
printf("------------------------\n");
printf("------ chess game ------\n");
printf("------ 1 play ----------\n");
printf("-------0 exit ----------\n");
printf("------------------------\n");
}
2.2 棋盘逻辑
test.c : game()
game()函数是整个小游戏的逻辑框架,我们需要思考这样几个问题,当用户和电脑进行游戏的时候,这是一个持续的过程,也就意味着会有数据的产生,所以数据存在哪里?存好数据之后,我们如何以棋盘的形式把数据展示出来?
2.2.1 棋子数据初始化
①带着这两个问题我们继续代码的编写,三子棋的棋盘有三行三列,我们可以使用二维字符数组进行存储。那么游戏开始之前我们需要一个空棋盘,这就意味着我们要把二维数组的每一个元素初始化为空字符。
void game()
{
// 存储棋子方位数据
char board[ROW][COL] = { 0 };
// 初始化数据,使得空棋盘显示的数据是空的而不会是0
InitBoard(board,ROW,COL);
}
game.c : InitBoard()
// 初始化棋盘
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] = ' ';
}
}
}
2.2.2 棋盘的显示
game.c : DisplayBoard() 第一版
// 打印棋盘以及数据
DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row; i++)
{
// 打印数据
printf(" %c | %c | %c \n",board[i][0],board[i][1],board[i][2]);
// 打印分隔,最后一行少打一次
if(i < row - 1)
printf("---|---|---\n");
}
}
这样一来问题就会出现,这里的代码都是死的,若我们修改棋盘的的ROW和COL,代码又得修改。有没有一种一劳永逸的方法呢?我们可以将字母和‘|’分开打印,‘---’和‘|’分开打印,全部使用循环控制,有多少列就打印多少次即可。
game.c : DisplayBoard() 第二版
// 打印棋盘以及数据
DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row; i++)
{
// 打印数据 有几列就打印几次, %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");
}
此时我们修改棋盘的行和列,就会随意生成相应空棋盘,例如下图生成10*10的空棋盘。
2.3 玩家下棋
玩家输入两个合法的坐标,只需要根据坐标把棋盘数组填充即可。
game.c : PlayerMove();
// 玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col)
{
// 玩家眼中的坐标从1开始
int x = 0;
int y = 0;
while (1)
{
printf("玩家请下棋>");
scanf("%d %d", &x, &y);
// 判断坐标准确性 若3行3列则玩家选择的行列坐标应该是:1,2,3
if (x > 0 && x <= row && y > 0 && y <= col)
{
// 若该坐标的位置已经存在则不准下在该位置
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该位置已经有棋子了!!换个地方下!");
}
}
else
{
printf("坐标有误!重新输入!!");
}
}
}
玩家下棋完毕之后,只需要再次调用显示棋盘的方法即可完成效果。
2.4 电脑下棋
随机生成横纵坐标,在找空位下棋即可。
game.c : PlayerComputer();
// 电脑下棋
void PlayerComputer(char board[ROW][COL], int row, int col)
{
printf("电脑正在下棋....\n");
int x = 0;
int y = 0;
while(1)
{
// 若是3行3列则随机生成 0 1 2
x = rand() % row;
y = rand() % row;
// 判断该位置是否被占用
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
2.5 输赢的逻辑
电脑或者玩家每下一次,就会产生四种结果:玩家嬴(‘*’),电脑嬴(‘#’),玩家电脑都没有嬴,此时棋盘已经下满(平局‘Q’),继续(‘C’)。
game.c :isWin(); 第一版
// 判断输赢
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];
}
}
// 列
int j = 0;
for (j = 0; j < col; j++)
{
// 每一行相邻元素相等且不为空格
if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[j][0] != ' ')
{
// 不是人嬴就是机器嬴 只需要返回这个元素
return board[0][j];
}
}
// 正对角
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];
}
// 判断平局
int l = 0;
int k = 0;
for (l = 0; l < row;l++)
{
for (k = 0; k < col; k++)
{
if (board[l][k] == ' ')
{
// 走到这里的时候没有人嬴 且棋盘有空位
return 'C';
}
}
}
// 不然就是平局
return 'Q';
}
判断行、列、对角线的时候代码写死,若行列增加,代码要重新修改,代码显得些许丑陋,所以我们再改一版。
game.c :isWin(); 第二版
// 判断输赢
char isWin(char board[ROW][COL], int row, int col)
{
// 判断每一行、每一列、对角线的情况
// 行
int i = 0;
for (i = 0;i < row;i++)
{
int flag = 1;
// 判断相邻列元素相等
for (int t = 0; t < col - 1; t++)
{
if (board[i][t] == board[i][t + 1] && board[i][0] != ' ')
{
;
}
else
{
flag = 0;
}
}
if (flag)
{
return board[i][0];
}
}
// 列
int j = 0;
for (j = 0; j < col; j++)
{
int flag = 1;
// 判断相邻行元素相等
for (int t = 0; t < row - 1; t++)
{
if (board[t][j] == board[t + 1][j] && board[0][j] != ' ')
{
;
}
else
{
flag = 0;
}
}
if (flag)
{
return board[0][j];
}
}
// 正对角
int flag = 1;
for (int t = 0; t < row - 1; t++) {
if (board[t][t] != board[t + 1][t + 1] || board[0][0] == ' ') {
flag = 0;
break;
}
}
if (flag) {
return board[0][0];
}
// 反对角
flag = 1;
for (int t = 0; t < row - 1; t++) {
if (board[t][col - 1 - t] != board[t + 1][col - 2 - t] || board[0][col - 1] == ' ') {
flag = 0;
break;
}
}
if (flag) {
return board[0][col - 1];
}
// 判断平局
int l = 0;
int k = 0;
for (l = 0; l < row;l++)
{
for (k = 0; k < col; k++)
{
if (board[l][k] == ' ')
{
// 走到这里的时候没有人嬴 且棋盘有空位
return 'C';
}
}
}
// 不然就是平局
return 'Q';
}
这样一来代码的可扩展性就非常高了,只需要改变棋盘的大小就可以下n子棋了。如下图四子棋。
3. 代码总览
test.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
void showMenu()
{
printf("------------------------\n");
printf("------ chess game ------\n");
printf("------ 1 play ----------\n");
printf("-------0 exit ----------\n");
printf("------------------------\n");
}
void game()
{
// 生成随机数种子
srand((unsigned int)time(NULL));
// 存储棋子方位数据
char board[ROW][COL] = { 0 };
// 存储输赢
char ret = ' ';
// 初始化数据,使得空棋盘显示的数据是空的而不会是0
InitBoard(board,ROW,COL);
// 打印棋盘以及数据
DisplayBoard(board,ROW,COL);
while (1)
{
// 玩家下棋
PlayerMove(board, ROW, COL);
// 判断输赢 只要不是继续就退出循环
ret = isWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
// 打印棋盘以及数据
DisplayBoard(board, ROW, COL);
// 电脑下棋
PlayerComputer(board, ROW, COL);
// 判断输赢 只要不是继续就退出循环
ret = isWin(board, ROW, COL);
if (ret != 'C')
{
break;
}
// 打印棋盘以及数据
DisplayBoard(board, ROW, COL);
}
if (ret == '*')
{
printf("玩家获胜!\n");
}
else if (ret == '#')
{
printf("电脑获胜!\n");
}
else
{
printf("平局了!\n");
}
}
int main()
{
int input = 0;
do
{
showMenu();
printf("请选择>");
scanf("%d",&input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("游戏退出!\n");
break;
default:
printf("输入错误!请重新选择!\n");
}
} while (input);
}
game.h
#define _CRT_SECURE_NO_WARNINGS
#define COL 3
#define ROW 3
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
// 初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
// 打印棋盘以及数据
DisplayBoard(char board[ROW][COL], int row, int col);
// 玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
// 电脑下棋
void PlayerComputer(char board[ROW][COL], int row, int col);
// 判断输赢
char isWin(char board[ROW][COL], int row, int col);
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] = ' ';
}
}
}
// 打印棋盘以及数据
DisplayBoard(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0;i < row; i++)
{
// 打印数据 有几列就打印几次, %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 PlayerMove(char board[ROW][COL], int row, int col)
{
// 玩家眼中的坐标从1开始
int x = 0;
int y = 0;
while (1)
{
printf("玩家请下棋>");
scanf("%d %d", &x, &y);
// 判断坐标准确性 若3行3列则玩家选择的行列坐标应该是:1,2,3
if (x > 0 && x <= row && y > 0 && y <= col)
{
// 若该坐标的位置已经存在则不准下在该位置
if (board[x - 1][y - 1] == ' ')
{
board[x - 1][y - 1] = '*';
break;
}
else
{
printf("该位置已经有棋子了!!换个地方下!");
}
}
else
{
printf("坐标有误!重新输入!!");
}
}
}
// 电脑下棋
void PlayerComputer(char board[ROW][COL], int row, int col)
{
printf("电脑正在下棋....\n");
int x = 0;
int y = 0;
while(1)
{
// 若是3行3列则随机生成 0 1 2
x = rand() % row;
y = rand() % row;
// 判断该位置是否被占用
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++)
{
int flag = 1;
// 判断相邻列元素相等
for (int t = 0; t < col - 1; t++)
{
if (board[i][t] == board[i][t + 1] && board[i][0] != ' ')
{
;
}
else
{
flag = 0;
}
}
if (flag)
{
return board[i][0];
}
}
// 列
int j = 0;
for (j = 0; j < col; j++)
{
int flag = 1;
// 判断相邻行元素相等
for (int t = 0; t < row - 1; t++)
{
if (board[t][j] == board[t + 1][j] && board[0][j] != ' ')
{
;
}
else
{
flag = 0;
}
}
if (flag)
{
return board[0][j];
}
}
// 正对角
int flag = 1;
for (int t = 0; t < row - 1; t++) {
if (board[t][t] != board[t + 1][t + 1] || board[0][0] == ' ') {
flag = 0;
break;
}
}
if (flag) {
return board[0][0];
}
// 反对角
flag = 1;
for (int t = 0; t < row - 1; t++) {
if (board[t][col - 1 - t] != board[t + 1][col - 2 - t] || board[0][col - 1] == ' ') {
flag = 0;
break;
}
}
if (flag) {
return board[0][col - 1];
}
// 判断平局
int l = 0;
int k = 0;
for (l = 0; l < row;l++)
{
for (k = 0; k < col; k++)
{
if (board[l][k] == ' ')
{
// 走到这里的时候没有人嬴 且棋盘有空位
return 'C';
}
}
}
// 不然就是平局
return 'Q';
}
从《三子棋》再到《多子棋》的练习,其实就是部分代码的优化,所以我们在平时写代码的时候不能只针对当前需求写死代码,而是要有预见性地把代码写活,今天的代码练习就到这里了,各位读者看到这里的话,如果对您有帮助,务必点赞收藏关注评论支持一下,你们的支持是我最大的动力!!