目录

  • 1. 项目开发背景
  • 2.项目需求 & 开发环境
  • 3.项目设计 & 项目难点
  • 4.功能实现 & 难点解决
    • 4.1. 用户登录 / 注册
    • 4.2. 项目难点解决
    • 4.2.1 难点一:当用户点击开始匹配按钮时,创建房间并为用户分配相应的对手。
    • 4.2.2 难点二:在对战过程中,用户只能下一种颜色的棋子(黑棋/白棋),并且只能在属于自己的回合进行下棋。
    • 4.2.3 难点三:在对战过程中,对手下的棋子的位置能够及时同步在自己的棋盘上。

项目源码:https://github.com/mxzw/Project/tree/main/webgobang-project/src

1. 项目开发背景

小时候,闲来无事最喜欢和别人玩的游戏之一就是五子棋了,它规则简单,但却玩法多变,直到现在我还时不时的在手机上和别人在线对战。最近自己学习Linux方面的相关知识,学习了多线程和Socket编程,并对网络这块的数据流向有了基本的了解,加之刚好找到了一份纯前端的单机五子棋的源码,于是就想尝试能否在该前端的基础上,加上后端,使其变成联机版的网络在线对战五子棋。

由于本人对前端学习并不深入,所有前端的代码都是基于原来现有的前端代码进行的修改。

项目运行截图

用户登录:

用户注册:

下棋页面:

胜利页面:

2.项目需求 & 开发环境

项目需求

  • 支持用户登录、用户注册功能
  • 支持在线匹配对战功能
  • 支持点击重开按钮重新进行匹配功能
  • 支持对局信息持久化功能

开发环境

gcc/g++:7.3.1版本,使用C++语言进行开发,并采用MySQL数据库进行相应数据的存储。

项目所用到的相关知识点

多线程,互斥锁和条件变量,httplib,JSON,AJAX,MySQL数据库

3.项目设计 & 项目难点

项目设计

首先,本项目大体的数据流向是前端通过AJAX方法将JSON格式的数据传送到后端,后端通过HttpLib库中的方法获取到前端当中的JSON数据,然后对该数据进行相应逻辑的处理后,将结果返回给前端,前端根据结果显示相应的内容。

项目架构图如下:

项目难点:

  • 难点1: 当用户点击开始匹配按钮时,创建房间并为用户分配相应的对手。
  • 难点2:在对战过程中,用户只能下一种颜色的棋子(黑棋/白棋),并且只能在属于自己的回合进行下棋。
  • 难点3:在对战过程中,对手下的棋子的位置能够及时的同步在自己的棋盘上。

4.功能实现 & 难点解决

由项目设计,我们可以得到本项目大概的框架

  • HTTP模块:httplib.h
  • 用户管理模块房间管理模块:room_player.hpp
  • 会话模块:Session.hpp
  • 数据库管理模块:ManagerDB.hpp
  • 工具模块:tools.hpp
  • 项目驱动模块:webgobang.hpp、webgobang.cpp

4.1. 用户登录 / 注册

当用户在网页上点击登录按钮的时候,前端会对用户输入的邮箱和密码进行获取,将数据转为JSON格式(序列化),并通过AJAX,采用POST的http请求方法将该数据发送到后端去,后端校验该数据之后,将其持久化到数据库中,并且根据输入的邮箱和密码生成相应的MD5码作为当前用户的cookie信息返回回去。

登录页面后端代码如下:

