第七章---Bot代码的执行

2023-12-24 13:36
文章标签 代码 执行 第七章 bot

本文主要是介绍第七章---Bot代码的执行,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.流程设计

在这里插入图片描述
接下来要实现的是,Bot代码执行的微服务部分。

在这里插入图片描述

2.初始化

相应的,首先要创建该服务的后端。

在这里插入图片描述
然后将matchingsystem模块的依赖直接复制过来

<dependencies><!--Spring Security--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId><version>2.7.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--spring-cloud-dependencies--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>2021.0.3</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!--Project Lombok--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.24</version><scope>provided</scope></dependency></dependencies>

同时在BotRunningSystem项目中添加依赖joor-java-8(Maven仓库地址):用于动态的编译和执行代码

在这里插入图片描述
为了如果拓展为实现其他语言,可以在云端自动启动一个docker容器,来执行其他语言。

在这里插入图片描述
重命名:
在这里插入图片描述
在这里插入图片描述
同时添加resources/application.properties文件,写入端口号

server.port=3002

3.后端API

首先要实现一个后端API,接收Bot代码,并将其加入到Bot运行池

实现后端API需要加入对应的controllerserviceservice.impl,以及添加ResTemplateConfig,并且在SecurityConfig中配置网关。

在这里插入图片描述
下面暂时写一些测试性的内容。

BotRunningService.java

在这里插入图片描述
BotRunningServiceImpl.java

在这里插入图片描述
BotRunningController.java

在这里插入图片描述
RestTemplateConfig.java

在这里插入图片描述
SecurityConfig.java,用于配置网关

在这里插入图片描述

4.修改前端

前端需要做一些修改,可以选择人工对战还是Bot参与对战。

并且,在client向server发请求时,如果是Bot参与对战,还需要指名bot_id

需要在匹配页面加入一个复选框

在这里插入图片描述
在BootStrap中找到相应工具

在这里插入图片描述
添加如下:

在这里插入图片描述
效果:

在这里插入图片描述
然后还需要动态的获取Bot列表,

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样,需要将用户选择了哪个bot告诉前端,引出需要做一个双向数据绑定。

在这里插入图片描述
在这里插入图片描述
这样将用户的选择与前端的变量就双向绑定了起来。

在这里插入图片描述

5.参数传递

然后需要在通信的时候,将user_id作为参数返回,并且后端也要相应的接收参数。

在这里插入图片描述
BackEnd端接收

在这里插入图片描述
在这里插入图片描述
BackEnd端向MatchingSystem端发送

在这里插入图片描述
在这里插入图片描述
MatchingSystem端接收

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样MatchingSystem端再向Backend端返回结果的时候,也需要发送一个botId

在这里插入图片描述
在这里插入图片描述
Backend接收参数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这次,整个发送bot_id的流程才算完整。

在这里插入图片描述

6.取到Bot信息

WebSocketServer.java

1)首先将BotMapper注入

private static BotMapper botMapper;
@Autowired
public void setBotMapper(BotMapper botMapper){WebSocketServer.botMapper = botMapper;
}

2)借助BotMapper将两个用户选择的bot取出

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
此时将bot的信息传入了Game中

在这里插入图片描述

7.Bot or not判断

取到了bot信息,创建完地图之后,在执行nextstep之前,判断botid是否等于-1,如果是-1,就要处理的是用户手动键入的指令,那么就等待用户输入;如果不等于-1,说明参与游戏的是Bot代码,则需要向BotRunningSystem发送消息,使其自动计算,并返回结果。

因此,需要在nextStep()中实现上述的判断。
在这里插入图片描述
如果是人工输入,则无需操作;如果是Bot参与,需要将用户id,bot代码,以及当前的局面传到RotRunningSystem系统的BotRunningController

在这里插入图片描述
其中,getInput(Player player)表示获取当前游戏局面的信息

在这里插入图片描述
此外,为了防止人工输入和bot执行混淆,还需要在执行bot的时候,屏蔽掉用户的输入。只有判断用户亲自出马的时候,才接收人的输入。

