实现井字棋socket服务器
回到我们之前写过的那篇vue实现井字棋的文章
我们接着往下讲讲怎么在其原先代码的基础上使用socket.io实现玩家间的实时对局
一、前期准备工作:
由于我们的服务器使用的是基于nodeJs的express 所以我们这里必须要提前安装nodeJs及express:
nodeJs安装地址
express安装
二、socket.io基本使用
1.socket服务端
socketIo服务端基本使用:
//监听链接事件
io.on('connection', (socket) => {
//do something
//监听客户机发送的xxxx指令,并作出行为
socket.on('xxxx',(data)=>{
//do something
})
//向客户机发送xxxx指令,并带上data参数
socket.emit('xxxx',data)
});
// 监听连接断开事件
io.on("disconnect", (socket) => {
//do something
});
2. socket.io客户端
引入socket.io:
<script src="https://lib.baomitu.com/socket.io/4.2.0/socket.io.min.js"></script>
链接socket服务器:
const socket = io.connect("http://localhost:4001", { transports: ['websocket', 'xhr-polling', 'jsonp-polling'] });
io.connect 函数说明
这个函数有两个参数io.connect (url,option),其中:
- url:链接的socket服务器地址,类型:String
- option:socket链接的配置项,类型:Object
- option参数键值说明:
connect timeout
默认值: 5000
作用:设置创建连接所接收的超时时间,单位是毫秒。
try multiple transports
默认值: true
作用:当连接超时后是否允许Socket.io以其他连接方式尝试连接
reconnect
默认值: true
作用:当连接终止后,是否允许Socket.io自动进行重连
reconnection delay
默认值: 500
作用:为Socket.io的重连设置一个时间间隔,内部会在多次重连尝试时采用该值的指数值间隔,用来避免性能损耗(500 > 1000 > 2000 > 4000 > 8000)
max reconnection attempts
默认值: 10
作用:设置一个重连的最大尝试次数,超过这个值后Socket.io会使用所有允许的其他连接方式尝试重连,直到最终失败。
transports
默认值: [‘websocket’, ‘flashsocket’, ‘htmlfile’, ‘xhr-multipart’, ‘xhr-polling’, ‘jsonp-polling’]
作用:默认支持的链接方式
向服务器端发送信息
socket.emit("xxx", data);
socket.emit函数说明
这个函数有两个参数socket.emit(message,data),其中:
- message:发送给服务器的消息指令(即服务端要用哪个指令来接收这个消息),类型:String
- data:发送消息的同时候带上数据,类型:Any:可为任意javascript合法数据类型
接收服务端发过来的信息
socket.on("xxx", (data) => {
//do somthing
})
socket.on函数说明
这个函数有两个参数socket.on(message,function),其中:
- message:接收服务器的消息指令(即服务端用哪个指令发送的这个消息),类型:String
- function:接收消息后执行的回调方法,类型:Function
废话的介绍阶段就到这里告一段落了,下面 直接上代码:
客户端:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>井字棋</title>
<style>
.content {
position: relative;
margin: 0 auto;
width: 600px;
height: 600px;
box-sizing: border-box;
}
.cell {
position: relative;
float: left;
width: 200px;
height: 200px;
box-sizing: border-box;
border: 1px solid red;
}
.cell[value="1"] .chess {
position: absolute;
width: 180px;
height: 180px;
top: 50%;
left: 50%;
margin: -90px 0 0 -90px;
border-radius: 50%;
background: lightgray;
}
.cell[value="-1"] .chess {
position: absolute;
width: 180px;
height: 180px;
top: 50%;
left: 50%;
margin: -90px 0 0 -90px;
background: black;
border-radius: 50%;
}
.mask {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
background: white;
}
.gameResult {
position: absolute;
bottom: 60%;
left: 50%;
width: 400px;
height: 50px;
line-height: 50px;
text-align: center;
margin: 0 0 0 -200px;
}
.gameStart {
position: absolute;
top: 40%;
left: 50%;
width: 200px;
height: 50px;
line-height: 50px;
text-align: center;
margin: 0 0 0 -100px;
}
</style>
</head>
<body>
<div id="main">
<!--遮罩层-->
<div class="mask" v-if="isDraw">
<div class="gameResult" v-if="resultshow">游戏结束,平局</div>
<div class="gameStart"><button @click="gameStart()">{{masktips}}</button></div>
</div>
<div class="mask" v-if="maskshow&&!isDraw">
<div class="gameResult" v-if="resultshow">游戏结束,{{turn==2?"白":"黑"}}方胜</div>
<div class="gameStart"><button @click="gameStart()">{{masktips}}</button></div>
</div>
<!--主代码-->
<div class="content">
<div v-for="(row,i) in chess">
<div v-for="(col,j) in row" class="cell" :value="chess[i][j]" @click="active(i,j)">
<div class="chess"></div>
</div>
</div>
</div>
</div>
</body>
<script src="./js/vue.js"></script>
<script src="./js/socket.io.min.js"></script>
<script>
const vm = new Vue({
el: "#main",
data: {
//遮罩文字显示情况
maskshow: true,
//遮罩文字
masktips: "游戏开始",
//结果信息显示情况
resultshow: false,
//当前回合轮到谁
turn: 1,
whoFirst: 1,
//是否平局
isDraw: false,
//棋子信息
chess: [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
],
socket: null
},
created() {
sessionStorage.clear()
this.linkSocket()
},
methods: {
//链接socket服务器
linkSocket() {
this.socket = io.connect("http://localhost:4001", { transports: ['websocket', 'xhr-polling', 'jsonp-polling'] });
//获取双方Id
this.socket.emit("getSocketId")
this.socket.emit("getEnemyId")
this.socket.on("socketId", (data) => {
sessionStorage.setItem('socketId', data)
console.log("socketId", data)
})
this.socket.on("enemyId", (data) => {
if (data === null) {
this.socket.emit("getEnemyId")
} else {
sessionStorage.setItem('enemyId', data)
}
})
//获取谁先手
this.socket.emit("getWhoFirst")
this.socket.on("whoFirst", (data) => {
this.$data.whoFirst = data === sessionStorage.getItem('socketId') ? 1 : 2
})
//监听落子
this.socket.on("getChess", data => {
console.log(data)
const { x, y, turn } = data
if (this.turn === 1) {
this.$set(this.chess[x], y, turn)
this.rule();
this.turn = 2;
} else {
this.$set(this.chess[x], y, turn)
this.rule();
this.turn = 1;
}
})
},
//点击开始,遮罩消失
gameStart() {
this.maskshow = false
this.isDraw = false
},
//游戏结束,初始化游戏状态,并提示
gameOver() {
this.socket.emit("gameOver")
this.maskshow = true;
this.masktips = "再来一局";
this.resultshow = true;
this.chess = [
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
]
},
//游戏规则判断
rule() {
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
//打横
if (this.chess[i][0] + this.chess[i][1] + this.chess[i][2] === 3) {
this.gameOver();
return false;
} else if (this.chess[i][0] + this.chess[i][1] + this.chess[i][2] === -3) {
this.gameOver();
return false;
}
//打竖
else if (this.chess[0][j] + this.chess[1][j] + this.chess[2][j] === 3) {
this.gameOver();
return false;
} else if (this.chess[0][j] + this.chess[1][j] + this.chess[2][j] === -3) {
this.gameOver();
return false;
}
//斜向下
else if (this.chess[0][0] + this.chess[1][1] + this.chess[2][2] === 3) {
this.gameOver();
return false;
} else if (this.chess[0][0] + this.chess[1][1] + this.chess[2][2] === -3) {
this.gameOver();
return false;
}
//斜向上
else if (this.chess[2][0] + this.chess[1][1] + this.chess[0][2] === 3) {
this.gameOver();
return false;
} else if (this.chess[2][0] + this.chess[1][1] + this.chess[0][2] === -3) {
this.gameOver();
return false;
}
}
}
//平局条件判断
let flag = true
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
if (this.chess[i][j] === 0) {
flag = false
break;
}
}
}
if (flag) {
this.gameOver();
this.isDraw = true
}
},
//落子
active(x, y) {
console.log(this.whoFirst, this.turn)
if (this.whoFirst !== this.turn) {
alert("当前不是你的回合 请等待对方落子")
return false
}
if (this.chess[x][y] !== 0) {
alert("此处已有子")
return false
}
this.socket.emit("putChess", {
socketId: sessionStorage.getItem("socketId"),
enemyId: sessionStorage.getItem("enemyId"),
x,
y,
turn: this.turn === 1 ? 1 : -1
})
}
}
})
</script>
</html>
服务端:
//导入依赖
const express = require('express');
const app = express();
const server = require('http').createServer(app);
const io = require('socket.io')(server);
const port = 4001//服务器启动在哪个端口
//useList为用户列表,即链接上socket服务器的用户人数
let useList = []
// 设置静态文件托管目录
app.use(express.static('node_modules'));
app.get('/', (request, response) => {
response.send(`<h1>webSocket server in 127.0.0.1:${port}</h1>`);
});
//监听客户端链接,回调函数会传递本次链接的socket
io.on('connection', socket => {
console.log(`${socket.id}用户连接了`)
//用户链接后将用户id添加到useList里面
useList.push(socket.id)
console.log(`当前链接用户:${useList}`)
//获取我方id
socket.on('getSocketId', data => {
io.to(socket.id).emit("socketId", socket.id)
if (useList.length % 2 === 0) {
io.to(useList[useList.length - 2]).emit("enemyId", socket.id)
} else {
io.to(socket.id).emit("socketId", socket.id)
io.to(socket.id).emit("enemyId", useList[useList.length - 2] || null)
}
})
//获取对手id
socket.on("getEnemyId", data => {
if (useList.length % 2 === 0) {
io.to(useList[useList.length - 2]).emit("enemyId", socket.id)
io.to(socket.id).emit("enemyId", useList[useList.length - 2])
} else {
io.to(socket.id).emit("enemyId", useList[useList.length - 2] || null)
}
})
// 落子:socketId---谁的回合
socket.on("putChess", data => {
console.log(data)
const { socketId, enemyId } = data
//向双方发送当前落子的信息
io.to(socketId).emit("getChess", data)
io.to(enemyId).emit("getChess", data)
})
//获取谁是先手
socket.on("getWhoFirst", data => {
io.to(socket.id).emit("whoFirst", useList[0])
})
//游戏结束
socket.on("gameOver", data => {
useList = []
})
});
// 监听连接断开事件
io.on("disconnect", (socket) => {
useList.slice(useList.indexOf(socket.id), 1)
console.log(`${socket.id}的连接已断开...`);
});
server.listen(port);