http_svr_.Post("/login", [=](const Request& req, Response& res) {//1.校验用户(登录页面)所提交的邮箱和密码(与数据库进行比较)cout << req.body << endl;Json::Reader r;Json::Value v;   //存储string :string 的键值对 r.parse(req.body, v); // 用于将请求正文中的内容反序列化(从连续的二进制转为对应键值对)//cout <<"v[\"email\"]" <<v["email"] << endl; //cout <<"v[\"password\"]" <<v["password"] << endl; //// 1.1 同数据库进行校验并得到对应user_idint user_id = this->db_svr_->CheckUserExist(v);//1.2 为了防止用户不经过登录,直接访问主页面,我们需要在这里加上一个session会话//      设置MD5码,作为会话id 进行http响应string tmp = "";if (user_id > 0){Session sess(v, user_id);string session_id = sess.GetSessionID();//回复的会话格式为JSESSIONID = xxxxxtmp += "JSESSIONID=" + session_id;all_sess_->SetSessionInfo(session_id, sess);//将该用户放到用户管理类中进行管理this->pm_->insertPlayer2Map(user_id);}//2.组织http响应格式使用校验的结果进行响应(使用Json对其进行响应,因为ajax中接收的数据类型为json)Json::Value res_value;// 在前端ajax请求中,要求接收的是一个Json对象,并且该对象中含有status的键值对res_value["status"] = user_id >= 0 ? true : false;//将该Json-Value对象序列化 并进行响应res.body = Serializa(res_value);res.set_header("Set-Cookie", tmp.c_str());res.set_header("content-Type", "application/json");});

生成MD5码

在头文件#include<openssl/md5.h>中包含着生成MD5码的方法。

/*
* SumMd5 : 生成对应的MD5码(初始化session_id_)
*  即将real_str_中的内容调用MD5_xxx函数生成对应的md5码,存入session_id中
*  成功:true
*  失败:false
*  int MD5_Init(MD5_CTX *c);初始化MD5码
*  int MD5_Update(MD5_CTX *c, const void *data, unsigned long len);
*                    通过传入的字符串更新MD5码
*  int MD5_Final(unsigned char *md, MD5_CTX *c); 获取生成的MD5码
*/
bool SumMd5()
{MD5_CTX c;//1.初始化MD5码MD5_Init(&c);//2.更新和获取MD5码if (MD5_Update(&c, real_str_.c_str(), real_str_.size()) != 1){return false;}unsigned char md5[16] = { 0 };if (MD5_Final(md5, &c) != 1){return false;}//3.将生成的MD5码按照16进制的格式存入会话id中char tmp[3] = { 0 };char buf[32] = { 0 };for (int i = 0; i < 16; ++i){snprintf(tmp, sizeof(tmp) - 1, "%02x", md5[i]);//cout << tmp;strncat(buf, tmp, 2);}session_id_ = buf;return true;
}

用户注册的大体逻辑也一样,这里则不再进行过多的解释。

4.2. 项目难点解决

4.2.1 难点一:当用户点击开始匹配按钮时,创建房间并为用户分配相应的对手。

解决:

这里采用vector作为匹配池,创建一个线程用来从匹配池中获取匹配玩家进行匹配,并且引入玩家状态(ONLINE、MATCHING、PLAYING)作为区分,当玩家点击开始匹配按钮时,玩家状态变为MATCHING,并且将其加入匹配池当中,唤醒匹配线程进行匹配,如果匹配线程发现当前匹配玩家数量 < 2,则不进行匹配,如果发送当前匹配玩家数量为奇数,则匹配池中最后一个玩家不进行匹配,等待下一次匹配,其余玩家两两配对。

匹配线程所做的事情:当从匹配池中获取到两个玩家信息后,则为这两名玩家创建一个房间,并将这两名玩家的状态变为PALYING状态,为其分配创建出的房间号和对应所执的黑棋/白棋。

匹配线程代码如下:

// 匹配线程入口函数
static void* MatchServer(void* arg)
{pthread_detach(pthread_self());WebGoBang* wg = (WebGoBang*)arg;while (1){pthread_mutex_lock(&wg->vec_lock_);//从匹配池中进行匹配//要操作的是vector,所以首先要加锁,//然后还要判断当前正在匹配玩家的数量,如果小于2,则放入等待队列中 while (wg->match_pool_num_ < 2){pthread_cond_wait(&wg->vec_cond_, &wg->vec_lock_);}//匹配池中的人数一定大于等于2// 奇数:一定有一个玩家要轮空// 偶数:两两进行匹配vector<int>& iv = wg->match_pool_;size_t size = iv.size();int last_id = -1;if (size % 2){//人数是奇数//默认让最后一个人轮空last_id = iv[size - 1];size -= 1;}for (int i = size - 1;i >= 0; i -= 2){int player_one = iv[i];int player_two = iv[i - 1];//假设匹配成功int room_id = wg->rm_->CreateRoom(player_one, player_two);//然后再让当前这两个用户的状态变为PALYINGwg->pm_->SetUserStartStatus(player_one, PLAYING);wg->pm_->SetUserStartStatus(player_two, PLAYING);//为该用户分配房间号wg->pm_->SetRoomID(player_one, room_id);wg->pm_->SetRoomID(player_two, room_id);//为用户分配黑棋/白棋wg->pm_->SetUserChessName(player_one, "黑棋");wg->pm_->SetUserChessName(player_two, "白棋");cout << "player1:" << player_one << ", palyer2:" << player_two << ",roomId:" << room_id << endl;}if (last_id < -1)wg->PushPlayer2MatchPool(last_id);wg->MatchPoolClear();pthread_mutex_unlock(&wg->vec_lock_);}return NULL;
}

这个时候,前端的逻辑就是当监听到用户点击开始匹配按钮之后,就向后端发送一个AJAX请求,用来进行通知后端进行匹配,当后端将当前用户放入匹配池中之后,将相应的结果返回给前端,前端在获得这个结果之后,则就意味着当前用户正在处于匹配状态(MATCHING),为了获知什么时候才会匹配上,则前端需要不停的向后端发送AJAX请求,获知当前匹配的结果,直到当前用户匹配上为止。这里的循环我们采用的是setInterval函数,在该函数内部不停调用AJAX请求,并且每隔1秒发送一次。

js如下:

SetMatch: function() {if (game.match_flag == = false)
return;
console.log("SetMatch Func , 开始匹配...");
$.ajax({url: "/SetMatch",type : "Get",dataType : "JSON",//async : false ==> 禁止异步请求   chrome 有可能会禁止 ajax 的同步请求async : false,success : function(data) {if (data.status == = 0) {console.log("SetMatch Func , 开始匹配状态设置成功...");game.GetMatchResult()}
else {alert("匹配失败, 重新匹配...");
}
},});
},GetMatchResult: function() {//循环的获取匹配结果//向后台循环发送获取匹配结果, 直到匹配成功 -- 调用setInterval函数即可  let s = 0;let _time = setInterval(() = > {console.log("GetMatchResult, " + s++);$.ajax({url: "/Match",type : "Get",dataType : "JSON",async : false,//同步success : function(data) {game.match_status = data.status;if (data.status == = 1) {/**   data = {*      status : 匹配状态*      room_id : xxx,*      chess_name: xxxx*   }* */console.log("匹配成功,room_id is:" + data.room_id);alert("匹配成功, 您执 " + data.chess_name + ", 黑棋先走");game.room_id = data.room_id;game.my_chess_name = data.chess_name;/** 当匹配成功之后,持白棋的人,* 需要循环去获取执黑色棋子的落子位置*/if (data.chess_name == = "白棋")game.GetPeerStep();game.match_flag = false;//将颜色变为灰色game.start_match.style.background = '#d0cdcd';game.start_match.style.color = '#505050';}else if (data.status == = 0 && s >= 30){//当没有匹配上,一直到循环结束,才弹框提示匹配人数太少alert("当前匹配人数太少,请重新匹配...");}else if (data.status == = -1){alert("当前会话失效,请重新登录....");window.location.href = "index.html";}},});if (s >= 30 || game.match_status == = 1 || game.match_status == = -1) {//当调用次数超过30次时。就不再循环调用// 或者当匹配上了// 会着匹配失败clearInterval(_time);}}, 1000); // 1000 -> 每隔1秒调用一次
},

这里也不是无限制的在进行调用,而是当调用次数超过30次,或者已经匹配上、匹配失败的情况下,都会停止对该函数的调用。

4.2.2 难点二:在对战过程中,用户只能下一种颜色的棋子(黑棋/白棋),并且只能在属于自己的回合进行下棋。

解决:

首先,我们要明确的一点就是当前用户能够下棋的条件是一定是已经匹配上了,并且当前处于自己的回合,才可以进行下棋。当两个用户匹配上之后,在创建房间的时候,已经规定了每个用户只能使用一种棋子的颜色(用户1:黑色,用户2:白色),并且决定了当前该哪个用户下棋(初始请求是第一个用户先下棋),因此,我们只需要判断当前房间中的是否该自己下棋即可。


因此,每次只需判断当前用户的user_id_和whose_turn_是否相同即可,相同则代表着是自己的回合,不同则代表着是对方的回合,不能进行下棋

此时前端的逻辑就是当用户点击棋盘上的某一点的时候,就向后端发送一个AJAX请求,判断当前是否是自己的回合,如果是则允许落子,否则,不允许落子。如果当前是允许落子的,那么在棋盘上绘制对应棋子之前,会再发送一个AJAX请求到后端,后端会对当前房间的 whose_turn_ 进行修改,并且保存当前所走棋子的位置信息在vector中

4.2.3 难点三:在对战过程中,对手下的棋子的位置能够及时同步在自己的棋盘上。

解决:

假设当前是用户1的回合,当用户1点击棋盘上的某个位置的时候,前端会给后端发送一个AJAX请求,后端在接收到该请求后,会对当前房间的 whose_turn_ 进行修改,并且保存当前所走棋子的位置信息在一个vector中,然后返回相应的结果,前端在接收到该结果之后,就去循环获取对方棋盘最近一次下棋的棋子位置,同理,调用setInterval函数进行循环获取,即向后端发送AJAX请求,后端将保存在vector中的最后位置的棋子位置信息进行返回,前端拿到该棋子位置信息之后,将该棋子绘制到当前用户的棋盘上,这样就实现了两个用户对战棋盘之间的同步。


这里还需注意的是初始情况下,即两个用户刚刚匹配上的时候,这个时候由于是黑棋先走,因此,黑棋用户在刚开始时是不需要获取对方棋盘上棋子的结果的,而相应的白棋是第二步走的,因此,白棋用户需要在刚开始时循环获取对方棋子的下棋结果

项目——网络对战五子棋(Web-Gobang)相关推荐

  1. 网络对战五子棋(来一起PK鸭)

    网络对战五子棋(来一起PK鸭) 一.本地调用和RPC调用的区别 首先了解一下RPC~ RPC主要是解决了两个问题: 解决了分布式系统中,服务之间的调用问题 尤其是在远程调用的时候,可以让调用者感受不到 ...

  2. 【181018】VC++ 网络对战五子棋游戏(服务端+用户端)

    VC++ 网络对战五子棋游戏(服务端+客户端),编译后先开启服务器端,服务端管理着各个用户之间的数据传递,用户端是多个的.就像游戏大厅一样.用户登录了后服务端将向用户端发送当前所有在线玩家列表数据.由 ...

  3. C#版网络对战五子棋以及Socket通信

    前言     这个网络版五子棋游戏是今年四月初写的.当时觉得自己应该学一些网络编程的东西.而我课程设计的题目已经定了---做一个Everything. 那就帮我斐哥做个网络版的五子棋吧. 源码:htt ...

  4. 五子棋网络对战 java实现

    五子棋网络对战 java实现 最近期末考试,压力实在是太大了.专业不对口的痛苦,别人永远体会不来.闲暇之余,便想着巩固一下java的基础知识,以前看别人写的游戏,心里都觉得哇 好厉害啊,我什么时候才可 ...

  5. Java swing + socket 写的一个五子棋网络对战游戏

    自从开始接触Swing以来,就喜欢写写各种管理系统,写多了就萌生了一种类似于实时在线对战的游戏,经过一番构思后就开始着手设计这个网络对战版本的五子棋了. 游戏代码包含两部分,常规的C/S模式(C代表客 ...

  6. QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★)

    QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★) 文章目录 QT5/C++项目:基于QT的跨平台网络对战象棋(一)(推荐★★★★) 本篇副标题: 本篇博客讲了什么or解决了什么问 ...

  7. 单机版五子棋java功能_java实现单机版和网络对战版五子棋程序

    [实例简介] java实现单机版和网络对战版五子棋,功能包括计时.悔棋.聊天.显示双方状态等等.详细见https://blog.csdn.net/qq_37913997/article/details ...

  8. java五子棋联网对战,毕业设计-- 用JAVA实现五子棋网络对战系统

    毕业设计-- 用JAVA实现五子棋网络对战系统 本科生毕业设计(论文)本科生毕业设计(论文) 题 目 用 JAVA 实现五子棋网络对战系统 姓 名 xxxx 学 号 xxxxx 系 别 计算机工程系 ...

  9. C++新手项目实践 — 智能人机对战五子棋

    C-project-practice-intelligent-man-machine-gobang-fight C++新手项目实践 - 智能人机五子棋对战(利用Easyx的基础函数) 项目演示视频 智 ...

  10. java实现单机版和网络对战版五子棋

    java实现单机版和网络对战版五子棋,功能包括计时.悔棋.聊天.显示双方状态等等. 选择com.fivechess.view中的SelectMenu.java开始游戏,效果如图: (1)单机版 点击& ...

最新文章

  1. link-time version is older than compile-time version
  2. MongoDB应用篇(转)
  3. python怎么启动服务器_如何通过cgi启动服务器端python程序?
  4. 大数据初学者必备的详细版学习路线图
  5. C语言表示一元四次函数,论c语言程序设计 第四次讨论.ppt
  6. webdriver之select、alert、prompt、confirm
  7. camera(16)---双摄持续扩散 摄像头供应链阵营变动加剧
  8. Flask实现发送邮件功能
  9. Ubuntu安装配置JDK、Tomcat、SVN服务器
  10. python自动化运维工程师面试题_运维面试题(含答案)
  11. 表单进阶 单选框 多选框
  12. 云科技网络验证源码_APK云科技网络验证-APK一键加网络验证一键加弹窗引流
  13. java九宫格案例讲解_Java实现九宫格的教程详解
  14. 小白怎样使用EXCEL导入MATLAB画出图像
  15. java打印26个大写字母
  16. 金彩教育:店铺推广要注意的小知识
  17. CVPR 2022 Oral | MLP进军底层视觉!谷歌提出MAXIM:刷榜多个图像处理任务,代码已开源!...
  18. 如何将PDF文件或图片或网页中的公式变为可编辑的--mathpix快速输入公式
  19. 统计正数和负数的个数然后计算这些数的平均值_人人都是数据分析师之统计分析...
  20. Mac显示/隐藏文件夹

热门文章

  1. puppeteer实现百度贴吧自动签到
  2. Speed Gear(变速精灵XP) V6.0 - 免费版,破解版,绿色版
  3. gridview的sort_C#教程之Gridview自动排序功能的实现
  4. 计算机等级考试中0是升序,2017年03月计算机等级VisualFoxPro考试试题
  5. 服务器打包文件命令,nuxt构建项目打包部署服务器二(打包部署)
  6. “我被苹果人脸识别系统「坑」进了监狱”
  7. 虚拟地址空间和物理地址空间
  8. 苹果Mac突然没有声音,3 种方法快速检测
  9. 一文详解HFSS波端口和集总端口
  10. 数据分析 超市条码_京东超市11.11酒类1分钟成交额破亿 持续夯实行业领先优势...