C++项目:在线五子棋对战(网页版)

项目介绍

本项⽬主要实现⼀个⽹⻚版的五⼦棋对战游戏,其主要⽀持以下核⼼功能:

• 用户管理:实现用户注册,用户登录、获取用户信息、用户天梯分数记录、用户比赛场次记录等。
• 匹配对战:实现两个玩家在网页端根据天梯分数匹配游戏对⼿,并进行五子棋游戏对战的功能。
• 聊天功能:实现两个玩家在下棋的同时可以进⾏实时聊天的功能。

程序截图

开发环境

• Linux (Centos-7.6)

• VSCode/Vim
• g++/gdb
• Makefile

核心技术

• HTTP/WebSocket
• Websocket++
• JsonCpp
• Mysql
• C++11
• BlockQueue
• HTML/CSS/JS/AJAX

项目大流程

• 环境搭建(在Linux环境下安装需要用到的的工具以及第三方库)

• 框架设计

• 前置知识的了解

• 模块开发

配置开发环境

项目框架

项目期望:用户访问服务器获取注册页面,通过注册页面注册账号,注册成功后在登录页面进行登录。登录成功后进入游戏大厅,在游戏大厅中进行匹配对战,匹配成功,将进入游戏房间内与对手进行实时对战和实时聊天。

用户访问服务器获取的注册页面、登录页面、游戏大厅页面和游戏房间页面,属于静态资源请求。进行注册请求、登录请求、进入游戏大厅后展示个人信息的个人信息请求、匹配对战请求和下棋聊天请求属于动态功能请求。

服务器流程图:

模块解析

在项目中,需要用到6个模块,分别是:

数据管理模块:基于mysql数据库进行数据管理以及封装数据管理模块实现数据库访问。

在数据管理模块中,需要实现的功能有:注册新用户功能、登录验证功能、通过用户名获取用户信息功能、通过用户id获取用户信息功能,以及对战胜利和失败后,对数据的更新功能。

网络服务器模块:基于websocketpp库搭建websocket服务器,实现与客户端网络通信。

早网络服务器模块中,websocketpp支持http协议和websocket协议,需要实现的是http请求处理回调函数和websocket请求处理回调函数。其中,HTTP请求的处理回调函数包含了静态资源请求处理、用户注册请求处理、用户登录请求处理和用户信息请求处理。websocket请求处理回调函数包含游戏大厅、游戏房间等长连接的请求处理。

session管理模块:封装session管理,实现http客户端通信状态的维护及身份识别。

session管理模块是用于在浏览器中保存用户的通信状态和身份识别的,当用户在注册或登录后,进入了游戏大厅或游戏房间,那么将会永久保存其Cookie,当用户断开连接后,在一定的时间内,他的Cookie就会被销毁,在登录时需要重新输入账号密码。

在线用户管理模块:对于进入游戏大厅&游戏房间的长连接通信进行管理,实现随时能够获取客户端连接进行消息的主动推送。

在线用户管理模块的作用是将用户id与游戏大厅或游戏房间连接起来,在建立了websocket长连接后,将玩家加入到游戏大厅或游戏房间。当游戏结束,将玩家从游戏房间移除,当玩家退出客户端后,websocket连接断开,将玩家从游戏大厅移除。除此之外,还需要实现判断用户是否在线,即在游戏大厅中或游戏房间中,还需要通过玩家用户id去获取游戏大厅/游戏房间管理对应的通信连接。

游戏房间管理:对于同一个房间中的用户及动作进行处理(对战匹配,下棋,聊天,退出)。

在游戏房间中,游戏房间包含了房间id,玩家数量,房间状态、黑棋白棋玩家的id,以及棋盘,在线用户管理和数据模块管理的指针等字段。在游戏房间中,需要实现的是下棋的动作、处理下棋动作、处理聊天动作和处理玩家退出房间的动作,以及将动作处理广播给房间的所有玩家的方法。

游戏房间管理是需要实现的是对房间的管理,因此需要一个房间的计数器、需要实现的是创建一个游戏房间,通过房间id获取房间信息,通过用户id获取所在的房间的信息,删除房间等方法。

对战匹配管理:将所有玩家根据分数进行等级划分,进行不同等级的对战匹配。

