【C语言小项目】五子棋游戏
目录
前言
一、游戏规则
1.功能分析
2.玩法分析
3.胜负判定条件
二、游戏实现思路
三、代码实现与函数封装
1.项目文件创建
2.头文件说明
3.函数封装
1)菜单实现
2)进度条实现
3)main函数实现
4)Game函数
5)ShowBoard函数实现
6)PlayerMove函数实现
7)ChessCount函数实现
8)IsOver函数实现
四、源码分享
1.main.c
2.ProBar.h
3.probar.c
4.game.h
5.game.c
总结
前言
五子棋,又称连珠棋,是一种双人对弈的棋类游戏。游戏目标是在一个棋盘上,通过在横、竖、斜线上依次放置棋子,使自己的五个棋子连成一线,即横线、竖线或斜线,且无被对手堵住的空位,从而获胜。
实现简单的五子棋游戏,需要有二维数组、函数调用等知识。本项目代码量大致为两三百行。
一、游戏规则

1.功能分析
五子棋的功能比较简单,只有黑棋落子和白棋落子,有些五子棋还有悔棋功能、显示胜率、记录走的步数等功能,但是初学时可以只实现简单的落子功能即可。
2.玩法分析
由黑棋先走,鼠标左击即可落子,然后轮到白棋回合,如此循环直到游戏结束。
3.胜负判定条件
五子棋的棋盘通常为15×15的格子,双方轮流在空位上放置自己的棋子,先连成五子的一方获胜。如果棋盘填满但没有五子连成一线,游戏结束为平局。
二、游戏实现思路
- 使用坐标输入代替鼠标点击,坐标应该符合人们使用习惯从1开始;(【鼠标左击】功能)
- 若有人胜利提示胜利方为谁,并结束游戏;
- 若无人胜利,且棋盘未满,提示继续;
- 若棋盘已满,提示平局;
- 实现清屏功能,每次落完子之后刷新屏幕;
- 可以加入进度条,在游戏开始时展示。
三、代码实现与函数封装
1.项目文件创建
将源代码分为五个文件,两个头文件(.h),用于声明函数,分别声明进度条和游戏函数;
三个源文件(.c),两个用于写函数体,实现进度条和游戏主体,一个为主函数,调用函数。

