三子棋(C 语言)

server/2024/10/19 10:19:56/

目录

  • 一、游戏设计的整体思路
  • 二、各个步骤的代码实现
    • 1. 菜单及循环选择的实现
    • 2. 棋盘的初始化和显示
    • 3. 轮流下棋及结果判断实现
    • 4. 结果判断实现
  • 三、所有代码
  • 四、总结

一、游戏设计的整体思路

(1)提供一个菜单让玩家选择人机对战、玩家对战或者退出游戏,且一局结束以后还可以继续选择直到退出。
(2)使用一个 3*3 二维数组来表示一个棋盘,存放双方的落子情况并进行显示。且显示时使用一些符号进行分隔让棋盘更加合理。
(3)初始棋盘应为空,然后提示先手方下棋,要对先手方落子位置进行判断(是否合法,是否该位置已经落子),最后先手方下棋完毕后应检查棋局状态(获胜、平局或继续);同理后手方下棋。
(4)若平局或者任意一方获胜则结束对局,显示结果。

本次代码使用多文件形式,一共有三个文件,一个头文件包含各种声明,一个 .c 文件进行测试,一个 .c 文件实现函数功能。
在这里插入图片描述

二、各个步骤的代码实现

1. 菜单及循环选择的实现

(1)主函数代码的实现

// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"int main()
{// 所需变量int select = 0;// 选择do{// 菜单menu();// 选择scanf("%d", &select);// 判断switch (select){case 1 :  // 人机对战PVE();break;case 2 :PVP();  // 玩家对战break;case 0 :printf("游戏结束!\n");break;}} while (select);return 0;
}

(2)头文件中的声明

// 菜单
void menu();

(3)函数实现

// 菜单
void menu()
{printf("**************************************\n");printf("**********      1. PVE      **********\n");printf("**********      2. PVP      **********\n");printf("**********      0. exit     **********\n");printf("**************************************\n");
}

(4)运行效果如下
在这里插入图片描述

2. 棋盘的初始化和显示

棋盘的初始化和显示分别设计为两个函数 —— InitBoard()、PrintBoard()。首先,需要在主函数创建棋盘,且大小使用符号常量,方便替换。然后把棋盘的信息传入人机对战和玩家对战函数中,并在头文件和实现文件中进行相应的声明和定义。

(1)主函数创建棋盘

int main()
{// 所需变量int select = 0;char board[ROW][COL] = { 0 };  // 创建棋盘//...
}

(2)头文件进行相应声明

// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);// 打印棋盘
void PrintBoard(char board[][COL], int row, int col);// 人机对战模式
void PVE(char board[][COL], int row, int col);// 玩家对战模式
void PVP(char board[][COL], int row, int col);

(3)实现文件进行定义

// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{int i;for (i = 0; i < row; ++i){int j;for (j = 0; j < col; ++j)board[i][j] = ' ';}
}// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{// 打印棋盘时,需要适当添加符号分隔int i;for (i = 0; i < row; ++i){int j;for (j = 0; j < col; ++j)printf(" ---");// 下一行printf("\n");for (j = 0; j < col; ++j)printf("| %c ", board[i][j]);// 补齐改行分隔printf("|");// 下一行printf("\n");}// 补齐最后一行分隔for (i = 0; i < col; ++i)printf(" ---");printf("\n");
}// 人机对战模式
void PVE(char board[][COL], int row, int col)
{printf("\n人机对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);
}// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{printf("\n玩家对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);
}

运行效果如下:
在这里插入图片描述

3. 轮流下棋及结果判断实现

这里假设人机对战时,玩家使用符号 ‘#’,机器人使用符号 ‘*’;玩家对战时,一号玩家使用符号 ‘#’,二号玩家使用符号 ‘*’。玩家下棋和机器人下棋分别使用两个函数 —— PMove() 和 EMove()。PMove() 函数需要使用一个额外的变量判断是 1 号玩家下棋,还是 2 号玩家下棋。

判断结果函数 is_win() 需要判断每行是否相同、每列是否相同,倘若其中有一个实现,则有一方获胜,倘若没有则需要使用函数 is_full() 判断棋盘是否已满,若满则平局,否则继续。is_full() 函数也可以直接实现在 is_win() 函数内部。

(1)头文件函数声明

// 玩家下棋
void PMove(char board[][COL], int row, int col, int who);// 机器人下棋
void EMove(char board[][COL], int row, int col);// 判断是否获胜
void is_win(char board[][COL], int row, int col);

(2)实现文件函数定义

// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{// 所需变量int x, y;do{// 输入坐标printf("玩家下棋(输入坐标中间用空格隔开):\n");scanf("%d %d", &x, &y);// 判断合法性if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' '){printf("坐标非法,请重新输入!\n");}else{board[x - 1][y - 1] = who;break;}} while (1);
}// 机器人下棋
void EMove(char board[][COL], int row, int col)
{// 机器人是随机下棋的,这里就要获取随机数了// 需要包含头文件 stdlib.h 和 time.h // 设置随机数种子srand((unsigned)time(0));printf("电脑下棋:\n");while (1){int x = rand() % row;  // 0 - row - 1int y = rand() % col;  // 0 - col - 1if (board[x][y] == ' '){board[x][y] = '*';break;}}
}// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{// 判断每行int r = 1;int i;for (i = 0; i < row; ++i){// 重置判断符号r = 1;// 若首字符为空则下一行if (board[i][0] == ' ')continue;// 判断改行int j;for (j = 1; j < col; ++j){r *= (board[i][j - 1] == board[i][j]);// 判断if (!r)break;}if (r)return board[i][0];}// 判断每列int c = 1;int j;for (j = 0; j < col; ++j){// 重置判断符号c = 1;// 首字符为空则下一列if (board[0][j] == ' ')continue;// 判断该列int i;for (i = 1; i < row; ++i){c *= (board[i - 1][j] == board[i][j]);// 判断if (!c)break;}if (c)return board[0][j];}// 判断对角线int d = 1;if (board[0][0] != ' '){for (i = 1; i < row; ++i){d *= (board[i - 1][i - 1] == board[i][i]);// 判断if (!d)break;}if (d)return board[0][0];}if (board[0][col] != ' '){d = 1;for (i = 1; i < row; ++i){d *= (board[i - 1][col - i + 1] == board[i][col - i]);// 判断if (!d)break;}if (d)return board[0][col];}// 判断棋盘是否已满int full = 1;for (i = 0; i < row; ++i){for (j = 0; j < col; ++j){if (board[i][j] == ' '){// 未满return 'C';}}}// 已满return 'D';}

上述代码中 is_win() 函数中所使用的是通用判断方法,即使改变了符号常量 ROW 和 COL 的值也可以是判断。(如果实在理解不了,可以只当 3*3 的特殊情况来实现判断,当然,也可能是我的代码写的不好)。

(3)程序运行结果:
这里值展示人机对战的三种结果:

玩家获胜:
在这里插入图片描述

电脑获胜:
在这里插入图片描述

平均:
在这里插入图片描述

4. 结果判断实现

该代码直接放在了函数 PMove() 和函数 EMove() 中,也可以单独写一个函数。

(1)实现文件函数定义

// 人机对战模式
void PVE(char board[][COL], int row, int col)
{printf("\n人机对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 开始下棋int ret = 0;do{// 玩家下棋PMove(board, row, col, '#');// 显示棋盘PrintBoard(board, row, col);// 判断ret = is_win(board, row, col);if (ret != 'C')break;// 机器人下棋EMove(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 判断ret = is_win(board, row, col);if (ret != 'C')break;} while (1);// 判断最终结果if (ret == '#')printf("玩家获胜!\n");else if (ret == '*')printf("电脑获胜!\n");elseprintf("平局!\n");
}// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{printf("\n玩家对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 开始下棋int ret = 0;do{// 玩家 1 号下棋PMove(board, row, col, '#');// 显示棋盘PrintBoard(board, col, row);// 判断ret = is_win(board, row, col);if (ret != 'C')break;// 玩家 2 号下棋PMove(board, row, col, '*');// 显示棋盘PrintBoard(board, col, row);// 判断ret = is_win(board, row, col);if (ret != 'C')break;} while (1);// 判断最终结果if (ret == '#')printf("玩家 1 号获胜!\n");else if (ret == '*')printf("玩家 2 号获胜!\n");elseprintf("平局!\n");
}

(2)演示结果
在上一次演示结果中已经体现。

三、所有代码

分三个文件,分别是头文件 Tic_tac_toe.h,测试文件 test.c,和函数实现文件 Tic_tac_toe.c。

(1)Tic_tac_toe.h

// 常量声明
#define ROW 3
#define COL 3// 函数声明// 菜单
void menu();// 初始化棋盘
void InitBoard(char board[][COL], int row, int col);// 打印棋盘
void PrintBoard(char board[][COL], int row, int col);// 玩家下棋
void PMove(char board[][COL], int row, int col, char who);// 机器人下棋
void EMove(char board[][COL], int row, int col);// 判断是否获胜
char is_win(char board[][COL], int row, int col);// 人机对战模式
void PVE(char board[][COL], int row, int col);// 玩家对战模式
void PVP(char board[][COL], int row, int col);