匹配对战分有三个段位:普通、高手和大神三种段位,分别使用三个匹配队列进行玩家的匹配队列的进入等待,并且使用三个线程,异步高效地处理三种玩家的匹配对战。

对于匹配队列来说,不是使用队列,而是使用双向链表来作为匹配队列。匹配队列需要的功能是:出队入队、阻塞线程、移除指定元素等功能。

对于匹配队列的管理,需要实现将玩家进行入队,出队等操作。

将6个模块整合起来,在服务器中进行业务处理:通过网络通信获取到客户端的请求,提供不同的业务处理。

前置知识的学习:

1.websocketpp的学习和使用。

本项目中,使用websocketpp来搭建服务器,因为websocketpp同时支持http和websocket。在本项目中,HTTP用于注册、登录等服务请求中提供短链接服务。而websocket用于在游戏大厅或游戏房间中,提供长连接,并且服务器主动发送消息给客户端的服务。下面链接是关于websocket的介绍以及使用websocketpp搭建简单服务器的框架:

websocket协议

我在做项目时的难点:①HTTP请求响应和websocket请求响应的区别。②服务器搭建流程。③为什么连接句柄是使用lib::weak_ptr管理起来,而不是使用lib::shared_ptr来管理。

解决:

①TTP请求响应和websocket请求响应的区别:

HTTP请求回调处理函数主要是处理来自客户端的HTTP请求,它从连接对象中获取HTTP请求的正文,并通过请求对象获取URI和方法等信息,然后根据不同的方法和URI来进行相应的处理,最后构建HTTP响应对象并发送回客户端。HTTP是一种无状态协议,每个请求都是独立的。

WebSocket消息处理回调函数主要是处理来自客户端的WebSocket消息,它从连接对象中获取WebSocket消息的内容,并进行相应的处理逻辑。不像HTTP请求那样需要获取URI和方法等信息,WebSocket是一种双向通信协议,服务器和客户端可以在持久连接上进行实时双向通信。这个回调函数通过使用连接对象的 send 方法直接将响应消息发送回客户端。

服务器搭建流程:先实例化出websocketpp的server类对象,通过server类对象设置日志等级、调度器、四种处理回调函数、进入监听状态、获取客户端新连接,最后启动服务器。

③为什么连接句柄是使用lib::weak_ptr管理起来,而不是使用lib::shared_ptr来管理。

hdl是wssrv.start_accept()创建出来的连接对象的引用,他们两者会有互相引用的关系,如果使用了lib::weak_ptr来管理hdl,就不会发生内存泄漏。

MySQLClient库

JsonCpp

模块开发

数据库代码

数据库设计这边,玩家的信息包含了玩家的用户id、用户名、用户密码、天梯分数、排位总场次和胜场总场次。其中,用户id作为主键,并且是自增长的,而用户名和用户密码不能为空,且用户名唯一。根据这个需求,写出以下代码:

drop database if exists online_gobang;
create database if not exists online_gobang;
use online_gobang;
create table if not exists user(
    id int primary key auto_increment comment '用户id',
    username varchar(32) unique key not null comment '用户名',
    password varchar(128) not null comment '用户密码',
    score int comment '分数',
    total_count int comment '总场次',
    win_count int comment '胜利场次'
);

日志打印

#ifndef _M_LOGGER_H_
#define _M_LOGGER_H_
#include<stdio.h>
#include<time.h>

//正常
#define INF 0
//调试信息
#define DBG 1
//错误信息
#define ERR 2

//strftime:将时间转换为指定格式的字符串
#define LOG(level,format,...) do{\
    time_t t = time(NULL);\
    struct tm *lt = localtime(&t);\
    char buf[32];\
    strftime(buf,21,"%H:%M:%S",lt);\
    fprintf(stdout,"[%s %s:%d] " format "\n",buf,__FILE__,__LINE__,##__VA_ARGS__);\
}while(0)

#define ILOG(format,...) LOG(INF,format,##__VA_ARGS__);
#define DLOG(format,...) LOG(DBG,format,##__VA_ARGS__);
#define ELOG(format,...) LOG(ERR,format,##__VA_ARGS__);

工具类模块开发

工具类模块开发代码

数据管理模块开发

数据管理模块开发代码

