补充
-
JSON.Stringfy({})将一个 JavaScript 对象或值转换为 JSON 字符串
这里指的是js对象 可能不同语言之间 相同类型实际并不相同 通过这种方法转化为
另外一种语言可以理解的对象 对应的JSONObject data=JSONObject.parseObject(message);
-
前后端通信都是以来的字符串 所以上一条才需要转化
-
elseif一定要写 要不然可能自己代码有bug呢
-
重启之后就可以很欢快的调试了
-
原则 变量小写 函数驼峰?
-
我们将game定义为thread 说明是将不同的比赛放进不同的线程
Game game=new Game(13,14,20,a.getId(),b.getId());//应该存到A和B的连接里 这里用局部变量代替
game.createMap();
当两名玩家成功匹配之后 我们就新建了一个类 执行完一些操作(将用户的比赛开始)
这里也体现了两个匹配成功的玩家 比赛相关的数据都是咸通的
就会开启这个game的线程 每一句单独的游戏 都会new一个这个类
users.get(a.getId()).game=game;
users.get(b.getId()).game=game;
game.start();
-
一定要写一点 调一点 不要一下子调试
-
延迟太大了 有可能我们输入时正在sleep 我们可以循环50次 每次100毫秒 总时间五秒不变
-
从上往下写 一步一步看哪个模块需要什么功能
-
代码十分钟 debug一小时
-
中英文的符号长度不一样 会导致格式不相同
-
写组件设计页面的时候 先写上文字 之后再替换就可以了
-
恋爱光是想想就很美好! 而恋爱对我来说也只能美好的想想了
debug
嗨哟 难受 前端不报错应该没有问题
出现非预期结果一定要着重看看后端
单词或者变量名都会在Idea里标识出来
debug1:ture 返回写成false
record对局记录-两个toString
public String getStepString(){
StringBuilder res=new StringBuilder();
for(int d:steps){
res.append(d);
}
return res.toString();
}//将步数转化为字符串
直接存储一维的就可以了
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();
添加数据库
查看讲义
新建表 record
新建pojo
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableId(type= IdType.AUTO)
private Integer id;
private Integer aId;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone="Asia/Shanghai")
private Date createtime;
新建mapper
@Mapper
public interface RecordMapper extends BaseMapper<Record> {
}
在websocketservier
public static RecordMapper recordMapper;
@Autowired
public void setRecordMapper(RecordMapper recordMapper){WebSocketServer.recordMapper=recordMapper;}
新建record对象
Record record=new Record(
null,/主键不写
playerA.getId(),
playerA.getSx(),
playerA.getSy(),
playerB.getId(),
playerB.getSx(),
playerB.getSy(),
playerA.getStepString(),//主要是这个函数 player下转换
playerB.getStepString(),
getMapString(),//定义
loser,
new Date()//直接新建一个时间
);
调用其服务端下的
WebSocketServer.recordMapper.insert(record);
判断为输
<div class="result-board-text" v-if="$store.state.pk.loser === 'all'">
Darw
</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>
这里要写三个if一个else 只有这样才能判断输赢
但是这里用的三个等号判断 会出现双赢的情况 将两个id输出到控制台发现
一个是数字 一个是字符串 如果类型不一样可以用两个等号 1:33:25
魔性笑声
所以在js里写三个等号 三个等号会比较类型 两个等号如果类型不相同 会自动转化为
字符串来进行比较
或者===parseint($store.state.user.id);
这样写会比较清楚
这个阶段 我们先匹配的是在左下角
计分板
设定位置为
position:absolute;//此时不再适合盒子模型
会按照设定的位置显示 z轴较高
top: 30vh;
left: 35vw;
注意这里的css写在组件内部 而不是pk页面里
scoped会锁定这个面板 分离隔开
页面只负责引入这个组件
css
text-align
居中都是 btn
text类
font-style: italic;
斜体
计算蛇的长度
C++一秒钟10的7次方~10的8次方 java 10的6次方不成问题
private boolean check_tail_increasing(int step){//检验蛇社么时候会变长 前后端统一 检验蛇的长度是否增长
if(step<=10) return false;//debug深夜30分钟 应该为true
return step%3==1;
}
public List<Cell> getCell(){//创建蛇的身体
List<Cell> res=new ArrayList<>();
int[] dx={-1,0,1,0},dy={0,1,0,-1};
int x=sx,y=sy;
int step=0;
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)){//不增长就移除尾巴
res.remove(0);///arrtlist O(n),linklist O1 很小所以没有差别
//求最后一个元素 arraylist 就是O1 的 linklist 就是On的
}
}
在前端反馈中有gameObject
我们创建了gamemap存进去
const game = store.state.pk.gameObject;
const [snake0, snake1] = game.snakes;//所以这里可以直接使用
意义上可以理解为指针
for(int i=0;i<50;i++){
try {
Thread.sleep(100);//这里等待循环时间变短 次数增多 服务器更加大压力
前端反馈
将GameMap存下来 用来访问
需要将gamemap存下来 在pk.js里面开辟gameObject
gameObject:null,
mutations:updateObject
onMounted(() => {//挂载完之后需要创建游戏对象
store.commit(
"updateGameObject",
new GameMap(canvas.value.getContext('2d'), parent.value, store)
)
});
后端接受
@OnMessage
public void onMessage(String message, Session session) {
JSONObject data=JSONObject.parseObject(message);//这个代表着所有发送过来的信息 解析过的
String event=data.getString("event");//这里定义的event
if("start-matching".equals(event)){//这里就是路由了
startMatching();
}else if("stop-matching".equals(event)){
stopMatching();
}else if("move".equals(event)){
move(data.getInteger("direction"));
}
// 从Client接收消息
}
抽象过程实例化
至此
我们使用webSocketServer进行路由
存取了session这个session是抽象的不具体的 我们新定义了一个session变量存下来
一个user用来保存这条连接对应的用户 一个game对象实例
Game存取已经匹配成功的用户a和用户b(只有成功匹配之后才会创建Game实例)
Game实例存取了用户a和用户b 地图 地图连接性 用户a下一步操作 用户b下一步操作
方向偏移量 地图行数列数 一个锁
Player类定义了用户id 起点终点 和步数
我们在OnOPen时将session储存了下来//前端存储的是socket
如果传来的token找不到对象 //此时封装了类 传入token传出userid
this.session.close() //这种方式关闭 应该是地址
前后端通信
这里把箭头删去(gamemapjs) 并且snake0.set_direction(3)
变成了 d=3;
这里是为了将操作具体化为变量 便于发送给后端
this.state.store.pk.socket.send(JSON.stringfy({})//把一个JSON转化为字符串
发送的都是字符串
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;
if(d>0){
this.store.state.pk.socket.send(JSON.stringify({
event:"move",
direction:d,
}))
}
move
写在WebSocketServier里边的
if(game.getPlayerA().getId().equals(user.getId())){
game.setNextStepA(direction);
}else if(game.getPlayerB().getId().equals(user.getId())){
game.setNextStepB(direction);
}
每一个连接对应着一个websocket对象 我们定义了user 在这里 用于存取
这个对象对应的用户 需要和game里边的对应起来
sendResult
private void sendResult(){//向两个玩家发送结果
JSONObject resp=new JSONObject();
resp.put("event","result");
resp.put("loser",loser);
sendAllResult(resp.toJSONString());//这里传递的是JSON对象
}
sendMove
lock.lock();//用到了nextStepA
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();
}
线程 如果两个线程操作同一个变量 至少有一个变量是写的话 就需要加锁 读读 不用 读写用 写写用
if之后
可以先写好函数名 把他放着 流程确定好
sendresult
知道的是playerA,palyerB存取着信息 里面有id 需要从外边获取users里面的连接
final public static ConcurrentHashMap<Integer,WebSocketServer> users=new ConcurrentHashMap<>();
WebSocketServer.users.get(playerA.getId()).sendMessage(message);
WebSocketServer中存在apisendMessage
users本身是一个map
问题
函数异常可以写在外边 但是 如果这样的话
调用这个函数的函数也需要抛异常
如果报异常了 后面语句就不执行了 直接跳转到catch或之后的finally
机制类似于goto
run
run操作 一步一步操作
不到两百格 每三步长一格 600步 循环1000次 一千步一定可以结束
这样做害怕写错了长死循环
这里边界情况是这里(5.2秒钟)没有获取到两条蛇的下一步(异常)
if(nextStep()){// 是否获取到两条蛇的下一步
}else {
status="finished";
status="finished";
lock.lock();
try{
if(nextStepA==null&&nextStepB==null){//判断标准是没有下一步操作
loser="all";
}else if(nextStepA==null){
loser="A";
}else {
loser="B";
}
}finally {
lock.unlock();
}//这里但凡
}
这里边界情况是这里(5.2秒钟)没有获取到两条蛇的下一步(异常)
但是有一种情况是 正在判断 (lock)之后却读到了 这种边界情况怎么办呢
在超时的边界输入 但是在else时突然读到了 可能在边界情况a和b都不是空的
不用管他 超时判断为输也是合理的 而且情况概率是非常低的
防止输入和纪录异常
前端蛇每秒走五个格子 如果两个玩家反映足够快
动一格期间发送了很多次请求 中间很多都会被覆盖掉 前端渲染会遗漏一些步数
前端只读nextStep 但是nextStepA的数组记录了很多次 前端
所以返回下一步操作的时候 一定先sleep一个最小值 让前端移动期间 不可输入 后端输入合法
前端渲染跟不上 后端sleep200毫秒(一秒钟走5次 假设每100毫秒就会收到信息)这样表现为蛇一直在走 但是
中间的隔100毫秒输入不合法 而这个拒绝不合法操作 就是让后端停200毫秒 在此期间 拒绝输入
ps 每秒走五个格子 一个格子对应一步操作 走完一步再走下一步
所以在进行下一步操作的时候 起码睡200毫秒 等待前端渲染完成
前端最多一秒走五个格子
锁
lock.lock();//涉及到的nextStep变量读写操作的都需要这样写 先锁起来
try{
//这里try catch是保险 如果发生异常 他不会解锁 就发生死锁了
}finally {
lock.unlock();//不管有没有报异常都解锁
}
等待玩家输入
Thread.sleep(1000);报错
悬浮 更多操作 使用try catch环绕// 这里必须trycatch 不然就报错
//lock不能在外面 死锁
for(int i=0;i<5;i++){//每一秒都拿过来看一看用户是否操作
try {
Thread.sleep(1000);
lock.lock();// 此时nextStepA不可读写
try{
if(nextStepA!=null&&nextStepB!=null){
playerA.getSteps().add(nextStepA);
playerB.getSteps().add(nextStepB);//这里的函数都是自己构造好的 getStep是返回了步数集合
return true;
}
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();//不要报异常 直接输出就好了
}
}
如果在外面锁住 锁住之后 等待五秒 这期间其他进程想要修改的时候
nextstep已经其他的线程拿住了 他不能被赋值仍然是空的 就死锁了
多线程事项
private Integer nextStepA=null;
private Integer nextStepB=null;//0123 上下左右四个方向
一定要下专业版
public void setNextStepA(Integer nextStepA){
this.nextStepA=nextStepA;
}//game内部存储下一步 和定义的接口
这里涉及到了两个线程同时读写一个变量的问题 一读一写
可能会发生读写冲突 有读写冲突我们需要加上锁
private ReentrantLock lock=new ReentrantLock();//定义在Game类 Thread
public void setNextStepA(Integer nextStepA){
lock.lock();// 操作之前先锁起来
try{
this.nextStepA=nextStepA;//这里涉及读写操作 可能出现异常
}finally{
lock.unlock();//不管有没有解锁都会解锁
}
这样写报异常的话也会解锁 不会出现死锁
}
game类多线程实现
只需要继承Thread alt+insert 重新方法 run函数
开启新线程执行的时候 新线程的入口函数就是run函数
game.start
进入一个新的线程 api 一回合一回合的操作
Game game=new Game(13,14,20,a.getId(),b.getId());//应该存到A和B的连接里 这里用局部变量代替
game.createMap();
users.get(a.getId()).game=game;
users.get(b.getId()).game=game;
game.start();
//这里有点陌生
实现思路
client代表浏览器
现在有了三个棋盘 两个浏览器和一个在云端需要保证状态同步
两个客户端分别发送请求之后 同步到两个客户端
流程
服务器维护一个game棋盘 棋盘有api nextStep 获取来源可以是客户端也可以是bot
当服务器收到信息后 再把结果发送给评测系统 判断是否合法 返回结果
问题
这里考虑的是一场对局 如果在这场对局还没有结束 另一场游戏只能等待第一场输入
另外一场体验就会很差
game不能作为单线程处理处理(线程的理解 线程一个人干事 多线程是多人做 )
获取用户输入是一个线程 评测是一个线程 多线程的通信
把地图相关的信息封装
封装成JSON逻辑上比较连贯
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());
JSONObject respA=new JSONObject();
respA.put("game",respGame);
这样两个玩家得到的信息就是一样的了
const data = JSON.parse(msg.data);
但是这里仅仅解析了一层 就可以直接使用game了
store.commit("updateGame", data.game);
这里需要注意
gamemap: null,
a_id: 0,
a_sx: 0,
a_sy: 0,
b_id: 0,
b_sx: 0,
b_sy: 0,
//这里开辟了几个成员变量和一个updateGame方法
新建玩家对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player{}
自动生成构造函数
private final Player playerA,playerB;//这里先可以不用初始化
Game(){
playerA =new Player(idA,rows-2,1,new ArrayList<>());
playerB=new Player(idB,1,cols-2,new ArrayList<>());//这里传入了一个空的集合
和数组的区别是可变长度
}
同步玩家位置
game加入player维护玩家位置
写项目用模块来写 缺什么补什么类成员或者方法
id,sx,sy,step 起始坐标 历史步数
ts是强类型的 容易维护一些 自己开发ts和js 没有太大区别
速查
陌生
注意
但凡
哎呀 又想起了我妈 总是贪图小便宜 想要总是被约束住了手脚
在姥姥面前总是担起一面 当年如果嫁给富贵人家 说不定就会有个不错的人生
而如今陪着我们 受苦受累 我能做的只有向前走 深夜留给我自己的感动 在
她需要我帮忙时 不要计较 听她的话和教导就是我的命运了 口中的儿子从未忘记爱你
简单朴素的爱不会被辜负 常回家给她打个电话 少玩点游戏安顿好自己
劳动了大半辈子 要是你累了就让我陪着你吧
sp-0118-6-2 匹配系统中
客户端向服务器两端通信 生成游戏地图
表头
Idea操 作 指 南:https://www.acwing.com/blog/content/25456/
每次都要点击的网址:https://www.acwing.com/blog/content/28250/
G i t B a s h : https://www.acwing.com/blog/content/22768/
WindowsIdea :https://www.acwing.com/blog/content/23868/
本节课讲义https://www.acwing.com/file_system/file/content/whole/index/content/6325603/