当前位置: 首页 > news >正文

express+vue 在线五子棋(一)

示例

在这里插入图片描述

在线体验地址五子棋,记得一定要再拉个人才能对战

本期难点

1、完成了五子棋的布局,判断游戏结束
2、基本的在线对战
3、游戏配套im(这个im的实现,请移步在线im)

下期安排

1、每步的倒计时设置
2、黑白棋分配由玩家自定义
3、新增旁观者
4、悔棋
5、自行创建一盘棋局

感兴趣的,还请点个免费的收藏与关注,后续会一直跟进这个系列

前端部分五子棋布局与游戏输赢判断

<template><div class="flex-wrap flex-justify-between"><div class="gobang-main"><!-- {{ userdata._id }}{{ gameBase }} -->步数: {{ currentStep }} 我方:{{ getQi }}<el-button v-if="isAdmin" @click="clearGame"> 清空游戏 </el-button><!-- 下棋区 --><game :list="gobangList" :config="config" @clickHandle="clickHandle"></game><!-- <el-button v-if="isGameOver && isAdmin" @click="reloadGame"> 重新开始 </el-button> --></div><!-- im区 --><Imref="imRef"v-if="game_im_id":style="{ width: '440px' }":isShowLeft="false":gobang_id="gobang_id":game_im_id="game_im_id"@room_baseinfo="room_baseinfo"@get_game_content="get_game_content"@get_gb_info="get_gb_info"@del_gobang="del_gobang"></Im></div>
</template><script>
import Im from '@/views/blog/im/index.vue'
import { baseURL } from '@/plugins/config.js'
import { get_gobang_list, post_gobang, get_gobang, del_gobang } from '@/api/data.js'
import game from './game.vue'
export default {components: {Im,game,},data() {return {isGameOver: false,page: 'list',gameBase: {_id:'',status: '',max: 10,max_move_time: 0,all_time: 0,hei_user_id: '',bai_user_id: '',im_romm_id:''},gobangMembers: [], // 五子棋游戏成员列表room_id: '',gobangList: [],config: {type: 1, // 1为白棋 2为黑棋line: 15, // 棋盘线条数width: 36,},first: true,}},computed: {...Vuex.mapState(['userdata']),...Vuex.mapGetters(['isAdmin']),gobang_id() {return this.gameBase._id},game_im_id() {return this.gameBase.im_romm_id},// 当前步数currentStep() {let result = 0this.gobangList.forEach((item) => {item.forEach((itey) => {if (itey.step_number > 0) {result += 1}})})return result},getQi() {// 自己是黑棋还是白棋let { _id } = this.userdataif (_id == this.gameBase.bai_user_id) {return '白棋'}if (_id == this.gameBase.hei_user_id) {return '黑棋'}return '观众'},step_content() {// 具体下的内容 1白字 2黑子const obj = {白棋: 1,黑棋: 2,}return obj[this.getQi] || 0},},created() {this.init()},methods: {del_gobang() {// 清空数据// this.gobangList = []// this.isGameOver = false// this.gameBase = {//     status: '',//     max: 10,//     max_move_time: 0,//     all_time: 0,//     hei_user_id: '',//     bai_user_id: '',// }location.reload()},clearGame() {this.$confirm('确定要清空游戏吗?').then((res) => {this.$refs.imRef.send_msg({room_id: this.room_id,specialType: 3,gobang_id: this.gobang_id,})}).catch(() => {})},// 黑棋先行,一人一只下isShould() {let { currentStep, gobangList, step_content } = this// 黑棋个数let heiNumber = 0// 白棋个数let baiNumber = 0// 遍历棋盘gobangList.forEach((aaa) => {aaa.forEach((item) => {if (item.step_content == 1) {baiNumber += 1}if (item.step_content == 2) {heiNumber += 1}})})// 判断现在下的步数的奇数还是偶数let isOdd = currentStep % 2if (step_content === 1) {// 白棋return isOdd === 1}if (step_content === 2) {// 黑棋return isOdd === 0}},clickHandle({ x, y }) {let {step_content,room_id,gobang_id,userdata: { _id: author_id },currentStep,isGameOver,} = thisif (isGameOver) {return this.$message.warning('游戏已结束')}// 只有棋手才能下棋if (![1, 2].includes(+step_content)) return// 判断是否该下子if (!this.isShould()) {return this.$message.warning('请等待对方落子')}let obj = {room_id,specialType: 2,gobang_id,gobang_member_id: author_id,step_number: currentStep + 1,step_content,x,y,author_id,}this.$refs.imRef.send_msg(obj, () => {})},room_baseinfo({ gobangMembers, room_id }) {this.gobangMembers = Array.isArray(gobangMembers) ? gobangMembers : []this.room_id = room_id || ''},initConfigList() {// 生成一个二维数组let { line } = this.configfor (let i = 0; i < line; i++) {this.gobangList.push([])for (let j = 0; j < line; j++) {this.gobangList[i].push({x: i,y: j,step_content: 0, // 0: 空 1: 白 2: 黑})}}},async init() {this.initConfigList()let res = nulltry {res = await get_gobang_list()} catch (err) {return}if (res.data || this.isArray(res.data.data)) {if (res.data.data.length === 0) {let res = await post_gobang({game_name: '五子棋',}).catch(() => {return {}})if (!res.data || !this.isObject(res.data.data)) returnObject.assign(this.gameBase,res.data.data)}if (res.data.data.length) {Object.assign(this.gameBase,res.data.data[0])}}// 获取现有的对局信息get_gobang({ gobang_id: this.gobang_id }).then((res) => {if (res.data && this.isArrayLength(res.data.data)) {let arr = res.data.datathis.gobangList = this.gobangList.map((aaa) => {aaa = aaa.map((item) => {let { x, y } = itemarr.find((itey) => {let { step_content, step_number, gobang_member_id } = iteyif (itey.x == x + 1 && itey.y == y + 1 && step_content) {Object.assign(item, {step_content,step_number,gobang_member_id,})return true}})return item})return aaa})console.log(this.gobangList)}}).catch(() => {})},get_game_content(row) {this.gobangList = this.gobangList.map((aaa) => {aaa = aaa.map((item) => {let { x, y } = itemlet { step_content, step_number, gobang_member_id } = rowif (row.x == x + 1 && row.y == y + 1 && step_content) {Object.assign(item, {step_content,step_number,gobang_member_id,})}return item})return aaa})this.checkWin()},get_gb_info(row) {this.gameBase = Object.assign(this.gameBase, row)console.log('get_gb_info',this.gameBase)},// 判断是否胜利 需要知道哪种棋子胜利checkWin() {// if(this.first) returnthis.first = false// 判断当前棋盘是否有五子连珠let { line } = this.configlet { gobangList: list } = thislet type = 0 // 0: 没有胜利 1: 白棋胜利 2: 黑棋胜利 3: 平局// 判断横向for (let i = 0; i < line; i++) {for (let j = 0; j < line - 4; j++) {if (list[i][j].step_content !== 0 &&list[i][j].step_content === list[i][j + 1].step_content &&list[i][j].step_content === list[i][j + 2].step_content &&list[i][j].step_content === list[i][j + 3].step_content &&list[i][j].step_content === list[i][j + 4].step_content) {type = list[i][j].step_contentbreak}}}// 判断纵向for (let i = 0; i < line; i++) {for (let j = 0; j < line - 4; j++) {if (list[j][i].step_content !== 0 &&list[j][i].step_content === list[j + 1][i].step_content &&list[j][i].step_content === list[j + 2][i].step_content &&list[j][i].step_content === list[j + 3][i].step_content &&list[j][i].step_content === list[j + 4][i].step_content) {type = list[j][i].step_contentbreak}}}// 判断左斜for (let i = 0; i < line - 4; i++) {for (let j = 0; j < line - 4; j++) {if (list[i][j].step_content !== 0 &&list[i][j].step_content === list[i + 1][j + 1].step_content &&list[i][j].step_content === list[i + 2][j + 2].step_content &&list[i][j].step_content === list[i + 3][j + 3].step_content &&list[i][j].step_content === list[i + 4][j + 4].step_content) {type = list[i][j].step_contentbreak}}}// 判断右斜for (let i = 0; i < line - 4; i++) {for (let j = 4; j < line; j++) {if (list[i][j].step_content !== 0 &&list[i][j].step_content === list[i + 1][j - 1].step_content &&list[i][j].step_content === list[i + 2][j - 2].step_content &&list[i][j].step_content === list[i + 3][j - 3].step_content &&list[i][j].step_content === list[i + 4][j - 4].step_content) {type = list[i][j].step_contentbreak}}}// 判断是否平局let flag = truefor (let i = 0; i < line; i++) {for (let j = 0; j < line; j++) {if (list[i][j].step_content == 0) {flag = falsebreak}}}// 如果是平局if (flag) {type = 3}const obj = {1: '白棋胜利',2: '黑棋胜利',3: '平局',}if (obj[type]) {this.$message.success(obj[type])this.isGameOver = true}},},
}
</script><style lang="scss" scoped></style>

express代码

Gobang 为五子棋基本设置表
GobangMember 五子棋对战与观战人表
GobangItem 每一步的对战信息表

const { io } = require("../../tool/socket.js");
const { AuthorInfo } = require("../../mod/author/author_info");
const { ImRoom } = require("../../mod/game/im_room.js");
const { ImRoomSys } = require("../../mod/game/im_room_sys.js");
const { ImRoomMember } = require("../../mod/game/im_room_member.js");
const { Game } = require("../../mod/game/game.js");
const { GameList } = require("../../mod/game/game_list.js");
const { Gobang } = require("../../mod/game/gobang.js");
const { GobangMember } = require("../../mod/game/gobang_member.js");
const { GobangItem } = require("../../mod/game/gobang_item.js");let allSocket = {};// 监听客户端的连接
io.on("connection", function (socket) {allSocket[socket.id] = socket;// 监听用户掉线socket.on("disconnect", async () => {// 更新用户状态let user = await ImRoomMember.findOneAndUpdate({ socket_id: socket.id },{ status: "2" });if (user) {delete allSocket[user.im_room_id];// 这是触发的方法数组,默认只有im的人员信息变化const funStatus = ["members_change"];// 对于五子棋游戏相关退出房间操作try {let res = await GobangMember.findOneAndUpdate({ socket_id: socket.id },{ status: "2" });// TODO: 这儿存在性能问题if (res.n == 1) {funStatus.push("gobang_members_change");}} catch (err) {console.log(err);}// 向房间的用户同步信息sendMsgToRoom(user.im_room_id, null, funStatus);}});// 监听加入房间socket.on("join_room", async (data) => {if (!global.isObject(data)) {resMsg("加入房间参数错误", 400);return;}// game_id 是游戏id,只有游戏才需要传入let { user_id, room_id, gobang_id } = data;if (!user_id) {resMsg("用户id不能为空", 400);return;}let user = await AuthorInfo.findOne({ _id: user_id });if (!user) {resMsg("用户不存在", 400);return;}if (!room_id) {resMsg("房间id不能为空", 400);return;}let room = await ImRoom.findOne({ _id: room_id, status: "1" });if (!room) {resMsg("房间不存在", 400);return;}let { max, status } = room;if (+status !== 1) {resMsg("房间未开放", 300);return;}// 查找所有加入该房间,并且状态为在线的用户let members = await ImRoomMember.find({im_room_id: room_id,status: 1,}).countDocuments();if (members >= max) {resMsg("房间已满", 300);return;}// 查找用户是否已经加入过该房间let oldUser = await ImRoomMember.findOne({im_room_id: room_id,author_id: user_id,});if (!oldUser) {let res = await new ImRoomMember({im_room_id: room_id,author_id: user_id,author_type: 2,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),status: 1,socket_id: socket.id,}).save();if (!res) {resMsg("加入房间失败", 400);return;}} else {await ImRoomMember.updateOne({ im_room_id: room_id, author_id: user_id },{ socket_id: socket.id, status: 1 });}// 这是触发的方法数组,默认只有im的人员信息变化const funStatus = ["members_change"];// 对于五子棋游戏相关加入房间操作if (gobang_id) {let game = await Gobang.findOne({ _id: gobang_id });if (!game) {resMsg("游戏不存在", 400);return;}// 查找用户是否已经加入过该游戏let oldUser = await GobangMember.findOne({gobang_id,author_id: user_id,});if (!oldUser) {let res = await new GobangMember({gobang_id,author_id: user_id,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),status: 1,socket_id: socket.id,user_type: "3",}).save();if (!res) {resMsg("加入游戏失败", 400);return;}} else {try {await GobangMember.updateOne({ gobang_id, author_id: user_id },{ socket_id: socket.id, status: 1 });} catch (error) {console.log("err", err);}}// 查看是否需要更新游戏基本信息-黑棋与白棋let gameInfo = await Gobang.findOne({ _id: gobang_id });if (gameInfo) {let { bai_user_id, hei_user_id } = gameInfo;// 查看用户是否在线let baiUser = await GobangMember.findOne({author_id: bai_user_id,gobang_id,});let heiUser = await GobangMember.findOne({author_id: hei_user_id,gobang_id,});console.log(111,heiUser,baiUser)if (!heiUser) {await Gobang.updateOne({ _id: gobang_id }, { hei_user_id: user_id });} else if (!baiUser) {await Gobang.updateOne({ _id: gobang_id }, { bai_user_id: user_id });}}funStatus.push("get_gb");funStatus.push("gobang_members_change");}// 房间信息改变,向房间内所有在线用户推送房间信息sendMsgToRoom(room_id, null, funStatus, gobang_id);});// 主动推出登录socket.on("live_room", async (data) => {let { room_id, user_id, gobang_id } = data;// 更新用户状态let user = await ImRoomMember.findOneAndUpdate({ im_room_id: room_id, author_id: user_id },{ status: "2" });if (user) {delete allSocket[user.socket_id];// 这是触发的方法数组,默认只有im的人员信息变化const funStatus = ["members_change"];if (gobang_id) {// 对于五子棋游戏相关退出房间操作try {await GobangMember.findOneAndUpdate({ gobang_id, author_id: user_id },{ status: "2" });} catch (err) {console.log(err);}funStatus.push("gobang_members_change");}// 向房间的用户同步信息sendMsgToRoom(room_id, null, funStatus, gobang_id);}});// 发送消息socket.on("send_msg", async (data) => {if (!global.isObject(data)) return;// time是时长// specialType 默认1 im的发送消息;2 五子棋的发送下棋消息 3 五子棋发送清空数据消息let {room_id,author_id,content,msg_type = "1",time = 0,poster = "",video_width = "",video_height = "",specialType = 1,gobang_id,gobang_member_id,step_number,step_content = 0,x,y,} = data;if (specialType == 3) {// 有关五子棋的消息if (!gobang_id || !room_id) {resMsg("清空数据消息有字段缺失", 400, "err", socket);return;}let gobang = await Gobang.findOneAndDelete({ _id: gobang_id });if (!gobang) {resMsg("删除失败", 400, "err", socket);return;}let { im_romm_id } = gobang;// 删除人员await ImRoomMember.deleteMany({ gobang_id: gobang_id });// 删除对局信息await GobangItem.deleteMany({ gobang_id: gobang_id });// 删除聊天室await ImRoom.deleteOne({ _id: im_romm_id });// 删除聊天记录await ImRoomSys.deleteMany({ im_romm_id });// 删除聊天成员信息await ImRoomMember.deleteMany({ im_romm_id });console.log(111)sendMsgToRoom(room_id, null, ["del_gobang"], gobang_id);return}if (specialType == 2) {// 有关五子棋的消息console.log(data);if (!room_id ||!gobang_id ||!gobang_member_id ||!gobang_member_id ||!step_number ||!x ||!y ||!author_id) {resMsg("下棋消息有字段缺失", 400, "err", socket);return;}if (![1, 2].includes(+step_content)) {resMsg("观众不能下棋", 400, "err", socket);return;}let oldGb = await GobangItem.findOne({gobang_id,step_number,});if (oldGb) {resMsg("您已经下过棋了", 400);return;}try {let newGb = await new GobangItem({gobang_id,gobang_member_id: author_id,step_number,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),step_content,x,y,}).save();sendMsgToRoom(room_id, newGb, [], gobang_id);} catch (err) {console.log(err);resMsg("保存步数失败", 400);return;}return;}if (!content) {resMsg("请输入内容", 400);return;}// 判断用户是否存在if (!author_id) {resMsg("用户id不能为空", 400);return;}let user = await AuthorInfo.findOne({ _id: author_id });if (!user) {resMsg("用户id不能为空", 400);return;}// 判断房间是否存在if (!room_id) {resMsg("房间id不能为空", 400);return;}let room = await ImRoom({ _id: room_id, status: "1" });if (!room) {resMsg("房间未开放", 400);return;}if (!content) {resMsg("消息内容不能为空", 400);return;}// 保存消息let params = {im_room_id: room_id,author_id: author_id,content: content,msg_type,created_time: getCurrentTimer(),updated_time: getCurrentTimer(),};if (time) {params.time = time;}if (msg_type == 4) {if (poster) {params.poster = poster;}if (video_width) {params.video_width = video_width;}if (video_height) {params.video_height = video_height;}}let room_sys = await new ImRoomSys(params).save();if (!room_sys) {resMsg("保存消息失败", 400);return;}// 找出对应的成员信息let userinfo = await AuthorInfo.findOne({ _id: author_id },{username: 1,header_img: 1,});if (!userinfo) {resMsg("用户信息不存在", 400);return;}room_sys.author_id = userinfo;sendMsgToRoom(room_id, room_sys);});/*** 向一个房间内的所有在线用户推送房间的基本信息* row 本次聊天信息* name 触发事件的小别名* **/async function sendMsgToRoom(room_id, row = null, names = [], game_id = "") {if (!room_id) return;// 找出全部在线的成员let members = await ImRoomMember.find({im_room_id: room_id,status: 1,},{ socket_id: 1 });if (!members || members.length === 0) return;let sockets = members.map((item) => item.socket_id);// 找出房间信息let room = (await ImRoom.findOne({ _id: room_id, status: "1" })) || {};// 查找出当前房间的总消息数let roomSysCount = await ImRoomSys.find({im_room_id: room_id,}).countDocuments();let res = {data: room,roomSysCount,msg: "房间信息已更新",};if (names.length > 0) {// 去重names = [...new Set(names)];for (let i = 0; i < names.length; i++) {const item = names[i];switch (item) {case "members_change":// 人员状态有变化let roomMembers = await ImRoomMember.find({ im_room_id: room_id },{ author_id: 1, status: 1, room_username: 1 }).populate("author_id", "username header_img").exec();res.roomMembers = roomMembers;break;case "gobang_members_change":// 五子棋人员状态有变化let gobangMembers = await GobangMember.find({ gobang_id: game_id }).populate("author_id", "username header_img").exec();Object.assign(res, {gobangMembers,});break;case "get_gb":// 获取游戏的基本信息let gameInfo = await Gobang.findOne({ _id: game_id });if (gameInfo) {res.gb_info = gameInfo;}break;case "del_gobang":res.is_del_gobang = true;break;}}}if (global.isObject(row)) {if (game_id) {// 五子棋消息res.game_content = row;} else {// im有新消息res.content = row;}}sockets.forEach((item) => {let socket = allSocket[item];if (socket) {resMsg(res, 200, "room_baseinfo", socket);}});}// 获取当前时间戳function getCurrentTimer() {return Date.now();}// 统一返回消息function resMsg(msg, code = 400, name = "err", _socket) {let obj = {code,};if (code === 200) {obj.msg = "操作成功";obj.data = msg;} else {obj.msg = msg;}socket = _socket ? _socket : socket;socket.emit(name, obj);}
});

相关文章:

  • 使用Vue开发页面(前置知识)
  • npm全局安装依赖指定存放文件目录
  • 电子电气架构——由NRC优先级引起的反思
  • 华为appgallery上架
  • 借助AI快速提高英语听力:如何获得适合自己的听力材料?
  • Python爬虫介绍
  • 2024 年值得推荐的 10 款 iPhone 数据恢复软件
  • Less与Sass的区别
  • Mac数据如何恢复?3 款最佳 Mac 恢复软件
  • 汽车网络安全 -- 漏洞该如何管理
  • 面试题2:从浏览器输入一个URL,到最终展示前端页面这一过程,会发生什么?
  • 基于SpringBoot+大数据城市景观画像可视化设计和实现
  • transformer中的build_attention_mask
  • HTTP 请求中的 Content-Type 类型详解
  • [15] 使用Opencv_CUDA 模块实现基本计算机视觉程序
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 【跃迁之路】【735天】程序员高效学习方法论探索系列(实验阶段492-2019.2.25)...
  • CSS相对定位
  • Docker 笔记(2):Dockerfile
  • E-HPC支持多队列管理和自动伸缩
  • Flex布局到底解决了什么问题
  • Git学习与使用心得(1)—— 初始化
  • HTTP 简介
  • IP路由与转发
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • JS变量作用域
  • mysql常用命令汇总
  • nfs客户端进程变D,延伸linux的lock
  • overflow: hidden IE7无效
  • 对象管理器(defineProperty)学习笔记
  • 给新手的新浪微博 SDK 集成教程【一】
  • 基于web的全景—— Pannellum小试
  • 记一次用 NodeJs 实现模拟登录的思路
  • 近期前端发展计划
  • 利用jquery编写加法运算验证码
  • 如何在 Intellij IDEA 更高效地将应用部署到容器服务 Kubernetes ...
  • 组复制官方翻译九、Group Replication Technical Details
  • ###STL(标准模板库)
  • #pragam once 和 #ifndef 预编译头
  • #进阶:轻量级ORM框架Dapper的使用教程与原理详解
  • (C++17) std算法之执行策略 execution
  • (java版)排序算法----【冒泡,选择,插入,希尔,快速排序,归并排序,基数排序】超详细~~
  • (SERIES12)DM性能优化
  • (八)Flask之app.route装饰器函数的参数
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (教学思路 C#之类三)方法参数类型(ref、out、parmas)
  • (十七)Flink 容错机制
  • (五)Python 垃圾回收机制
  • (学习总结16)C++模版2
  • (转载)利用webkit抓取动态网页和链接
  • .Family_物联网
  • .NET CLR Hosting 简介
  • .net core webapi 大文件上传到wwwroot文件夹
  • .NET Core 发展历程和版本迭代
  • .Net Core中Quartz的使用方法