在测试代码的时候,发现插入的数据长度太长,也就是密码在被加密之后,长度超过了我原本设置的password varchar(32)。因此,我需要将其改成varchar(128),并且重新导入:

[wjmhlh@VM-12-9-centos myspace]$ mysql -uroot -p < online_gobang.sql   --重新导入
Enter password: 
[wjmhlh@VM-12-9-centos myspace]$ mysql -uroot -p
Enter password: 


Database changed
mysql> desc user;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| username    | varchar(32)  | NO   | UNI | NULL    |                |
| password    | varchar(128) | NO   |     | NULL    |                |
| score       | int(11)      | YES  |     | NULL    |                |
| total_count | int(11)      | YES  |     | NULL    |                |
| win_count   | int(11)      | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+
6 rows in set (0.00 sec)


mysql> select * from user;
+----+----------+-------------------------------------------+-------+-------------+-----------+
| id | username | password                                  | score | total_count | win_count |
+----+----------+-------------------------------------------+-------+-------------+-----------+
|  1 | xiaoming | *E56A114692FE0DE073F9A1DD68A00EEB9703F3F1 |  1000 |           0 |         0 |
+----+----------+-------------------------------------------+-------+-------------+-----------+
1 row in set (0.00 sec)

在线用户管理模块开发

在线用户管理模块开发代码

在实现代码中,需要注意的是,当websocket的长连接断开后,我在移除游戏大厅或游戏房间的在线用户管理的uid时,而由于是使用unordered_map作为容器,因此与之对应的通信连接conn就会失去一个映射关系,而通信连接使用了uinque_ptr进行管理,计数器直接减为0,这个通信连接就会自动销毁。

房间管理模块开发

房间管理模块开发代码

session管理模块开发

session管理模块开发

游戏对战匹配模块开发

匹配对战模块开发代码

服务器网络通信模块开发

服务器网络通信模块开发​​​​​​​

项目总结

1. 为什么做这个项目

为什么做这个C++五子棋对战网页版的项目,我总结了三点:

①我学习了网络编程,比如HTTP、socket编程等,还没有通过项目实践过,开发经验不足,因此我需要做一个关于网络通信连接的项目来加深我对网络编程的理解和使用。

②我是学习C++的一名计算机专业的学生,对自己掌握的C++的程度,需要有项目的实践操作去检验自己的学习成果。

③在之前学习计算机技术,实践都是通过从一些做题网站中,去做题目来检验自己对知识的掌握,以及写代码的能力,但是却没有过去写一个项目来检验自己的代码逻辑和写代码的能力的,而这个项目的开发,能够很好地检验我自己的代码逻辑和写代码的能力。


2. 项目中都用到了那些技术

在项目中,主要运用到了websocket协议和HTTP协议,以及C++11中的一些新特性,比如包装器,bing方法,互斥锁、智能指针等等,还有就是使用到了STL,比如vector、list、unordered_map等,以及是g++/gcc,makefile,vim/vscode等。


3. 讲一下项目都有那些功能,大概是怎么实现的

在线五子棋对战网页版,主要的功能有:让用户通过浏览器访问服务器,从而实现用户注册,用户登录,对战匹配,实时对战和实时聊天功能。

实现的大概思路是:

实现了6个模块,第一个模块是数据管理模块,这个模块是基于MySQL数据库进行数据管理,并且封装了MySQL的C语言接口,来进行数据管理。第二个模块是在线用户管理模块,这个模块对于进入了的游戏大厅和游戏房间的长连接通信进行管理,通过用户的uid与相对于的客户端的通信连接建立起映射关系,服务器可以实现随时获取客户端通信连接进行消息的主动推送。第三个模块是房间管理模块,在这个模块里面,先是实现了房间类,在房间类中,实现了下棋、聊天等动作,而再实现了一个房间管理的类,通过房间的管理,可以进行房间的创建、销毁、通过用户的id获取房间信息,通过房间id获取房间信息等功能。第四个模块是session模块,这个模块封装了session的管理,实现了HTTP通信连接中客户端通信状态的维护和登录或进入游戏大厅或游戏房间时进行身份识别。第五个模块是游戏对战匹配模块,在这个模块里面是将所有玩家根据分数吗,进行了档次的划分,使用多线程,分别对同档次的玩家进行不同的对战匹配。第六个模块是网络服务器的模块开发,在这个模块里面,是基于了websocketpp来搭建了服务器,实现了与客户端进行通信的功能。


