From 63dc49ccd76c75336c6f8594c81cdb92b031fbb3 Mon Sep 17 00:00:00 2001 From: flykhan Date: Fri, 10 Mar 2023 22:23:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=AF=B9=E5=B1=80=E5=BD=95?= =?UTF-8?q?=E5=83=8F=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/kob/backend/config/MybatisConfig.java | 18 ++ .../record/GetRecordListController.java | 24 +++ .../impl/record/GetRecordListServiceImpl.java | 68 ++++++ .../service/record/GetRecordListService.java | 8 + web/src/assets/scripts/GameMap.js | 78 +++++-- web/src/assets/scripts/Snake.js | 2 +- web/src/router/index.js | 10 + web/src/store/index.js | 2 + web/src/store/record.js | 46 ++++ web/src/views/pk/PkIndexView.vue | 3 +- web/src/views/ranklist/RanklistIndexView.vue | 2 +- web/src/views/record/RecordContentView.vue | 18 ++ web/src/views/record/RecordIndexView.vue | 197 +++++++++++++++++- 13 files changed, 448 insertions(+), 28 deletions(-) create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/config/MybatisConfig.java create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/controller/record/GetRecordListController.java create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/service/impl/record/GetRecordListServiceImpl.java create mode 100644 backendcloud/backend/src/main/java/com/kob/backend/service/record/GetRecordListService.java create mode 100644 web/src/store/record.js create mode 100644 web/src/views/record/RecordContentView.vue diff --git a/backendcloud/backend/src/main/java/com/kob/backend/config/MybatisConfig.java b/backendcloud/backend/src/main/java/com/kob/backend/config/MybatisConfig.java new file mode 100644 index 0000000..386cf01 --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/config/MybatisConfig.java @@ -0,0 +1,18 @@ +// 用于实现对局记录的分页功能 +package com.kob.backend.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class MybatisConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } +} \ No newline at end of file diff --git a/backendcloud/backend/src/main/java/com/kob/backend/controller/record/GetRecordListController.java b/backendcloud/backend/src/main/java/com/kob/backend/controller/record/GetRecordListController.java new file mode 100644 index 0000000..888bcde --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/controller/record/GetRecordListController.java @@ -0,0 +1,24 @@ +package com.kob.backend.controller.record; + +import com.alibaba.fastjson2.JSONObject; +import com.kob.backend.service.record.GetRecordListService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; + +@RestController +public class GetRecordListController { + @Autowired + private GetRecordListService getRecordListService; + + // 这里只需要获取,因此只需要用 get 方法(需要新建和修改数据时,使用 post 方法) + @GetMapping("/record/getlist/") + public JSONObject getList(@RequestParam Map data) { + // 解析出 page 信息 + Integer page = Integer.parseInt(data.get("page_index")); + return getRecordListService.getList(page); + } +} diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/impl/record/GetRecordListServiceImpl.java b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/record/GetRecordListServiceImpl.java new file mode 100644 index 0000000..8645f87 --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/impl/record/GetRecordListServiceImpl.java @@ -0,0 +1,68 @@ +// 实现对局信息分页发送 +package com.kob.backend.service.impl.record; + +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.kob.backend.mapper.RecordMapper; +import com.kob.backend.mapper.UserMapper; +import com.kob.backend.pojo.Record; +import com.kob.backend.pojo.User; +import com.kob.backend.service.record.GetRecordListService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.LinkedList; +import java.util.List; + +@Service +public class GetRecordListServiceImpl implements GetRecordListService { + @Autowired + private RecordMapper recordMapper; + + @Autowired + private UserMapper userMapper; // 注入 UserMapper 用于查询用户名和用户头像 + + @Override + public JSONObject getList(Integer page) { + // 使用 Mybatis API : IPage 实现 + IPage recordIPage = new Page<>(page, 10); // 参数列表:Page<>(传第几页, 每一页有多少项目) + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.orderByDesc("id"); // 通过 record 的 id 降序排序 + List records = recordMapper.selectPage(recordIPage, queryWrapper).getRecords(); + + + JSONObject resp = new JSONObject(); + List items = new LinkedList<>(); + for (Record record : records) { // 枚举对局记录 + // 获取用户 A 和 B + User userA = userMapper.selectById(record.getAId()); + User userB = userMapper.selectById(record.getBId()); + + // 取出 A 和 B 的用户名和头像信息 + String aName = userA.getUsername(), bName = userB.getUsername(); + String aPhoto = userA.getPhoto(), bPhoto = userB.getPhoto(); + + String winner = "平局!"; // 定义赢家 + + // 将 A 和 B 的用户名和头像信息存到 jsonObject 对象 item 中 + JSONObject item = new JSONObject(); + item.put("a_username", aName); + item.put("a_photo", aPhoto); + item.put("b_username", bName); + item.put("b_photo", bPhoto); + if ("A".equals(record.getLoser())) winner = userB.getUsername() + " 胜"; + else if ("B".equals(record.getLoser())) winner = userA.getUsername() + " 胜"; + item.put("winner", winner); // 存入赢家信息 + item.put("record", record); // 存入对战信息 + // 将 jsonObject 对象 item 中,添加到 items 列表中 + items.add(item); + } + + resp.put("records", items); // 将 items 信息存入 records 中 + resp.put("records_count", recordMapper.selectCount(null)); // 存入当前记录页面的总数 + + return resp; + } +} diff --git a/backendcloud/backend/src/main/java/com/kob/backend/service/record/GetRecordListService.java b/backendcloud/backend/src/main/java/com/kob/backend/service/record/GetRecordListService.java new file mode 100644 index 0000000..0577c3c --- /dev/null +++ b/backendcloud/backend/src/main/java/com/kob/backend/service/record/GetRecordListService.java @@ -0,0 +1,8 @@ +package com.kob.backend.service.record; + +import com.alibaba.fastjson2.JSONObject; + +public interface GetRecordListService { + // 参数:分页面列表编号 + JSONObject getList(Integer page); +} diff --git a/web/src/assets/scripts/GameMap.js b/web/src/assets/scripts/GameMap.js index b2eacaf..0b1a7b9 100644 --- a/web/src/assets/scripts/GameMap.js +++ b/web/src/assets/scripts/GameMap.js @@ -163,40 +163,72 @@ export class GameMap extends AcGameObject { // 添加监听:用于绑定键盘输入,以便获取用户操作控制蛇 add_listening_events() { - // 聚焦到获取输入的画布页面 - this.ctx.canvas.focus(); + // 如果是录像,则从数据库中的数据信息(历史存储的操作步骤)获取输入,并将之前的对局回放;如果不是录像,则获取用户输入 + if (this.store.state.record.is_record) { + let k = 0; + const a_steps = this.store.state.record.a_steps; // 取出两条蛇的历史步骤 + const b_steps = this.store.state.record.b_steps; + const loser = this.store.state.record.record_loser; + const [snake0, snake1] = this.snakes; // 取两条蛇的信息 + // setInterval 用于设定执行间隔 + const interval_id = setInterval(() => { + if (k >= a_steps.length - 1) { + // 如果下一步要死亡,则将死亡的蛇标记一下(最后一步不做移动,因此使用 steps.length - 1; 最后一步可以用来判断蛇眼方向) + // 修改最后一步(检测到碰撞后)蛇的眼睛方向 + snake0.eye_direction = parseInt(a_steps[k]); + snake1.eye_direction = parseInt(b_steps[k]); + if (loser === "all" || loser === "A") { + snake0.status = "die"; + } + if (loser === "all" || loser === "B") { + snake1.status = "die"; + } + // 死亡后要取消循环 + clearImmediate(interval_id); + } else { + snake0.set_direction(parseInt(a_steps[k])); // 修改下一步的运动方向 + snake1.set_direction(parseInt(b_steps[k])); + k++; // 递增下一步 + } + }, 300); // 每 300 毫秒获取下一步并执行一次 + } else { + // 聚焦到获取输入的画布页面 + this.ctx.canvas.focus(); - // 先取出两条蛇对象 - // const [snake0, snake1] = this.snakes; + // 先取出两条蛇对象 + // const [snake0, snake1] = this.snakes; - // 获取用户信息:绑定 keydown 事件 - this.ctx.canvas.addEventListener("keydown", (e) => { - // 取出移动的方向,需要将方向传给后端 - let d = -1; // -1 表示没有方向, 0-上, 1-右, 2-下, 3-左 + // 获取用户信息:绑定 keydown 事件 + this.ctx.canvas.addEventListener("keydown", (e) => { + // 取出移动的方向,需要将方向传给后端 + let d = -1; // -1 表示没有方向, 0-上, 1-右, 2-下, 3-左 - // 定义 snake0 的键盘绑定事件 - 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; + // 定义 snake0 的键盘绑定事件 + 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){ - // 使用 socket.send() 传递信息给后端,使用 JSON.stringify() 将一个 JSON 封装成一字符串 - this.store.state.pk.socket.send(JSON.stringify({ - event: "move", // 事件类型: move - direction : d, // 方向: d - })) - } + // 如果进行了合法的移动操作:向后端发送方向 + if (d >= 0) { + // 使用 socket.send() 传递信息给后端,使用 JSON.stringify() 将一个 JSON 封装成一字符串 + this.store.state.pk.socket.send( + JSON.stringify({ + event: "move", // 事件类型: move + direction: d, // 方向: d + }) + ); + } - /* + /* // 定义 snake1 的键盘绑定事件(前端调试时启用) 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); */ - }); + }); + } } start() { diff --git a/web/src/assets/scripts/Snake.js b/web/src/assets/scripts/Snake.js index 64bd1cd..477b08c 100644 --- a/web/src/assets/scripts/Snake.js +++ b/web/src/assets/scripts/Snake.js @@ -19,7 +19,7 @@ export class Snake extends AcGameObject { this.cells = [new Cell(info.r, info.c)]; this.next_cell = null; // 下一步的目标位置 - this.speed = 5; // 蛇的速度:每秒走五个格子 + this.speed = 5; // 蛇的速度:每秒走五个格子,每一步需要 200 ms // 定义蛇下一步的指令相关属性 // -1 表示没有指令, 0、 1、 2、 3 表示上右下左方向 diff --git a/web/src/router/index.js b/web/src/router/index.js index 1cbb329..1862d39 100644 --- a/web/src/router/index.js +++ b/web/src/router/index.js @@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from "vue-router"; import PkIndexView from "../views/pk/PkIndexView.vue"; import RanklistIndexView from "../views/ranklist/RanklistIndexView.vue"; import RecordIndexView from "../views/record/RecordIndexView.vue"; +import RecordContentView from "../views/record/RecordContentView.vue"; import UserBotIndexView from "../views/user/bot/UserBotIndexView.vue"; import NotFound from "../views/error/NotFound.vue"; import UserAccountLoginView from "@/views/user/account/UserAccountLoginView.vue"; @@ -47,6 +48,15 @@ const routes = [ requestAuth: true, }, }, + { + // 展示录像内容: :recordId 作为参数 + path: "/record/:recordId/", + name: "record_content", + component: RecordContentView, + meta: { + requestAuth: true, + }, + }, { path: "/user/bot/", name: "user_bot_index", diff --git a/web/src/store/index.js b/web/src/store/index.js index 02e4263..bf6d8d3 100644 --- a/web/src/store/index.js +++ b/web/src/store/index.js @@ -2,6 +2,7 @@ import { createStore } from "vuex"; import ModuleUser from "./user"; import ModuleBot from './bot'; import ModulePk from './pk'; +import ModuleRecord from './record'; export default createStore({ state: {}, @@ -12,5 +13,6 @@ export default createStore({ user: ModuleUser, bot: ModuleBot, pk: ModulePk, + record: ModuleRecord, }, }); diff --git a/web/src/store/record.js b/web/src/store/record.js new file mode 100644 index 0000000..fac3830 --- /dev/null +++ b/web/src/store/record.js @@ -0,0 +1,46 @@ +import $ from "jquery"; +import store from "."; + +export default { + state: { + is_record: false, // 当前页面是不是录像页面 + a_steps: "", // a 玩家的路径记录 + b_steps: "", + record_loser: "", + }, + getters: {}, + mutations: { + updateIsRecord(state, is_record) { + state.is_record = is_record; + }, + updateSteps(state, data) { + state.a_steps = data.a_steps; + state.b_steps = data.b_steps; + }, + updateRecordLoser(state, loser) { + state.record_loser = loser; + }, + }, + actions: { + getRecordList(context, data) { + // 获取 Bot 列表 + $.ajax({ + url: "http://localhost:3000/record/getlist/", + data: { + page_index: data.page, + }, + type: "GET", + headers: { + Authorization: "Bearer " + store.state.user.token, + }, + success(resp) { + data.success(resp); + }, + error(resp) { + data.error(resp); + }, + }); + }, + }, + modules: {}, +}; diff --git a/web/src/views/pk/PkIndexView.vue b/web/src/views/pk/PkIndexView.vue index 0d6f955..09bf63a 100644 --- a/web/src/views/pk/PkIndexView.vue +++ b/web/src/views/pk/PkIndexView.vue @@ -37,6 +37,7 @@ export default { }) store.commit("updateLoser", "none"); + store.commit("updateIsRecord", false); // 将 is_record 改为 false, 表示当前页面不是录像页面 // 新建 WebSocket socket = new WebSocket(socketUrl); @@ -89,7 +90,7 @@ export default { snake1.eye_direction = data.b_eyes_finally_direction; // 更新 loser 信息,存储到全局 store 中 - store.commit("updateLoser",data.loser); + store.commit("updateLoser", data.loser); // 更新 loser 的 snake 对象 status 状态 if (data.loser === "all" || data.loser === "A") { diff --git a/web/src/views/ranklist/RanklistIndexView.vue b/web/src/views/ranklist/RanklistIndexView.vue index 9d51d53..47a3f93 100644 --- a/web/src/views/ranklist/RanklistIndexView.vue +++ b/web/src/views/ranklist/RanklistIndexView.vue @@ -1,5 +1,5 @@ + + + \ No newline at end of file diff --git a/web/src/views/record/RecordIndexView.vue b/web/src/views/record/RecordIndexView.vue index 47a3f93..26b8a9e 100644 --- a/web/src/views/record/RecordIndexView.vue +++ b/web/src/views/record/RecordIndexView.vue @@ -1,15 +1,208 @@ - +