在这里插入图片描述
测试如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到,经过了漫长的传递过程,此时bot的信息,终于传到了BotRunningSystem

在这里插入图片描述

8.Bot微服务

接下来就是本节的重点,也就是实现Bot Running System微服务。

8.1生产者—消费者模式

这部分的工作在于,不断的接收用户的输入,将接收到的代码放在一个队列里面,也就是队列中存储当前所有的任务。每接收一个来自生产者的任务过来,就将其放在队列里。BotPool相当于消费者,每完成一个任务,检查一下队列是否为空,如果队列不空,就从队头取出代码执行。执行完之后继续检查。

MatchingPool中的循环,每循环一次,sleep一秒钟,但BotPool中的循环,为了保证用户体验,需要满足一旦有任务,立即执行。执行完之后,如果队列为空,就继续等待。因此两者循环的实现逻辑不一样,后者用到条件变量。

首先实现消费者线程及其流程。

8.2消费者线程

1)如果任务队列为空,就要将其阻塞,当有任务出现时,就要发生信号量,将其唤醒。因此需要用到条件变量。

使用condition.await()将当前线程阻塞
Causes the current thread to wait until it is signalled or interrupted.
(导致当前线程等待,直到发出信号或中断。)

2)此外还需要队列,来存储Bot,定义一个Bot类,

在这里插入图片描述
并定义一个存储Bot对象的队列Queue<Bot>

生产者和消费者都会对Queue<Bot>进行操作,因此处理的时候需要加锁。

3)在消费Bot对象之前,一点要先解锁,否则往队列添加Bot对象的时候就会被阻塞,但完全没有必要,因为没有读写冲突。

代码如下:

在这里插入图片描述
其中,如果队列为空,线程将会被阻塞。当addBot()被调用,队列中添加新的任务时,线程将会被唤醒

在这里插入图片描述
BotPool线程的存储,以及关于添加Bot的调用,均放在BotRunningServiceImpl

在这里插入图片描述
在这里插入图片描述
与匹配系统一样,也是在BootStrap服务启动的时候,启动BotPool线程。
在这里插入图片描述

8.3consum操作

这里只是简单的实现Java代码的编译和执行。后续如果需要添加安全验证或者支持其他语言,只需要修改consum函数即可。对于安全验证,也就是防止程序运行可能产生的危害,可以将其放在沙箱中运行。对于支持其他语言,可以将consum函数改为对docker的执行(Java中执行终端命令,将终端命令的执行放进docker即可)

这里使用Java中的一个工具Joor,可以动态编译和执行Java代码。

为了让整个执行过程时间可控,每执行一段代码,就需要将其放在一个线程中,线程可以支持如果超时就会断掉的操作。新建一个Consumer类用于表示这种线程。

在这里插入图片描述

然后在botpoolconsum函数中,创建一个Consumer对象,并调用对象的startTimeout方法。

在这里插入图片描述
再回到Consumer类的run()中,需要使用到joor.Reflect类来动态编译执行一段代码

在这里插入图片描述
在这里插入图片描述
不过这里有个问题,在动态编译过程中,如果是重名的类,只会编译一次。但是对于每一个任务代码,都应该重新编译一遍,因此,需要在类名之前,添加一个随机字符串,来保证类不一样。

下面这段代码,就可以实现从前端动态接收一段代码,并动态编译一遍。

在这里插入图片描述

8.4测试Bot代码

1)1号玩家的Bot,返回0,表示向上走

在这里插入图片描述
2)6号玩家,返回2,表示向下走

在这里插入图片描述
在这里插入图片描述
解决一个空指针异常

在这里插入图片描述
修改依赖

在这里插入图片描述
这样,在控制台中就能看到输出结果

在这里插入图片描述
表示1号玩家往上移动,6号玩家往下移动。

此时Bot信息就传递到了consumer(Joor)