4. 做的时候遇到过什么问题,当时是怎么想的,最后是怎么解决的

在做项目的过程中,遇到了不少的技术问题。

①比如在使用websocketpp协议的时候,一开始的时候没有掌握好HTTP协议和websocket协议的使用,比如说在接受请求和发送响应,它们是有区别的。一开始以为在响应的时候,需要获取uri和方法,然后根据uri和方法将那些响应的。然后当时在网上查了文档,别人写的文章,还问了一些类似chargpt的ai,最后才发现,原来websocket不需要获取请求中的uri,可以直接通过send方法给客户端做出响应。

②一开始在设计user表的时候,我把密码的长度设置了32个字符长度。开始的时候,我给用户注册密码都是6位,我没弄清楚为什么在报错的时候,提示我是密码长度超出了设置的长度。于是我在MySQL的客户端上,重新查看了describe user命令查看了表结构,发现是32位,然后我于是通过SHOW VARIABLES LIKE 'validate_password%'命令,去查看密码等级的设置,并没有出现设置不匹配的原因,后来我查了很多资料,问了老师,才知道,是在密码加密之后,密码长度会变长,于是我就设置成128位。


5. 项目中最难的一个知识点

我认为,在这个项目中最难的一个知识点是对于如何去使用websocketpp去搭建服务器这个点上。因为在做这个项目之前,我都只用过HTTP去做过一个简单的服务器,没有拓展到去学习websocket协议,从而使用websocket去搭建一个拥有HTTP协议和websocket协议的服务器。


6. 有想过怎么扩展吗

想过给下棋玩家设置一个时间限制,在规定时间内没有下棋动作,那么就直接轮到对方下棋。

还有就是增加一个AI选手,可以让玩家与AI进行对战。

项目整体代码链接

代码链接

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
五子棋是一种非常古老的游戏,也是一种非常常见的人机对战游戏。下面是一个简单的C++五子棋人机对战项目实现的示例: 1. 定义棋盘类 首先,我们需要定义一个棋盘类。在这个类中,我们需要定义棋盘的大小、棋子的状态(黑、白、空)、以及各种棋子的位置等信息。 ```cpp // 定义棋盘类 class ChessBoard { public: ChessBoard(int boardSize); // 构造函数,初始化棋盘 ~ChessBoard(); // 析构函数,释放棋盘内存 void reset(); // 重置棋盘 bool setChessman(int row, int col, ChessmanType type); // 在指定位置下子 ChessmanType getChessman(int row, int col) const; // 获取指定位置的棋子状态 bool isFull() const; // 判断棋盘是否已满 private: int m_boardSize; // 棋盘大小 ChessmanType** m_board; // 棋盘数组,存储各个位置的棋子状态 }; ``` 2. 定义AI类 接下来,我们需要定义一个AI类,用于计算机与人类玩家交互并生成下一步的走法。在这个类中,我们需要实现一个算法来判断哪一步最有可能赢得比赛。 ```cpp // 定义AI类 class AI { public: AI(ChessBoard* board, ChessmanType aiType); // 构造函数,初始化AI ~AI(); // 析构函数,释放AI内存 void getNextStep(int& row, int& col); // 获取下一步走法 private: ChessmanType m_aiType; // AI的棋子类型(黑或白) ChessBoard* m_board; // 棋盘 }; ``` 3. 定义游戏类 最后,我们需要定义一个游戏类,用于处理游戏的流程和逻辑。在这个类中,我们需要实现人机交互、判断胜负、判断游戏是否结束等功能。 ```cpp // 定义游戏类 class Game { public: Game(int boardSize); // 构造函数,初始化游戏 ~Game(); // 析构函数,释放游戏内存 void start(); // 开始游戏 private: ChessBoard* m_board; // 棋盘 AI* m_ai; // AI bool m_isPlayerTurn; // 当前是否是玩家回合 bool m_isGameOver; // 游戏是否结束 }; ``` 以上是一个简单的C++五子棋人机对战项目的实现示例。当然,这只是一个简单的示例,实际的实现可能会更加复杂,需要根据具体的需求进行调整和修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山雾隐藏的黄昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值