2.头文件说明
1.为了防止在项目中多次申明同一个头文件,需要加入防重复判断语句:
#pragma once
2.头文件中可以直接引用需要的标准库,然后在源文件中只需要引用自己写的头文件即可;
#include "game.h"
#include "ProBar.h"
3.函数封装
棋盘设置为15×15,且可更改方便之后的更改。
1)菜单实现
void Menu()
{printf("#########################\n");printf("### 1.Play 0.Exit ###\n");printf("#########################\n");printf("Please Select:> ");
}
2)进度条实现
'\r'使光标每次回到开头的位置,使用fflush函数刷新输出界面,并使用usleep函数进行延迟,达到很好的效果。
#define NUM 100void process_bar()
{char bar[NUM+1];memset(bar, '\0', sizeof(bar));const char* lable = "|/-\\";int i = 0;while(i <= NUM){printf("Load...[%-100s][%-3d%%][%c]\r", bar, i, lable[i%4]);fflush(stdout);bar[i++] = '#';usleep(10000);}printf("\n");
}
3)main函数实现
使用quit变量来控制循环的退出;
Game函数实现游戏的主体。
int main()
{int quit = 0;int select = 0;while(!quit){Menu();scanf("%d", &select);switch(select){case 1:process_bar();Game();break;case 0:quit = 1;printf("Exit Success!\n");break;defualt :printf("Enter Error, Try Again!\n");break;}}return 0;
}
4)Game函数
- 建立棋盘
- 使用memset函数初始化棋盘;
- do...while执行游戏运行;
- IsOver函数判断游戏是否结束;
- PlayerMove函数玩家走一步棋;
- Showboard 打印出棋盘;
- switch...case 显示出游戏结果。
#define ROW 20
#define COL 20#define PLAYER1 1
#define PLAYER2 2#define NEXT 0
#define PLAYER1_WIN 1
#define PLAYER2_WIN 2
#define DRAW 3void Game()
{int board[ROW][COL];memset(board, '\0', sizeof(board));int result = NEXT;do{ShowBoard(board, ROW, COL);PlayerMove(board, ROW, COL, PLAYER1);result = IsOver(board, ROW, COL);if(NEXT != result){break;}ShowBoard(board, ROW, COL);PlayerMove(board, ROW, COL, PLAYER2);result = IsOver(board, ROW, COL);if(NEXT != result){break;}}while(1);//p1 win , p2 win, drawShowBoard(board, ROW, COL);switch(result){case PLAYER1_WIN:printf("congratulate Player1, you win!\n");break;case PLAYER2_WIN:printf("congratulate player2, you win!\n");break;case DRAW:printf("draw!\n");break;default:break;}
}
5)ShowBoard函数实现
刷新屏幕
printf("\033c");
void ShowBoard(int board[ROW][COL], int row, int col)
{//clear screen//printf("\e[1;1H\e[2J")"]]");//printf(" ");printf("\033c");printf("\n\n ");for(int i=0; i<col; i++){printf("%3d", i+1);}printf("\n");for(int i=0; i<row; i++){printf("%2d ", i+1);for(int j=0; j<col; j++){if(board[i][j] == 0){printf(" . ");}else if(board[i][j] == PLAYER1){printf(" x ");}else{printf(" o ");}}printf("\n");}
}
6)PlayerMove函数实现
int x = 0;
int y = 0;
设置为全局变量,由用户输入,从1开始
int x = 0;
int y = 0;void PlayerMove(int board[ROW][COL], int row, int col, int player)
{while(1){printf("\nPlayer[%d] Please Enter Your Pos:>", player);scanf("%d %d", &x, &y);//判断合法坐标if(x<1 || x> row || y<1 || y>col){printf("Pos is not right!\n");continue;}else if(board[x-1][y-1] != 0){printf("Pos is occpuied!\n");continue;}else{board[x-1][y-1] = player;//谁落子,就放置谁的数据break;}}
}
7)ChessCount函数实现
要判断是否继续下棋需要判断是否已经五子连珠,就需要数下的这个子周围有没有出现五子连珠,由于每次下棋都会判断一次,所以不会出现漏判的情况。
enum Dir
{LEFT,RIGHT,UP,DOWN,LEFT_UP,LEFT_DOWN,RIGHT_UP,RIGHT_DOWN
};int ChessCount(int board[ROW][COL], int row, int col, enum Dir d)
{int _x = x-1;int _y = y-1;int count = 0;while(1){switch(d){case LEFT:_y--;break;case RIGHT:_y++;break;case UP:_x--;break;case DOWN:_x++;break;case LEFT_UP:_x--;_y--;break;case LEFT_DOWN:_x++;_y--;break;case RIGHT_UP:_x--, _y++;break;case RIGHT_DOWN:_x++, _y++;break;default://Do nothingbreak;}if(_x<0 || _x>row-1 || _y<0 || _y > col-1){break;}if(board[x-1][y-1] == board[_x][_y]){count++;}else{break;}}return count;
}
8)IsOver函数实现

任何落子位置都有八个方向,所以判定五子连珠,本质是判定1,5方向之和,2,6方向之和,3,7方向之和,4,8方向 之和,其中任意一个出现相同的连续五个棋子,即游戏结束
int IsOver(int board[ROW][COL], int row, int col)
{//注意,每次统计的时候,都没有统计当前节点,需要单独+1int count1 = ChessCount(board, row, col, LEFT) +ChessCount(board, row, col, RIGHT) + 1;int count2 = ChessCount(board, row, col, UP) +ChessCount(board, row, col, DOWN) + 1;int count3 = ChessCount(board, row, col, LEFT_UP) +ChessCount(board, row, col, RIGHT_DOWN) + 1;int count4 = ChessCount(board, row, col, LEFT_DOWN) +ChessCount(board, row, col, RIGHT_UP) + 1;if(count1 >= 5 || count2>=5 || count3>=5 || count4>=5){//谁赢返回谁//return board[x-1][y-1];if(board[x-1][y-1] == PLAYER1){return PLAYER1_WIN;}else{return PLAYER2_WIN;}}for(int i=0; i<row; i++){for(int j=0; j<col; j++){if(board[i][j] == 0){//棋盘未满,返回继续return NEXT;}}}//棋盘已满且没人赢,返回平局return DRAW;
}
四、源码分享
1.main.c
#include "game.h"
#include "ProBar.h"int main()
{int quit = 0;int select = 0;while(!quit){Menu();scanf("%d", &select);switch(select){case 1:process_bar();Game();break;case 0:quit = 1;printf("Exit Success!\n");break;defualt :printf("Enter Error, Try Again!\n");break;}}return 0;
}
2.ProBar.h
#pragma once #include <stdio.h>
#include <unistd.h>
#include <string.h>#define NUM 100void process_bar();
3.probar.c
#include "ProBar.h"void process_bar()
{char bar[NUM+1];memset(bar, '\0', sizeof(bar));const char* lable = "|/-\\";int i = 0;while(i <= NUM){printf("Load...[%-100s][%-3d%%][%c]\r", bar, i, lable[i%4]);fflush(stdout);bar[i++] = '#';usleep(10000);}printf("\n");
}
4.game.h
#include <string.h>
#include <stdlib.h>#define ROW 20
#define COL 20#define PLAYER1 1
#define PLAYER2 2#define NEXT 0
#define PLAYER1_WIN 1
#define PLAYER2_WIN 2
#define DRAW 3enum Dir
{LEFT,RIGHT,UP,DOWN,LEFT_UP,LEFT_DOWN,RIGHT_UP,RIGHT_DOWN
};void Menu();
void Game();int IsOver(int board[ROW][COL], int row, int col);
void ShowBoard(int board[ROW][COL], int row, int col);
int ChessCount(int board[ROW][COL], int row, int col, enum Dir d);
void Playermove(int board[ROW][COL], int row, int col, int player);
5.game.c
#include "game.h"
#include "ProBar.h"
int x = 0;
int y = 0;void Menu()
{printf("#########################\n");printf("### 1.Play 0.Exit ###\n");printf("#########################\n");printf("Please Select:> ");
}//four possbilities:
//NEXT: continue
//1: 1 win
//2: 2 win
//3: draw
int IsOver(int board[ROW][COL], int row, int col)
{//import && hard//wu zi lian zhuint count1 = ChessCount(board, row, col, LEFT) +ChessCount(board, row, col, RIGHT) + 1;int count2 = ChessCount(board, row, col, UP) +ChessCount(board, row, col, DOWN) + 1;int count3 = ChessCount(board, row, col, LEFT_UP) +ChessCount(board, row, col, RIGHT_DOWN) + 1;int count4 = ChessCount(board, row, col, LEFT_DOWN) +ChessCount(board, row, col, RIGHT_UP) + 1;if(count1 >= 5 || count2>=5 || count3>=5 || count4>=5){//return board[x-1][y-1];if(board[x-1][y-1] == PLAYER1){return PLAYER1_WIN;}else{return PLAYER2_WIN;}}for(int i=0; i<row; i++){for(int j=0; j<col; j++){if(board[i][j] == 0){return NEXT;}}}return DRAW;
}int ChessCount(int board[ROW][COL], int row, int col, enum Dir d)
{int _x = x-1;int _y = y-1;int count = 0;while(1){switch(d){case LEFT:_y--;break;case RIGHT:_y++;break;case UP:_x--;break;case DOWN:_x++;break;case LEFT_UP:_x--;_y--;break;case LEFT_DOWN:_x++;_y--;break;case RIGHT_UP:_x--, _y++;break;case RIGHT_DOWN:_x++, _y++;break;default://Do nothingbreak;}if(_x<0 || _x>row-1 || _y<0 || _y > col-1){break;}if(board[x-1][y-1] == board[_x][_y]){count++;}else{break;}}return count;
}void ShowBoard(int board[ROW][COL], int row, int col)
{//clear screen//printf("\e[1;1H\e[2J")"]]");//printf(" ");printf("\033c");printf("\n\n ");for(int i=0; i<col; i++){printf("%3d", i+1);}printf("\n");for(int i=0; i<row; i++){printf("%2d ", i+1);for(int j=0; j<col; j++){if(board[i][j] == 0){printf(" . ");}else if(board[i][j] == PLAYER1){printf(" x ");}else{printf(" o ");}}printf("\n");}
}void PlayerMove(int board[ROW][COL], int row, int col, int player)
{while(1){printf("\nPlayer[%d] Please Enter Your Pos:>", player);scanf("%d %d", &x, &y);if(x<1 || x> row || y<1 || y>col){printf("Pos is not right!\n");continue;}else if(board[x-1][y-1] != 0){printf("Pos is occpuied!\n");continue;}else{board[x-1][y-1] = player;break;}}
}void Game()
{int board[ROW][COL];memset(board, '\0', sizeof(board));int result = NEXT;do{ShowBoard(board, ROW, COL);PlayerMove(board, ROW, COL, PLAYER1);result = IsOver(board, ROW, COL);if(NEXT != result){break;}ShowBoard(board, ROW, COL);PlayerMove(board, ROW, COL, PLAYER2);result = IsOver(board, ROW, COL);if(NEXT != result){break;}}while(1);//p1 win , p2 win, drawShowBoard(board, ROW, COL);switch(result){case PLAYER1_WIN:printf("congratulate Player1, you win!\n");break;case PLAYER2_WIN:printf("congratulate player2, you win!\n");break;case DRAW:printf("draw!\n");break;default:break;}
}
总结
实际上这个版本还是一个非常简易的版本,在之后学习到别的模块之后可以对这个项目再进行改进,比如可以尝试以下功能:
- 人机对战
- 功能扩展:颜色提示,步数记录,先手随机交换等
- 网络版本
码云Gitee项目链接:GoBangGame · Kevin Ray/LinuxPractice - 码云 - 开源中国 (gitee.com)
相关文章:
【C语言小项目】五子棋游戏
目录 前言 一、游戏规则 1.功能分析 2.玩法分析 3.胜负判定条件 二、游戏实现思路 三、代码实现与函数封装 1.项目文件创建 2.头文件说明 3.函数封装 1)菜单实现 2)进度条实现 3)main函数实现 4)Game函数 5࿰…...
基于Java语言的能源管理系统-水电气热油数据采集系统
基于Java语言的能源管理系统-水电气热油数据采集系统 介绍 适用于高能耗企业、建筑、工厂、园区的水、电、气、热、油、空压机等能源数据采集、分析、报表; 基于SpringCloud的能源管理系统-能源管理平台源码-能源在线监测平台-双碳平台源码-SpringCloud全家桶-能管…...
人工智能在肿瘤亚型分类领域的研究进展|顶刊速递·24-08-13
小罗碎碎念 文献日推主题:人工智能在肿瘤亚型分类领域的研究进展 昨天晚上在研究鼻咽癌的病理学诊断指南,看到了下面这段话的时候,我问了自己一个问题——通过AI识别出肿瘤亚型的根本目的是什么?可以衔接哪些具体的下游任务&#…...
Taro+Vue 创建微信小程序
TaroVue 创建微信小程序 一、全局安装 tarojs/cli二、项目初始化三、现在去启动项目吧 一、全局安装 tarojs/cli npm install -g tarojs/cli //安装 npm info tarojs/cli //查看安装信息 如果正常显示版本说明成功了,就直接跳到第二步吧官网说:…...
智能安全守护,寺庙安全用电解决方案
在四川省蓬溪县城北,高峰山以其千年的历史沉淀和独特的文化风貌,默默诉说着道教与佛教交融的传奇。然而,2017年5月31日凌晨的一声巨响,打破了这里的宁静,一场突如其来的大火,让这座承载着无数信徒信仰与梦想…...
加热系统加入达温即停和保温功能
加热系统加入达温即停和保温功能 引言 我们前一节, 是通过控制io口, 直接控制加热的开关, 但是这太简单粗暴了, 如果只是那样, 和普通的家居电器,是没区别的, 我们想要更智能一点, 就是像咱们自己家的热水壶那样, 水烧滚了(水温达到100摄氏度), 就自动停止了. 加热功能&a…...
C++_2_ inline内联函数 宏函数(2/3)
C推出了inline关键字,其目的是为了替代C语言中的宏函数。 我们先来回顾宏函数: 宏函数 现有个需求:要求你写一个Add(x,y)的宏函数。 正确的写法有一种,错误的写法倒是五花八门,我们先来“见不贤而自省也。” // …...
ROS执行多个节点报错(遥控运动及SLAM建图)
今天在实体机器人中同时执行多个ROS节点: roslaunch rei_robot_base oryxbot_base.launchroslaunch robot_joy robot_joy.launchroslaunch oryxbot_slam oryxbot_slam_local.launch结果全部报错退出了 现在换一种执行方式: roscoreroslaunch rei_robot_base oryxbot_base.la…...
Spring Boot项目中实现文件的上传、下载和预览功能
在Spring Boot项目中实现文件的上传、下载和预览功能,可以通过使用Spring MVC的MultipartFile接口来处理文件上传,并使用HttpServletResponse或Resource来实现文件下载和预览。下面是如何实现这些功能的完整示例。 1. 引入依赖 确保在pom.xml中引入了S…...
【JAVA入门】Day21 - 时间类
【JAVA入门】Day21 - 时间类 文章目录 【JAVA入门】Day21 - 时间类一、JDK7前的时间相关类1.1 Date1.2 SimpleDateFormat1.3 Calendar 二、JDK8新增的时间相关类2.1 Date 相关类2.1.1 ZoneId 时区2.1.2 Instant 时间戳2.1.3 ZoneDateTime 带时区的时间 2.2 DateTimeFormat 相关…...
SQL server数据库备份和还原
新手小白都懂的sql server数据库备份和还原 一、备份 1.打开sql server数据库找到 2.展开找到对应的数据库文件 鼠标右击—任务–备份 3.复制名称 4.复制完点击添加 5.点击添加完之后再次点击查找路径 6.分别两个路径 原路径和新路径 (新路径是找到原路径新建了一…...
B站搜索建库架构优化实践
前言 搜索是B站的重要基础功能,需要对包括视频、评论、图文等海量的站内优质资源建立索引,处理来自用户每日数亿的检索请求。离线索引数据的正确、高效产出是搜索业务的基础。我们在这里分享搜索离线架构整体的改造实践:从周期长,…...
XSS反射实战
目录 1.XSS向量编码 2.xss靶场训练(easy) 2.1第一关 2.2第二关 方法一 方法二 2.3第三关 2.4第四关 2.5第五关 2.6第六关 2.7第七关 第一种方法: 第二种方法: 第三个方法: 2.8第八关 1.XSS向量编码 &…...
远程消息传递的艺术:NSDistantObject在Objective-C中的妙用
标题:远程消息传递的艺术:NSDistantObject在Objective-C中的妙用 引言 在Objective-C的丰富生态中,NSDistantObject扮演着至关重要的角色,特别是在处理分布式系统中的远程消息传递。它允许对象之间跨越不同地址空间进行通信&…...
指向派生类的基类指针、强转为 void* 再转为基类指针、此时调用虚函数会发生什么?
指向派生类的基类指针、强转为 void* 再转为基类指针、此时调用虚函数会发生什么? 1、无论指针类型怎么转,类对象内存没有发生任何变化,还是vfptr指向虚函数表,下面是成员变量,这在编译阶段就已经确定好了;…...
操作系统(Linux实战)-进程创建、同步与锁、通信、调度算法-学习笔记
1. 进程的基础概念 1.1 进程是什么? 定义: 进程是操作系统管理的一个程序实例。它包含程序代码及其当前活动的状态。每个进程有自己的内存地址空间,拥有独立的栈、堆、全局变量等。操作系统通过进程来分配资源(如 CPU 时间、内…...
react的setState中为什么不能用++?
背景: 在使用react的过程中产生了一些困惑,handleClick函数的功能是记录点击次数,handleClick函数被绑定到按钮中,每点击一次将通过this.state.counter将累计的点击次数显示在页面上 困惑: 为什么不能直接写prevStat…...
2.2算法的时间复杂度与空间复杂度——经典OJ
本博客的OJ标题均已插入超链接,点击可直接跳转~ 一、消失的数字 1、题目描述 数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗? 2、题目分析 (1)numsS…...
【CentOS 】DHCP 更改为静态 IP 地址并且遇到无法联网
文章目录 引言解决方式标题1. **编辑网络配置文件**:标题2. **确保配置文件包含以下内容**:特别注意 标题3. **重启网络服务**:标题4. **检查配置是否生效**:标题5. **测试网络连接**:标题6. **检查路由表**࿱…...
Linux 操作系统 --- 信号
序言 在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时…...
React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
shell脚本--常见案例
1、自动备份文件或目录 2、批量重命名文件 3、查找并删除指定名称的文件: 4、批量删除文件 5、查找并替换文件内容 6、批量创建文件 7、创建文件夹并移动文件 8、在文件夹中查找文件...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
css的定位(position)详解:相对定位 绝对定位 固定定位
在 CSS 中,元素的定位通过 position 属性控制,共有 5 种定位模式:static(静态定位)、relative(相对定位)、absolute(绝对定位)、fixed(固定定位)和…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
动态 Web 开发技术入门篇
一、HTTP 协议核心 1.1 HTTP 基础 协议全称 :HyperText Transfer Protocol(超文本传输协议) 默认端口 :HTTP 使用 80 端口,HTTPS 使用 443 端口。 请求方法 : GET :用于获取资源,…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...
