目录

1、同步两个玩家的位置思路解析

2、实现了三个棋盘的同步原理

3、初始化一下我们的playerAplayerB

4、什么是线程为什么要用多线程?

5、如何去实现等待两名玩家输入

6、前端向后端发消息

7、在数据库中创建表record


1、同步两个玩家的位置思路解析

除了地图同步以外、我们还需要同步两个玩家的位置
同步玩家的位置我们可以标记一下、至于谁在A谁在B我们需要在云端确定
确定完之后我们会把每一个玩家的位置传给前端,我们可以傻瓜式的确定a在左下角b在
右上角、我们在存地图的时候需要存一下玩家的id和位置
在game这个类里我们需要加一个player类来维护玩家的位置信息
一般开发思路需要用什么定义什么、先定义需要用到的各种函数
有参构造函数无参构造函数、存一下每个玩家每一次的指令是什么

2、实现了三个棋盘的同步原理

现在有三个棋盘、还有一个在云端
有两个浏览器就是有两个client、状态同步的机制
client向云端发送消息表示这个蛇动了一下、当服务器接收到两个蛇的移动之后
服务器就会把两个蛇移动的信息分别返回给Client1client2
同步给两名玩家、这样我们就实现了三个棋盘的同步

3、初始化一下我们的playerAplayerB

首先我们构造map的时候传入两名玩家的userid、初始化一下我们的playerAplayerB
为了需要访问到我们的player、我们需要写两个函数
后端就可以把两个玩家的信息传过去、前端做出相应修改

4、什么是线程为什么要用多线程?

Game不能作为单线程来处理、线程:一个人干就是单线程,两个人干就是多线程
涉及到两个线程之间的通信以及加锁的问题
我们需要先把game变成一个支持多线程的类
就变成多线程了、我们需要实现thread类的一个入口函数
alt+insert就可以实现、重载run函数
start函数就是thread函数的一个api、可以另起一个线程来执行这个函数
为了方便我们需要先把我们的game存放到这个类里面
我们的线程就要一步一步等待下一步操作的操作
这里设计到两个线程同时读写一个变量、这样就会有读写冲突、涉及到顺序问题

5、如何去实现等待两名玩家输入

两名玩家都输入我们就进行下一步
如果超过一定时间之后两名玩家还没有输入的话
我们要结束这个操作、告诉我们哪个玩家没有输入
就输了、可以用sleep函数、如果是正在进行中的话
我们应该将这一步操作广播给两位玩家、需要同步一下
我们从服务器分别接收到两名玩家的输入之后、需要将两名玩家的输入分别广
播给两个人、比如说我们两个玩家,同时都向服务器发送了请求
c1不知道c2的操作s向c1c2广播操作

6、前端向后端发消息

当我们移动的时候、之前我们是在gamemap里面判断的
两个线程同时操纵一个变量、至少有一个变量是写的话那就需要加锁子
前端写完之后后端需要接收到这个请求
gameobject需要存下来才能访问到蛇、每一个新的游戏都会new一个新的类
都会开一个新的线程

7、在数据库中创建表record

record表用来记录每局对战的信息

表中的列:

id: int
a_id: int
a_sx: int
a_sy: int
b_id: int
b_sx: int
b_sy: int
a_steps: varchar(1000)
b_steps: varchar(1000)
map: varchar(1000)
loser: varchar(10)
createtime: datetime

联机对战:同步玩家的操作

文件结构

backend
    consumer
        utils
            Cell.java
            Player.java

src
    components
        ResultBoard.vue

1.后端:同步玩家位置信息

玩家类:需要联机对战肯定就有玩家,因此建立玩家类

package com.kill9.backend.consumer.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.List;@NoArgsConstructor
@AllArgsConstructor
@Data
public class Player {private Integer id;private Integer sx;private Integer sy;//每一次的指令private List<Integer> steps;
}

游戏类