在这里插入图片描述
接下来一步要考虑的,就是将Bot代码执行的结果,返回给3000断开的主后端服务器,最终传到nextstep中

在这里插入图片描述

9.Bot结果返回

9.1Backend API

为了接收consumer中计算的结果,我们要在主服务器中实现一个新的API。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
取出bot所对应的玩家userId和操作direction,然后需要玩家的操作传递到setNextStep

参考WebSocketServer中的move(玩家操作传递到setNextStep)

在这里插入图片描述

9.2setNextStep

在这里插入图片描述
这样就能实现,用户Bot生成的操作,通过Server传递给Game

在这里插入图片描述
现在只需要实现consumer动态编译Bot代码的结果返回给主服务器StartGameController

9.3结果返回

RestTemplate注入通过@Component到当前的Consumer

在这里插入图片描述
通过RestTemplate将结果返回到主服务器(主服务器中的StartGameController接收)

在这里插入图片描述
此时,整个流程就已经打通,通信过程完成闭环。

在这里插入图片描述

9.4功能测试

在这里插入图片描述
此时测试,两个Bot均可以实现自动执行。注意,先点击匹配的用户在左下角,后点匹配的在右上角。

可以修改使得左边用户返回1,则会一直往右走。

在这里插入图片描述
当然,可以实现人机对战,即左边蛇一直往右走(Bot运行)右边的蛇用户控制。

在这里插入图片描述

10.Bot编写

10.1设计过程

Bot代码的编写可以直接在IDEA中实现,之后将其复制到浏览器上。

在这里插入图片描述

这里实现一个稍微正常一点的AI,也就是在执行的时候判断上下左右哪一步可以走。

对input进行解码

String[] str = input.split("#");
String map = str[0];// 取出地图
int aSx = Integer.parseInt(str[1]), aSy = Integer.parseInt(str[2]);//取出我方起点坐标
String aSteps = str[3];// 取出我方操作
int bSx = Integer.parseInt(str[4]), bSy = Integer.parseInt(str[5]);//取出对手起点坐标
String bSteps = str[6];// 取出对手操作

在这里插入图片描述
1)取出地图

// 取出地图
int[][] g = new int[13][14];
int k = 0;
for (int i = 0; i < 13; i++) {for (int j = 0; j < 14; j++) {if(str[0].charAt(k) == '1')g[i][j] = 1;k++;}
}

2)取出两条蛇的路径

直接用之前在Play.java中写过的代码即可

在这里插入图片描述

//检验当前回合 蛇的长度是否增加
private  boolean check_tail_increasing(int step){if(step <= 10) return true;else return step % 3 == 1;
}
//返回蛇的身体
public List<Cell> getCells(int sx, int sy, String steps){List<Cell> res = new ArrayList<>();//对于四种操作0(w), 1(d), 2(s), 3(a)// 在行和列方向上的计算偏移量int[] dx = {-1, 0, 1, 0};int[] dy = {0, 1, 0, -1};int x = sx;int y = sy;int step = 0;//回合数char[] snacksteps = steps.toCharArray();res.add(new Cell(x,y));//添加起点//不断根据steps计算出整个蛇身体for (Character d : snacksteps) {x += dx[d - '0'];y += dy[d - '0'];res.add(new Cell(x,y));if(!check_tail_increasing(++step)){//如果蛇尾不增加 就删掉蛇尾res.remove(0);//O(N)}}return res;
}//取出蛇的轨迹
List<Cell> aCells = getCells(aSx, aSy, aSteps);
List<Cell> bCells = getCells(bSx, bSy, bSteps);for(Cell c : aCells) g[c.x][c.y] = 1;
for (Cell c : bCells) g[c.x][c.y] = 1;

3)判断可行的移动方向

枚举一下上右下左四个方向,一旦发现可以走,就设定移动方向。

