node express实现井字棋socket服务器

实现井字棋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);

PS:本文纯属本人原创,如有更优做法 欢迎指点 如有错误 亦欢迎指正 互相学习

可以使用以下代码实现: ``` import random class Node: def __init__(self, state, parent=None): self.state = state self.parent = parent self.children = [] self.wins = 0 self.visits = 0 def add_child(self, child_state): child = Node(child_state, self) self.children.append(child) return child def update(self, result): self.visits += 1 self.wins += result def fully_expanded(self): return len(self.children) == len(self.state.get_legal_moves()) def best_child(self, c_param=1.4): choices_weights = [ (c.wins / c.visits) + c_param * math.sqrt((2 * math.log(self.visits) / c.visits)) for c in self.children ] return self.children[choices_weights.index(max(choices_weights))] def rollout(self): current_rollout_state = self.state while not current_rollout_state.is_terminal(): possible_moves = current_rollout_state.get_legal_moves() if not possible_moves: break move = random.choice(possible_moves) current_rollout_state = current_rollout_state.move(move) return current_rollout_state.game_result() def backpropagate(self, result): node = self while node is not None: node.update(result) node = node.parent class UCT: def __init__(self, time_limit=None, iteration_limit=None): if time_limit: self.time_limit = time_limit self.limit_type = 'time' elif iteration_limit: self.iteration_limit = iteration_limit self.limit_type = 'iterations' else: raise ValueError("Must supply either time_limit or iteration_limit") self.states = [] def uct_search(self, state): self.states = [] root_node = Node(state) self.states.append(root_node.state) if self.limit_type == 'time': time_limit = time.time() + self.time_limit while time.time() < time_limit: self.uct_iteration(root_node) else: for i in range(self.iteration_limit): self.uct_iteration(root_node) best_child = root_node.best_child(c_param=0) return best_child.state.last_move def uct_iteration(self, root_node): node = root_node state = root_node.state.clone() # Select while node.fully_expanded() and not state.is_terminal(): node = node.best_child() state = state.move(node.state.last_move) self.states.append(state) # Expand if not state.is_terminal(): unexplored_move = random.choice(state.get_legal_moves()) state = state.move(unexplored_move) self.states.append(state) node = node.add_child(state) # Simulate result = node.rollout() # Backpropagate node.backpropagate(result) ``` 这是一个基于 UCT 算法的井字棋 AI,可以在 Python 中使用。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值