一场游戏包含两名玩家

 private final Player playerA,playerB;final int[][] g;public Game(Integer rows,Integer cols,Integer innerWallsCount,Integer idA,Integer idB){this.rows = rows;this.cols = cols;this.innerWallsCount = innerWallsCount;this.g = new int[rows][cols];playerA = new Player(idA,rows-2,1,new ArrayList<>());playerB = new Player(idB,1,cols-2,new ArrayList<>());}public Player getPlayerA() {return playerA;}public Player getPlayerB() {return playerB;}

websocket操作类

在匹配完之后,初始化地图时,将玩家传给Game构造函数

Game game = new Game(13,14,20,a.getId(),b.getId());game.createMap();//封装游戏信息JSONObject respGame = new JSONObject();respGame.put("a_id",game.getPlayerA().getId());respGame.put("a_sx",game.getPlayerA().getSx());respGame.put("a_sy",game.getPlayerA().getSy());respGame.put("b_id",game.getPlayerB().getId());respGame.put("b_sx",game.getPlayerB().getSx());respGame.put("b_sy",game.getPlayerB().getSy());respGame.put("map",game.getG());respA.put("game",respGame);
respB.put("game",respGame);

2.前端:接收两名玩家位置以及地图信息

pk类的存储

import ModuleUser from './user'
export default {state: {socket:null ,//ws连接opponent_username:"",opponent_photo:"",status:"matching",matching表示匹配界面,playing表示对战界面gamemap:null,a_id:0,a_sx:0,a_sy:0,b_id:0,b_sx:0,b_sy:0,},getters: {},mutations: {updateSocket(state,socket){state.socket = socket;},updateOpponent(state,opponent){state.opponent_username = opponent.username,state.opponent_photo = opponent.photo;},updateStatus(state,status){state.status = status;},updateGame(state,game){state.gamemap = game.map;state.a_id = game.a_id;state.a_sx = game.a_sx;state.a_sy = game.a_sy;state.b_id = game.b_id;state.b_sx = game.b_sx;state.b_sy = game.b_sy;}},actions: {},modules: {user: ModuleUser,}
}

pk界面

修改pk界面的接收地图相关参数

export default {...setup() {...onMounted(() => {...// 回调函数:接收到后端信息调用socket.onmessage = msg => {// 返回的信息格式由后端框架定义,django与spring定义的不一样const data = JSON.parse(msg.data);if(data.event === "start-matching") {...setTimeout(() => {store.commit("updateStatus", "playing");}, 200);store.commit("updateGame", data.game);}}...});...}
}

3.后端:一局游戏的逻辑

Webscoket操作类

将Game作为一局游戏的一个线程,使Game线程启动

 private Game game = null;...private void startMatching() {System.out.println("start matching!");matchPool.add(this.user);while(matchPool.size() >= 2) {...game.createMap();// 一局游戏一个线程,会执行game类的run方法game.start();users.get(a.getId()).game = game;users.get(b .getId()).game = game;...}}

游戏类

一局游戏的操作,首先执行run方法。只有读写、写写有冲突,此处关于nextStep,我们会接收前端的nextStep输入 或 bots代码的输入,而且会频繁的读,因此需要加锁。

Game.java

package com.kill9.backend.consumer.utils;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;//支持多线程
public class Game extends  Thread{private final Integer rows;private final Integer cols;private final Integer innerWallsCount;private  final static int[] dx = {-1,0,1,0},dy={0,1,0,-1};private final Player playerA,playerB;private Integer nextStepA = null;private Integer nextStepB = null;//定义锁private ReentrantLock lock = new ReentrantLock();final int[][] g;public Game(Integer rows,Integer cols,Integer innerWallsCount,Integer idA,Integer idB){this.rows = rows;this.cols = cols;this.innerWallsCount = innerWallsCount;this.g = new int[rows][cols];playerA = new Player(idA,rows-2,1,new ArrayList<>());playerB = new Player(idB,1,cols-2,new ArrayList<>());}public int[][] getG(){return g;}public Player getPlayerA(){return playerA;}public Player getPlayerB(){return playerB;}public void setNextStepA(Integer nextStepA){//上锁lock.lock();try{this.nextStepA = nextStepA;}finally {lock.unlock();}}public void setNextStepB(Integer nextStepB){//上锁lock.lock();try{this.nextStepB = nextStepB;}finally {lock.unlock();}}//判断连通性 flood fillprivate boolean check_connectivity(int sx,int sy,int tx,int ty){if(sx==tx&&sy==ty) return true;g[sx][sy] = 1;for(int i = 0;i<4;i++){int x = sx+dx[i],y = sy+dy[i];if(x<this.rows && x>=0 && y<this.cols && y>=0 && g[x][y]==0){if(check_connectivity(x,y,tx,ty)){//恢复原来的数组g[sx][sy] = 0;return true;}}}//恢复现场g[sx][sy] = 0;return  false;}//画地图private  boolean draw(){//初始化for(int i = 0;i<this.rows;i++){for(int j = 0;j<this.cols;j++){g[i][j] = 0;}}//给四周加墙for(int r = 0;r<this.rows;r++){g[r][0] = g[r][this.cols-1] = 1;}for(int c = 0;c<this.cols;c++){g[0][c] = g[this.rows-1][c] = 1;}Random random = new Random();for(int i = 0;i<this.innerWallsCount/2;i++){for(int j = 0;j<1000;j++){int r = random.nextInt(this.rows);int c = random.nextInt(this.cols);//画过的不画if(g[r][c]==1||g[this.rows-1-r][this.cols-1-c]==1) continue;g[r][c] = g[this.rows-1-r][this.cols-1-c] = 1;break;}}return check_connectivity(this.rows-2,1,1,this.cols-2);}public void createMap(){for(int i = 0;i<1000;i++){if(draw()) break;}}private boolean nextStep(){//每秒五步操作,因此第一步操作是在200ms后判断是否接收到输入。并给地图初始化时间//如果两名玩家操作非常快 比如1s操作了50次,但是返回前端,每动完一格之后才会去判断下一步,//如果我们在动一格的期间获取了很多下一步的操作的话,我们就会把中间结果都覆盖掉,在前端渲染的时候就会遗漏一些步数//所以在进行下一步操作时先睡200ms  ,因为200ms才能走一格,如果200ms内多输入了很多,它之只会留最后一步,就会 覆盖掉try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}//等待两名玩家的下一步操作,上锁for(int i = 0;i<5;i++){//此循环循环了5000ms,也就是5s,前端是一秒移动5步,//后端接收玩家键盘输入是5s内玩家的一个输入,若在一方先输入,//一方还未输入,输入的一方多此操作,以最后一次为准。try {Thread.sleep(1000);//等待玩家输入lock.lock();//读玩家输入要上锁try{if(nextStepA != null && nextStepB != null){playerA.getSteps().add(nextStepA);playerB.getSteps().add(nextStepB);return true;}}finally {//由于报异常的话 可能不会解锁,所以要在finally解锁lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();}}return false;}//入口函数@Overridepublic void run() {super.run();}
}

先搭好整体框架

 private void sendResult(){ //向两名玩家返回结果}private void judge(){//判断两名玩家是否合法操作}private void sendMove(){ //向两个client公布信息}//入口函数@Overridepublic void run() {for(int i = 0;i<1000;i++){//先判断是否获取两条蛇的下一步操作if(nextStep()){ // 是否获取了两条蛇的下一步的操作judge();if(status.equals("playing")){//将每名玩家的操作广播给两名玩家,实现同步sendMove();}else{//向前端返回结果sendResult();break;}}else{//这里也要加锁 涉及到nextStep的读 防止边界情况 ,在超时的边界输入lock.lock();status = "finished";try{if(nextStepA == null && nextStepB == null){loser = "all";}else if(nextStepA == null){loser =  "A";}else{loser = "B";}     }finally {lock.unlock();}sendResult();break;// 表示整个游戏结束}}}
}
  private void sendAllMessage(String message){WebSocketServer.users.get(playerA.getId()).sendMessage(message);WebSocketServer.users.get(playerB.getId()).sendMessage(message);}private void sendResult(){ //向两名玩家返回结果//向本局的两名玩家分别广播JSONObject resp = new JSONObject();resp.put("event","result");resp.put("loser",loser);sendAllMessage(resp.toJSONString());}private void judge(){//判断两名玩家是否合法操作}private void sendMove(){ //向两个client公布信息lock.lock();try{JSONObject resp = new JSONObject();resp.put("event","move");resp.put("a_direction",nextStepA);resp.put("b_direction",nextStepB);//清空操作nextStepA = nextStepB = null;}finally {lock.unlock();}}

4.前端: 发送移动指令给后端

GameMap.js

add_listening_events() {this.ctx.canvas.focus();this.ctx.canvas.addEventListener("keydown", e => {let d = -1;if(e.key === 'w') d = 0;else if(e.key === 'd') d = 1;else if(e.key === 's') d = 2;else if(e.key === 'a') d = 3;// else if(e.key === 'ArrowUp') snake1.set_direction(0);// else if(e.key === 'ArrowRight') snake1.set_direction(1);// else if(e.key === 'ArrowDown') snake1.set_direction(2);// else if(e.key === 'ArrowLeft') snake1.set_direction(3);// 若移动了,发送给后端if(d >= 0) {this.store.state.pk.socket.send(JSON.stringify({event: "move",direction: d,}));}});}

5.后端处理接收移动的事件

private void move(int d){if(game.getPlayerA().getId().equals(user.getId())) {game.setNextStepA(d);}else if(game.getPlayerB().getId().equals(user.getId())){game.setNextStepB(d);}}//路由@OnMessagepublic void onMessage(String message, Session session) {// 从Client接收消息System.out.println("receive message!");//将字符串转换成json对象JSONObject data = JSONObject.parseObject(message);String event = data.getString("event");//防止空指针if("start-matching".equals(event)){startMatching();}else if("stop-matching".equals(event)){stopMatching();}else if("move".equals(event)){move(data.getInteger("direction"));}}

6前端接收后端发送过来的移动事件

设立GameMap的存储

src/store/pk.jsexport default {state: {...gameObject: null,},getters: {},mutations: {...updateGameObject(state, gameobject) {state.gameObject = gameobject;}},actions: {},modules: {}
}

GameMap存储

src/components/GameMap.vue<template>...
</template><script>
...export default {setup() {...onMounted(() => {store.commit("updateGameObject",new GameMap(canvas.value.getContext('2d'), parent.value, store));});...}
}
</script><style scoped>...</style>

Pk页面:接收后端的移动事件

src/views/pk/PkIndexView.vue<template>...
</template><script>
...export default {components: {PlayGround,MatchGround,},setup() {...onMounted(() => {...// 回调函数:接收到后端信息调用socket.onmessage = msg => {// 返回的信息格式由后端框架定义,django与spring定义的不一样const data = JSON.parse(msg.data);if(data.event === "start-matching") {...} else if(data.event === "move") {console.log(data);const game = store.state.pk.gameObject;const [snake0, snake1] = game.snakes;snake0.set_direction(data.a_direction);snake1.set_direction(data.b_direction);} else if(data.event === "result") {console.log(data);}}...});onUnmounted(() => {...});}
}
</script><style scoped></style>

7.前端:根据后端返回结果将死了的蛇变白

Snake.js

删除前端判断蛇的操作是否有效

export class Snake extends AcGameObject {...// 将蛇状态变为走下一步next_step() {...// 让蛇在下一回合长一个格子const k = this.cells.length;for(let i = k; i > 0; i--) {this.cells[i] = JSON.parse(JSON.stringify(this.cells[i - 1]));}// 删除前端判断蛇的操作是否有效}...
}

PkIndexView.vue

根据后端结果将失败的一方颜色变白

// 回调函数:接收到后端信息调用socket.onmessage = msg => {// 返回的信息格式由后端框架定义,django与spring定义的不一样const data = JSON.parse(msg.data);if(data.event === "start-matching") {...} else if(data.event === "move") {...} else if(data.event === "result") {console.log(data);const game = store.state.pk.gameObject;const [snake0, snake1] = game.snakes;if(data.loser === "all" || data.loser === "A") {snake0.status = "die";}if(data.loser === "all" || data.loser === "B") {snake1.status = "die";}}}

8.后端:将前端的裁判程序移到后端

注意,此地方有两处代码有迷惑性,分别是:
1. 在Player.java的getCells函数里为什么删除的是第0个元素
2. 在Game.java的check_valid函数里为什么不判断最后一个元素是否重合
以上两个答案在下方代码的关键处有解释

Cell.java

package com.kill9.backend.consumer.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Cell {private Integer x;private Integer y;
}

Player.java

package com.kill9.backend.consumer.utils;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.ArrayList;
import java.util.List;@NoArgsConstructor
@AllArgsConstructor
@Data
public class Player {private Integer id;private Integer sx;private Integer sy;//每一次的指令private List<Integer> steps;private boolean check_tail_increasing(int step){//检验当前回合蛇的长度是否增加if(step<=10) return true;return step % 3 == 1;}public List<Cell> getCells(){List<Cell> res = new ArrayList<>();int[] dx={-1,0,1,0},dy = {0,1,0,-1};int x = sx,y = sy;int step = 0;/*** 每一步移动都会把蛇头移动到下一个格子(注:蛇头有两个cell,详看前端Snake.js的next_step()与update_move()逻辑),* 若当前长度增加,蛇头正好移到新的一个格子,剩下的蛇身长度不变,因此长度 + 1;若长度不增加,则删除蛇尾*/res.add(new Cell(x,y));for(int d: steps){x += dx[d];y += dy[d];res.add(new Cell(x,y));if(!check_tail_increasing(++step)){/*** 关键:* 为什么此处删除0呢,首先存储蛇身、且判定是否增加、且画蛇的逻辑此时还是在前端,我们只是将* 判断蛇是否撞到 墙和蛇身 移到后端。并且我们在后端保存的是是蛇头的x、y坐标和蛇身相对* 于上一步操作的方向,但是在我们做了第一个操作后蛇尾才是蛇头,意思就是res逆序才是蛇* 头到蛇尾的位置!*/res.remove(0);//删掉蛇尾}return  res;}}}

Game.java

public class Game extends Thread {...private boolean check_valid(List<Cell> cellsA, List<Cell> cellsB) {int n = cellsA.size();Cell cell = cellsA.get(n - 1);// 如果是墙,则非法if(g[cell.x][cell.y] == 1) return false;// 遍历A除最后一个Cell/*** 关键:* 首先我在Player中已经解释getCells的函数返回的res是蛇尾到蛇头的位置。* 因此以下两个for循环分别判断的是蛇头是否和两条蛇的蛇身重合!* 那么为什么不用判断两个蛇头是否重合呢?可能是地图大小为13 * 14,* 两个蛇头的位置初始为(1, 1)和(11, 12),两个蛇头的位置横纵之和分别为偶数* 和奇数,因此两个蛇头永远不会走到同一个格子!*/for(int i = 0; i < n - 1; i++) {// 和蛇身是否重合if(cellsA.get(i).x == cell.x && cellsA.get(i).y == cell.y) {return false;}}// 遍历B除最后一个Cellfor(int i = 0; i < n - 1; i++) {// 和B蛇身是否重合if(cellsB.get(i).x == cell.x && cellsB.get(i).y == cell.y) {return false;}}return true;}private void judge() {      // 判断两名玩家操作是否合法List<Cell> cellsA = playerA.getCells();List<Cell> cellsB = playerB.getCells();boolean validA = check_valid(cellsA, cellsB);boolean valibB = check_valid(cellsB, cellsA);if(!validA || !valibB) {status = "finished";if(!validA && !valibB) {loser = "all";} else if(!validA) {loser = "A";} else {loser = "B";}}}private void senAllMessage(String message) {...}private void sendMove() {   // 向两名玩家传递移动信息...}private void sendResult() {     // 向两名玩家发送游戏结果...}@Overridepublic void run() {...}
}

9.结果板的实现

pk信息存储类

src/store/pk.jsexport default {state: {...loser: "none",  // all、A、B},getters: {},mutations: {...updateLoser(state, loser) {state.loser = loser;}},actions: {},modules: {}
}

ResultBoard.vue

src/components/ResultBoard.vue
<template><div class="result-board"><div class="result-board-text" v-if="$store.state.pk.loser == 'all'">Draw</div><div class="result-board-text" v-else-if="$store.state.pk.loser == 'A' && $store.state.pk.a_id == $store.state.user.id">Lose</div><div class="result-board-text" v-else-if="$store.state.pk.loser == 'B' && $store.state.pk.b_id == $store.state.user.id">Lose</div><div class="result-board-text" v-else>Win</div><div class="result-board-btn"><button @click="restart" type="button" class="btn btn-warning btn-lg">再来一局</button></div></div></template>
<script>
import {useStore} from 'vuex';export default{setup(){const store = useStore();const restart = () =>{store.commit("updateStatus","matching");store.commit("updateOpponent",{username:"我的对手",photo:"https://cdn.acwing.com/media/article/image/2022/08/09/1_1db2488f17-anonymous.png",})store.commit("updateLoser","none");}return {restart,}}
}
</script>
<style scoped>
div.result-board{height: 30vh;width: 30vw;background-color:rgba(191, 227, 241,0.5);position: absolute;top: 40vh;left: 35vw;}
div.result-board-text{text-align: center;color: white;font-size: 50px;font-weight: 600;font-style: italic;padding-top: 5vh;}
div.result-board-btn{ text-align: center;padding-top: 7vh;
}
</style>

Pk页面

src/views/pk/PkIndexView.vue<template>...<ResultBoard v-if="$store.state.pk.loser != 'none'" />
</template><script>...import ResultBoard from '../../components/ResultBoard.vue';...export default {components: {...ResultBoard,},setup() {...onMounted(() => {...// 回调函数:接收到后端信息调用socket.onmessage = msg => {// 返回的信息格式由后端框架定义,django与spring定义的不一样const data = JSON.parse(msg.data);if(data.event === "start-matching") {...} else if(data.event === "move") {...} else if(data.event === "result") {...store.commit("updateLoser", data.loser);}}...});...}
}
</script><style scoped></style>

完善pk页面 对局中添加玩家头像,确认自己是哪条蛇

PlayGround.vue

<template><div class="playground"><div class="user_b" v-if="$store.state.pk.b_id == $store.state.user.id"><img :src="$store.state.user.photo" alt=""><p>{{$store.state.user.username}}</p></div><div class="user_b" v-if="$store.state.pk.b_id != $store.state.user.id"><img :src="$store.state.pk.opponent_photo" alt=""><p>{{$store.state.pk.opponent_username}}</p></div><GameMap/><div class="user_a" v-if="$store.state.pk.a_id != $store.state.user.id"><img :src="$store.state.pk.opponent_photo" alt=""><p>{{$store.state.pk.opponent_username}}</p></div><div class="user_a" v-if="$store.state.pk.a_id == $store.state.user.id"><img :src="$store.state.user.photo" alt=""><p>{{$store.state.user.username}}</p></div></div>
</template><script>
import GameMap from './GameMap';export default{components:{GameMap,}
}</script><style scoped>div.playground{width:60vw;height: 70vh;margin: 70px auto;
}
.user_a > img{width: 10vh;border-radius: 40%;}.user_a{margin-left: 10%;}.user_b > img{width: 10vh;border-radius: 40%;}.user_b{margin-left: 80%;}p{margin-left:27px ;}
</style>

对局回放

通过保存对局每个状态的信息实现对局的回放功能

backend
    pojo
        Record.java
    mapper
        RecordMapper.java

创建数据库

Record.java

package com.kill9.backend.pojo;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.util.Date;@Data
@AllArgsConstructor
@NoArgsConstructor
public class Record {@TableId(type = IdType.AUTO)private Integer id;private Integer aId;private Integer aSx;private Integer aSy;private Integer bId;private Integer bSx;private Integer bSy;private String aSteps;private String bSteps;private String map;private String loser;@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")private Date createtime;
}

RecordMapper.java

package com.kill9.backend.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.kill9.backend.pojo.Record;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface RecordMapper extends BaseMapper<Record> {
}

WebSocketServer.java

注入RecordMapper,为了保存对局信息

@Component
// url链接:ws://127.0.0.1:3000/websocket/**
@ServerEndpoint("/websocket/{token}")  // 注意不要以'/'结尾
public class WebSocketServer {...private static UserMapper userMapper;public static RecordMapper recordMapper;...@Autowiredpublic void setRecordMapper(RecordMapper recordMapper) {WebSocketServer.recordMapper = recordMapper;}...}

Player.java

将玩家的蛇的方向偏移量转化成String

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player {...public String getStepsString() {StringBuilder res = new StringBuilder();for(int d : steps) {res.append(d);}return res.toString();}
}

Game.java

将对局信息保存至数据库

public class Game extends Thread {...private String getMapString() {StringBuilder res = new StringBuilder();for(int i = 0; i < rows; i++) {for(int j = 0; j < cols; j++) {res.append(g[i][j]);}}return res.toString();}private void saveToDataBase() {Record record = new Record(null,playerA.getId(),playerA.getSx(),playerA.getSy(),playerB.getId(),playerB.getSx(),playerB.getSy(),playerA.getStepsString(),playerB.getStepsString(),getMapString(),loser,new Date());WebSocketServer.recordMapper.insert(record);}private void sendResult() {     // 向两名玩家发送游戏结果...saveToDataBase();senAllMessage(resp.toJSONString());}...
}

完成~~

实现微服务:匹配系统(中)相关推荐

  1. .NET Core微服务 权限系统+工作流(二)工作流系统

    一.前言 接上一篇 .NET Core微服务 权限系统+工作流(一)权限系统 ,再来一发 工作流,我在接触这块开发的时候一直好奇它的实现方式,翻看各种工作流引擎代码,探究其实现方式,个人总结出来一个核 ...

  2. 一个微服务业务系统的中台构建之路

    一个微服务业务系统的中台构建之路 中台是近两年软件开发领域的热点话题,相关的文章也成为了各个技术社区和媒体争相报道的网红内容.作为企业支撑业务开发的核心系统,中台的重要性不言而喻,很多企业也开始尝试中 ...

  3. SpringCloud - 微服务架构系统

    过渡分布式微服务 以前的架构还是最原始阶段,官网.论坛.云平台等应用即一台服务器搞定一切.对应的web服务器.数据库.静态文件资源等,部署到一台服务器上即可.一般每秒几百请求没啥问题,结合内核参数调优 ...

  4. springcloud 整合 gateway_GitHub上最火的SpringCloud微服务商城系统项目,附全套教程

    项目介绍 mall-swarm是一套微服务商城系统,采用了 Spring Cloud Greenwich.Spring Boot 2.MyBatis.Docker.Elasticsearch等核心技术 ...

  5. 获取网关_阿里二面问了这道题:如何设计一个微服务网关系统

    有一天隔壁组的小王灰头土脸的跑过来,问我说:"李哥,你会设计微服务网关系统吗?".我一愣,小王怎么突然问这么抽象的问题,关键是我们最近也没有这样的需求.吃午饭的时候,在我旁敲侧击的 ...

  6. 微服务分布式架构中,如何实现日志链路跟踪

    摘要:接口设计出来返回结果值和编码,还有哪些是需要我们优化的结果参数?微服务分布式架构中,如何实现日志链路跟踪? 本文分享自华为云社区<微服务分布式架构中,如何实现日志链路跟踪?>,作者: ...

  7. 对微服务监控系统分层和监控架构的理解

    对微服务监控系统分层和监控架构的理解 目录 微服务专栏地址 目录 1. 简介 2. 为什么需要监控体系 3. 与单体应用有什么区别 4. 要监控什么 5. 监控体系和分层 6. 监控架构和主流技术栈 ...

  8. 图数据库和知识图谱在微财风控系统中的探索和应用

    来源:AI前线 本文约3500字,建议阅读7分钟 本文为你介绍图数据库作为复杂关系网络分析的一个强有力的工具在微财风控系统中的探索和应用. 近年来随着监管力度的不断提升,金融机构业务的不断发展,交易方 ...

  9. mall-swarm是一套微服务商城系统

    介绍: mall-swarm是一套微服务商城系统,采用了 Spring Cloud Hoxton & Alibaba.Spring Boot 2.3.Oauth2.MyBatis.Elasti ...

  10. mall-swarm微服务商城系统

    mall-swarm是一套微服务商城系统,采用了 Spring Cloud 2021 & Alibaba.Spring Boot 2.7.Oauth2.MyBatis.Docker.Elast ...

最新文章

  1. (转)浅谈HTML5与css3画饼图!
  2. 保障粮食安全-农业大健康-温铁军 谋定落实粮食安全责任
  3. 实验一 小凡和VMware虚拟机的使用练习
  4. mysql设置catalog_catalog恢复目录介绍和配置
  5. 方差为什么用平方不用绝对值_为什么炖鸡汤,人们喜欢用母鸡而不用公鸡?原来差别这么大!...
  6. selenium 鼠标悬浮_处理Selenium3+python3定位鼠标悬停才显示的元素
  7. python多线程下载器_用 python 实现一个多线程网页下载器
  8. Nginx的应用之虚拟主机
  9. swift 富文本编辑_如何使用Swift构建协作式文本编辑器
  10. python len函数_你需要了解的最重要的Python概念
  11. mplayer-ww-37356 compile with mingw gcc 4.5.1 修复无法播放wmv
  12. uniapp实现的购物列表左右联动功能
  13. 笔记之_Java整理IO流
  14. CommandArgument 与 CommandName 属性
  15. DELL存储SCv3020组件概念
  16. 根据北上资金操作上证50指数基金
  17. 矩阵分析(1)--一些基本概念
  18. 2012年度十大杰出IT博客之 蒋金楠
  19. veins安装及运行笔记
  20. python 安装dmPython

热门文章

  1. 输入nvidia-smi不能查看显卡NVIDIA型号的解决办法及快速查看电脑显卡NVIDIA型号信息
  2. 【ES6】ES6超详讲解(@_@;)
  3. A矩阵与B矩阵相似充要条件?
  4. Gem5的FS(全系统)模拟
  5. 文献检索、阅读与管理方法分享(持续更新ing...)
  6. java mathematica_Java和Mathematica交互
  7. 《炬丰科技-半导体工艺》化学清洗过程中重金属污染的监测方法
  8. 为什么程序员都喜欢节后跳槽?内行人告诉你原因
  9. 够大牌才够质感:京东11.11数读产品品质化新趋
  10. 训练小米叫,让狗狗叫还真的不容易!