(2)test.c

// 头文件
#include <stdio.h>
#include "Tic_tac_toe.h"int main()
{// 所需变量int select = 0;char board[ROW][COL] = { 0 };// 选择do{// 菜单menu();// 选择scanf("%d", &select);// 判断switch (select){case 1 :  // 人机对战PVE(board, ROW, COL);break;case 2 :PVP(board, ROW, COL);  // 玩家对战break;case 0 :printf("游戏结束!\n");break;default :printf("选择错误请重新选择!\n");break;}} while (select);return 0;
}

(3)Tic_tac_toe.c

// 头文件
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "Tic_tac_toe.h"// 函数定义// 菜单
void menu()
{printf("**************************************\n");printf("**********      1. PVE      **********\n");printf("**********      2. PVP      **********\n");printf("**********      0. exit     **********\n");printf("**************************************\n");
}// 初始化棋盘
void InitBoard(char board[][COL], int row, int col)
{int i;for (i = 0; i < row; ++i){int j;for (j = 0; j < col; ++j)board[i][j] = ' ';}
}// 打印棋盘
void PrintBoard(char board[][COL], int row, int col)
{// 打印棋盘时,需要适当添加符号分隔int i;for (i = 0; i < row; ++i){int j;for (j = 0; j < col; ++j)printf(" ---");// 下一行printf("\n");for (j = 0; j < col; ++j)printf("| %c ", board[i][j]);// 补齐改行分隔printf("|");// 下一行printf("\n");}// 补齐最后一行分隔for (i = 0; i < col; ++i)printf(" ---");printf("\n");
}// 玩家下棋
void PMove(char board[][COL], int row, int col, char who)
{// 所需变量int x, y;do{// 输入坐标printf("玩家下棋(输入坐标中间用空格隔开):\n");scanf("%d %d", &x, &y);// 判断合法性if ((x < 0 || x > row || y < 0 || y > col) || board[x - 1][y - 1] != ' '){printf("坐标非法,请重新输入!\n");}else{board[x - 1][y - 1] = who;break;}} while (1);
}// 机器人下棋
void EMove(char board[][COL], int row, int col)
{// 机器人是随机下棋的,这里就要获取随机数了// 需要包含头文件 stdlib.h 和 time.h // 设置随机数种子srand((unsigned)time(0));printf("电脑下棋:\n");while (1){int x = rand() % row;  // 0 - row - 1int y = rand() % col;  // 0 - col - 1if (board[x][y] == ' '){board[x][y] = '*';break;}}
}// 判断是否获胜
char is_win(char board[][COL], int row, int col)
{// 判断每行int r = 1;int i;for (i = 0; i < row; ++i){// 重置判断符号r = 1;// 若首字符为空则下一行if (board[i][0] == ' ')continue;// 判断改行int j;for (j = 1; j < col; ++j){r *= (board[i][j - 1] == board[i][j]);// 判断if (!r)break;}if (r)return board[i][0];}// 判断每列int c = 1;int j;for (j = 0; j < col; ++j){// 重置判断符号c = 1;// 首字符为空则下一列if (board[0][j] == ' ')continue;// 判断该列int i;for (i = 1; i < row; ++i){c *= (board[i - 1][j] == board[i][j]);// 判断if (!c)break;}if (c)return board[0][j];}// 判断对角线int d = 1;if (board[0][0] != ' '){for (i = 1; i < row; ++i){d *= (board[i - 1][i - 1] == board[i][i]);// 判断if (!d)break;}if (d)return board[0][0];}if (board[0][col] != ' '){d = 1;for (i = 1; i < row; ++i){d *= (board[i - 1][col - i + 1] == board[i][col - i]);// 判断if (!d)break;}if (d)return board[0][col];}// 判断棋盘是否已满int full = 1;for (i = 0; i < row; ++i){for (j = 0; j < col; ++j){if (board[i][j] == ' '){// 未满return 'C';}}}// 已满return 'D';}// 人机对战模式
void PVE(char board[][COL], int row, int col)
{printf("\n人机对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 开始下棋int ret = 0;do{// 玩家下棋PMove(board, row, col, '#');// 显示棋盘PrintBoard(board, row, col);// 判断ret = is_win(board, row, col);if (ret != 'C')break;// 机器人下棋EMove(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 判断ret = is_win(board, row, col);if (ret != 'C')break;} while (1);// 判断最终结果if (ret == '#')printf("玩家获胜!\n");else if (ret == '*')printf("电脑获胜!\n");elseprintf("平局!\n");
}// 玩家对战模式
void PVP(char board[][COL], int row, int col)
{printf("\n玩家对战:\n");// 初始化棋盘InitBoard(board, row, col);// 显示棋盘PrintBoard(board, row, col);// 开始下棋int ret = 0;do{// 玩家 1 号下棋PMove(board, row, col, '#');// 显示棋盘PrintBoard(board, col, row);// 判断ret = is_win(board, row, col);if (ret != 'C')break;// 玩家 2 号下棋PMove(board, row, col, '*');// 显示棋盘PrintBoard(board, col, row);// 判断ret = is_win(board, row, col);if (ret != 'C')break;} while (1);// 判断最终结果if (ret == '#')printf("玩家 1 号获胜!\n");else if (ret == '*')printf("玩家 2 号获胜!\n");elseprintf("平局!\n");
}

四、总结

本次三子棋的代码编写总体来说还可以,基本上都实现了上述功能,其中的某些代码本人均是根据符号常量来编写的,也就是即使符号常量改变,本代码依旧可以实现。当然,本人水平有限,有的地方看不懂可能是本人代码写的太烂,这里先道个歉。

当然代码还有很多可以提升的地方,比如优化棋盘,可以使用鼠标来下棋,提升电脑的水平等。


http://www.ppmy.cn/server/133018.html

相关文章

探索 C# 的进阶特性

随着 C# 语言的不断演进&#xff0c;越来越多的特性被引入&#xff0c;提升了代码的可读性和性能。这些进阶特性为开发者提供了更多简洁而强大的工具&#xff0c;用来编写高效、优雅的代码。本文将介绍 C# 中的一些重要进阶特性&#xff0c;包括属性模式匹配、异常过滤器、记录…

学习索引时想到的问题

问题 在查询时&#xff0c;根据两个单独进行索引的字段进行查询&#xff0c;他的查询过程是什么样的&#xff1f; 答&#xff1a;数据库会先评估怎样使用索引是最快的&#xff08;两个单独的索引和一个包含两个字段的复合索引会使用复合索引而不是用两个单独的索引,也有可能会…

用Python构建动态折线图:实时展示爬取数据的指南

背景/引言 随着大数据和人工智能的不断发展&#xff0c;实时数据分析变得越来越关键&#xff0c;尤其是在金融市场中。股市数据的实时可视化可以帮助投资者快速做出决策&#xff0c;避免错失良机。Python 凭借其强大的数据处理能力和丰富的可视化库&#xff0c;成为分析和展示…

外包干了3周,技术退步太明显了。。。。。

先说一下自己的情况&#xff0c;大专生&#xff0c;21年通过校招进入武汉某软件公司&#xff0c;干了差不多3个星期的功能测试&#xff0c;那年国庆&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我才在一个外包企业干了3周的功…

Flink CDC同步mysql数据到doris

前置参考 flink快速安装&#xff1a;Flink入门-CSDN博客 doris快速安装&#xff1a;Apache Doris快速安装-CSDN博客 Flink CDC简介 Flink CDC 是一个基于流的数据集成工具&#xff0c;旨在为用户提供一套功能更加全面的编程接口&#xff08;API&#xff09;。 该工具使得用户能…

C语言_指针_进阶

引言&#xff1a;在前面的c语言_指针初阶上&#xff0c;我们了解了简单的指针类型以及使用&#xff0c;下面我们将进入更深层次的指针学习&#xff0c;对指针的理解会有一个极大的提升。从此以后&#xff0c;指针将不再是难点&#xff0c;而是学习底层语言的一把利器。 本章重点…

《linux运维》之如何使用putty软件完成宿主机到虚拟机传递文件

背景 当虚拟机不具备外网环境时&#xff0c;无法使用下载及使用Xshell自带的xftp工具时&#xff0c;可使用Putty软件完成宿主机至虚拟机的文件传输 使用过程 1&#xff09; 在Putty文件夹目录下输入cmd 2&#xff09;进入命令行模式&#xff0c;使用命令 pscp 3) 将所传输的…

go语言实现简单的grpc客户端服务端案例

在 Go 中实现一个简单的 gRPC 客户端和服务端&#xff0c;可以按照以下步骤进行。 1. 安装 gRPC 和相关工具 首先&#xff0c;需要安装 protobuf 编译器和 protoc-gen-go 插件&#xff1a; # 安装protoc brew install protobuf# 安装protoc-gen-go和protoc-gen-go-grpc插件 …