// 判断可行的移动方向
// 对于四种方向0(↑), 1(→), 2(↓), 3(←)
// 在行和列方向上的计算偏移量
int[] dx = {-1, 0, 1, 0};
int[] dy = {0, 1, 0, -1};for (int i = 0; i < 4; i++) {int x = aCells.get(aCells.size() - 1).x + dx[i];//下一处xint y = aCells.get(aCells.size() - 1).y + dy[i];//下一处yif(x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0)return i;
}
return 0;//如果没有可行的方向 向上走--灭亡
10.2代码实现
package com.kob.botrunningsystem.utils;import java.util.ArrayList;
import java.util.List;public class Bot implements com.kob.botrunningsystem.utils.BotInterface{public static class Cell{private final int x;private final int y;public Cell(int x, int y) {this.x = x;this.y = y;}}//检验当前回合 蛇的长度是否增加private  boolean check_tail_increasing(int step){if(step <= 10) return true;else return step % 3 == 1;}//返回蛇的身体public List<Cell> getCells(int sx, int sy, String steps){List<Cell> res = new ArrayList<>();//对于四种操作0(w), 1(d), 2(s), 3(a)// 在行和列方向上的计算偏移量int[] dx = {-1, 0, 1, 0};int[] dy = {0, 1, 0, -1};int x = sx;int y = sy;int step = 0;//回合数char[] snacksteps = steps.toCharArray();res.add(new Cell(x,y));//添加起点//不断根据steps计算出整个蛇身体for (Character d : snacksteps) {x += dx[d - '0'];y += dy[d - '0'];res.add(new Cell(x,y));if(!check_tail_increasing(++step)){//如果蛇尾不增加 就删掉蛇尾res.remove(0);//O(N)}}return res;}@Overridepublic Integer nextMove(String input) {// 对input解码String[] str = input.split("#");String map = str[0];// 取出地图int aSx = Integer.parseInt(str[1]), aSy = Integer.parseInt(str[2]);//取出我方起点坐标String aSteps = str[3];// 取出我方操作int bSx = Integer.parseInt(str[4]), bSy = Integer.parseInt(str[5]);//取出对手起点坐标String bSteps = str[6];// 取出对手操作// 取出地图int[][] g = new int[13][14];int k = 0;for (int i = 0; i < 13; i++) {for (int j = 0; j < 14; j++) {if(map.charAt(k) == '1')g[i][j] = 1;k++;}}//取出蛇的轨迹List<Cell> aCells = getCells(aSx, aSy, aSteps);List<Cell> bCells = getCells(bSx, bSy, bSteps);for(Cell c : aCells) g[c.x][c.y] = 1;for (Cell c : bCells) g[c.x][c.y] = 1;// 判断可行的移动方向// 对于四种方向0(↑), 1(→), 2(↓), 3(←)// 在行和列方向上的计算偏移量int[] dx = {-1, 0, 1, 0};int[] dy = {0, 1, 0, -1};for (int i = 0; i < 4; i++) {int x = aCells.get(aCells.size() - 1).x + dx[i];//下一处xint y = aCells.get(aCells.size() - 1).y + dy[i];//下一处yif(x >= 0 && x < 13 && y >= 0 && y < 14 && g[x][y] == 0)return i;}return 0;//如果没有可行的方向 向上走--灭亡}
}
10.3应用

将两个bot修改替换为上面的代码

在这里插入图片描述
如果需要对Bot代码需要调试,只能通过println的方式,添加到原来bot中。

在这里插入图片描述
对于操作而言,需要去掉两端的括号。

此时测试,成功!

在这里插入图片描述
也可以人机对战

在这里插入图片描述
至此,这部分的代码全部完成。

这篇关于第七章---Bot代码的执行的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/531944

相关文章

uniapp接入微信小程序原生代码配置方案(优化版)

uniapp项目需要把微信小程序原生语法的功能代码嵌套过来,无需把原生代码转换为uniapp,可以配置拷贝的方式集成过来 1、拷贝代码包到src目录 2、vue.config.js中配置原生代码包直接拷贝到编译目录中 3、pages.json中配置分包目录,原生入口组件的路径 4、manifest.json中配置分包,使用原生组件 5、需要把原生代码包里的页面修改成组件的方

公共筛选组件(二次封装antd)支持代码提示

如果项目是基于antd组件库为基础搭建,可使用此公共筛选组件 使用到的库 npm i antdnpm i lodash-esnpm i @types/lodash-es -D /components/CommonSearch index.tsx import React from 'react';import { Button, Card, Form } from 'antd'

17.用300行代码手写初体验Spring V1.0版本

1.1.课程目标 1、了解看源码最有效的方式,先猜测后验证,不要一开始就去调试代码。 2、浓缩就是精华,用 300行最简洁的代码 提炼Spring的基本设计思想。 3、掌握Spring框架的基本脉络。 1.2.内容定位 1、 具有1年以上的SpringMVC使用经验。 2、 希望深入了解Spring源码的人群,对 Spring有一个整体的宏观感受。 3、 全程手写实现SpringM

代码随想录算法训练营:12/60

非科班学习算法day12 | LeetCode150:逆波兰表达式 ,Leetcode239: 滑动窗口最大值  目录 介绍 一、基础概念补充: 1.c++字符串转为数字 1. std::stoi, std::stol, std::stoll, std::stoul, std::stoull(最常用) 2. std::stringstream 3. std::atoi, std

记录AS混淆代码模板

开启混淆得先在build.gradle文件中把 minifyEnabled false改成true,以及shrinkResources true//去除无用的resource文件 这些是写在proguard-rules.pro文件内的 指定代码的压缩级别 -optimizationpasses 5 包明不混合大小写 -dontusemixedcaseclassnames 不去忽略非公共

麻了!一觉醒来,代码全挂了。。

作为⼀名程序员,相信大家平时都有代码托管的需求。 相信有不少同学或者团队都习惯把自己的代码托管到GitHub平台上。 但是GitHub大家知道,经常在访问速度这方面并不是很快,有时候因为网络问题甚至根本连网站都打不开了,所以导致使用体验并不友好。 经常一觉醒来,居然发现我竟然看不到我自己上传的代码了。。 那在国内,除了GitHub,另外还有一个比较常用的Gitee平台也可以用于

众所周知,配置即代码≠基础设置即代码

​前段时间翻到几条留言,问: “配置即代码和基础设施即代码一样吗?” “配置即代码是什么?怎么都是基础设施即代码?” 我们都是知道,DevOp的快速发展,让服务器管理与配置的时间大大减少,配置即代码和基础设施即代码作为DevOps的重要实践,在其中起到了关键性作用。 不少人将二者看作是一件事,配置即大代码是关于管理特定的应用程序配置设置本身,而基础设施即代码更关注的是部署支持应用程序环境所需的

Redis-在springboot环境下执行lua脚本

文章目录 1、什么lua2、创建SpringBoot工程3、引入相关依赖4、创建LUA脚本5、创建配置类6、创建启动类7、创建测试类 1、什么lua “Lua”的英文全称是“Lightweight Userdata Abstraction Layer”,意思是“轻量级用户数据抽象层”。 2、创建SpringBoot工程 3、引入相关依赖 <?xml version

js小题:通过字符串执行同名变量怎么做

在JavaScript中,你不能直接使用一个字符串来直接引用一个变量,因为JavaScript是一种静态类型语言(尽管它的类型在运行时可以变化),变量的名字在编译时就被确定了。但是,有几种方法可以实现类似的功能: 使用对象(或Map)来存储变量: 你可以使用一个对象来存储你的变量,然后使用字符串作为键来访问这些变量。 let myVars = { 'var1': 'Hello', 'var

53、Flink Interval Join 代码示例

1、概述 interval Join 默认会根据 keyBy 的条件进行 Join 此时为 Inner Join; interval Join 算子的水位线会取两条流中水位线的最小值; interval Join 迟到数据的判定是以 interval Join 算子的水位线为基准; interval Join 可以分别输出两条流中迟到的数据-[sideOutputLeftLateData,