diff --git a/acapp/package-lock.json b/acapp/package-lock.json index 9abb3f6..2bfbd02 100644 --- a/acapp/package-lock.json +++ b/acapp/package-lock.json @@ -8,8 +8,13 @@ "name": "acapp", "version": "0.1.0", "dependencies": { + "@popperjs/core": "^2.11.6", + "bootstrap": "^5.2.0", "core-js": "^3.8.3", + "jquery": "^3.6.1", "vue": "^3.2.13", + "vue-router": "^4.0.3", + "vue3-ace-editor": "^2.2.2", "vuex": "^4.0.0" }, "devDependencies": { @@ -17,6 +22,7 @@ "@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", @@ -1924,6 +1930,11 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "node_modules/@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, "node_modules/@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz", @@ -3108,6 +3119,11 @@ "node": ">= 0.6" } }, + "node_modules/ace-builds": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/ace-builds/-/ace-builds-1.10.1.tgz", + "integrity": "sha512-w8Xj6lZUtOYAquVYvdpZhb0GxXrZ+qpVfgj5LP2FwUbXE8fPrCmfu86FjwOiSphx/8PMbXXVldFLD2+RIXayyA==" + }, "node_modules/acorn": { "version": "8.7.1", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.7.1.tgz", @@ -3562,6 +3578,14 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/bootstrap": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/bootstrap/-/bootstrap-5.2.0.tgz", + "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==", + "peerDependencies": { + "@popperjs/core": "^2.11.5" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -6566,6 +6590,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, "node_modules/js-message": { "version": "1.0.7", "resolved": "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz", @@ -8816,6 +8845,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz", @@ -10120,6 +10154,17 @@ "node": ">=8" } }, + "node_modules/vue-router": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.5.tgz", + "integrity": "sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==", + "dependencies": { + "@vue/devtools-api": "^6.1.4" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -10142,6 +10187,16 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vue3-ace-editor": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/vue3-ace-editor/-/vue3-ace-editor-2.2.2.tgz", + "integrity": "sha512-fZ6OWosbU+odLrtrcGC/536QjCigujYJB0Hf6/tBp+ef/ohTadwQAqyBlVzOmvrmzZyubphpV9zkaZcx5Fuivw==", + "dependencies": { + "ace-builds": "^1.4.13", + "resize-observer-polyfill": "^1.5.1", + "vue": "^3.2.26" + } + }, "node_modules/vuex": { "version": "4.0.2", "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", @@ -12191,6 +12246,11 @@ "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", "dev": true }, + "@popperjs/core": { + "version": "2.11.6", + "resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.6.tgz", + "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + }, "@sideway/address": { "version": "4.1.4", "resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz", @@ -13212,6 +13272,11 @@ "negotiator": "0.6.3" } }, + "ace-builds": { + "version": "1.10.1", + "resolved": "https://registry.npmmirror.com/ace-builds/-/ace-builds-1.10.1.tgz", + "integrity": "sha512-w8Xj6lZUtOYAquVYvdpZhb0GxXrZ+qpVfgj5LP2FwUbXE8fPrCmfu86FjwOiSphx/8PMbXXVldFLD2+RIXayyA==" + }, "acorn": { "version": "8.7.1", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.7.1.tgz", @@ -13572,6 +13637,11 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "bootstrap": { + "version": "5.2.0", + "resolved": "https://registry.npmmirror.com/bootstrap/-/bootstrap-5.2.0.tgz", + "integrity": "sha512-qlnS9GL6YZE6Wnef46GxGv1UpGGzAwO0aPL1yOjzDIJpeApeMvqV24iL+pjr2kU4dduoBA9fINKWKgMToobx9A==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -15959,6 +16029,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "jquery": { + "version": "3.6.1", + "resolved": "https://registry.npmmirror.com/jquery/-/jquery-3.6.1.tgz", + "integrity": "sha512-opJeO4nCucVnsjiXOE+/PcCgYw9Gwpvs/a6B1LL/lQhwWwpbVEVYDZ1FokFr8PRc7ghYlrFPuyHuiiDNTQxmcw==" + }, "js-message": { "version": "1.0.7", "resolved": "https://registry.npmmirror.com/js-message/-/js-message-1.0.7.tgz", @@ -17672,6 +17747,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.1.tgz", @@ -18729,6 +18809,14 @@ } } }, + "vue-router": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.1.5.tgz", + "integrity": "sha512-IsvoF5D2GQ/EGTs/Th4NQms9gd2NSqV+yylxIyp/OYp8xOwxmU8Kj/74E9DTSYAyH5LX7idVUngN3JSj1X4xcQ==", + "requires": { + "@vue/devtools-api": "^6.1.4" + } + }, "vue-style-loader": { "version": "4.1.3", "resolved": "https://registry.npmmirror.com/vue-style-loader/-/vue-style-loader-4.1.3.tgz", @@ -18753,6 +18841,16 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vue3-ace-editor": { + "version": "2.2.2", + "resolved": "https://registry.npmmirror.com/vue3-ace-editor/-/vue3-ace-editor-2.2.2.tgz", + "integrity": "sha512-fZ6OWosbU+odLrtrcGC/536QjCigujYJB0Hf6/tBp+ef/ohTadwQAqyBlVzOmvrmzZyubphpV9zkaZcx5Fuivw==", + "requires": { + "ace-builds": "^1.4.13", + "resize-observer-polyfill": "^1.5.1", + "vue": "^3.2.26" + } + }, "vuex": { "version": "4.0.2", "resolved": "https://registry.npmmirror.com/vuex/-/vuex-4.0.2.tgz", diff --git a/acapp/package.json b/acapp/package.json index 6f96cdf..f2adf41 100644 --- a/acapp/package.json +++ b/acapp/package.json @@ -8,8 +8,13 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@popperjs/core": "^2.11.6", + "bootstrap": "^5.2.0", "core-js": "^3.8.3", + "jquery": "^3.6.1", "vue": "^3.2.13", + "vue-router": "^4.0.3", + "vue3-ace-editor": "^2.2.2", "vuex": "^4.0.0" }, "devDependencies": { @@ -17,6 +22,7 @@ "@babel/eslint-parser": "^7.12.16", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", + "@vue/cli-plugin-router": "~5.0.0", "@vue/cli-plugin-vuex": "~5.0.0", "@vue/cli-service": "~5.0.0", "eslint": "^7.32.0", diff --git a/acapp/src/App.vue b/acapp/src/App.vue index 591a031..b541347 100644 --- a/acapp/src/App.vue +++ b/acapp/src/App.vue @@ -1,26 +1,75 @@ - diff --git a/acapp/src/assets/images/background.png b/acapp/src/assets/images/background.png new file mode 100644 index 0000000..ddae80f Binary files /dev/null and b/acapp/src/assets/images/background.png differ diff --git a/acapp/src/assets/logo.png b/acapp/src/assets/logo.png deleted file mode 100644 index f3d2503..0000000 Binary files a/acapp/src/assets/logo.png and /dev/null differ diff --git a/acapp/src/assets/scripts/AcGameObject.js b/acapp/src/assets/scripts/AcGameObject.js new file mode 100644 index 0000000..614a0b1 --- /dev/null +++ b/acapp/src/assets/scripts/AcGameObject.js @@ -0,0 +1,50 @@ +const AC_GAME_OBJECTS = []; + +export class AcGameObject { + constructor() { + AC_GAME_OBJECTS.push(this); // 数组添加元素 + this.timedelta = 0; // 注意单位是毫秒,转化为秒需要除于1000 + this.has_called_start = false; + } + + start() { // 只执行一次 + } + + update() { // 每一帧执行一次,除了第一帧之外 + + } + + on_destroy() { // 删除之前执行 + + } + + destroy() { + this.on_destroy(); + + for (let i in AC_GAME_OBJECTS) { // 遍历数组下标 + const obj = AC_GAME_OBJECTS[i]; + if (obj === this) { // 注意是三个等号 + AC_GAME_OBJECTS.splice(i); // 从数组中移除元素 + break; + } + } + } +} + +let last_timestamp; // 上一次执行的时刻 +const step = timestamp => { + for (let obj of AC_GAME_OBJECTS) { // 遍历数组的值 + if (!obj.has_called_start) { // 如果对象没有被调用 + obj.has_called_start = true; + obj.start(); + } else { + obj.timedelta = timestamp - last_timestamp; // 获得当前帧和上一帧的时间差 + obj.update(); + } + } + + last_timestamp = timestamp; // 将当前时间更新为上一帧执行时刻 + requestAnimationFrame(step) // 递归调用该函数 +} + +requestAnimationFrame(step) \ No newline at end of file diff --git a/acapp/src/assets/scripts/Cell.js b/acapp/src/assets/scripts/Cell.js new file mode 100644 index 0000000..2def2d3 --- /dev/null +++ b/acapp/src/assets/scripts/Cell.js @@ -0,0 +1,9 @@ +export class Cell{ + constructor(r,c) { // 传入蛇的坐标(r,c) + this.r = r; + this.c = c; + this.x = c + 0.5; // 将蛇的左上角坐标(r,c)转化为画布上的圆心坐标(x,y) + this.y = r + 0.5; + } + +} \ No newline at end of file diff --git a/acapp/src/assets/scripts/GameMap.js b/acapp/src/assets/scripts/GameMap.js new file mode 100644 index 0000000..f4c0d69 --- /dev/null +++ b/acapp/src/assets/scripts/GameMap.js @@ -0,0 +1,155 @@ +import { AcGameObject } from "./AcGameObject"; // 导入js的export class +import { Snake } from "./Snake"; +import { Wall } from "./Wall"; + +export class GameMap extends AcGameObject { + constructor(ctx, parent, store) { + super(); // 继承类一直要先调用父类的构造函数 + + this.ctx = ctx; + this.parent = parent; + this.L = 0; + + this.rows = 13; + this.cols = 14; + + this.store = store; + + this.inner_walls_count = 20; // 障碍物的数量(最大建议80) + this.walls = []; // 所有障碍物组成的数组 + + // 创建两条蛇 + + this.snakes = [ // 蓝蛇和红蛇 + new Snake({ id: 0, color: "#4876EC", r: this.rows - 2, c: 1 }, this), + new Snake({ id: 1, color: "#F94848", r: 1, c: this.cols - 2 }, this), + ]; + } + + create_walls() { // 判断是否生成有效的地图 + const g = this.store.state.pk.gamemap; + for (let r = 0; r < this.rows; r++) { + for (let c = 0; c < this.cols; c++) { + if (g[r][c]) { + // 每次添加一个障碍物 + this.walls.push(new Wall(r, c, this)); + } + } + } + } + + add_listening_events() { // 绑定监听事件 + if (this.store.state.record.is_record === true) { // 播放录像 + 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; + + let interval_id = setInterval(() => { + if (k >= a_steps.length - 1) { // 不回放最后一步,最后一步直接展示结果 + if (loser == "all" || loser == "A") { + snake0.status = "die"; + } + if (loser == "all" || loser == "B") { + snake1.status = "die"; + } + clearInterval(interval_id); + }else { + snake0.set_direction(parseInt(a_steps[k])); + snake1.set_direction(parseInt(b_steps[k])); + } + k++; + + },300); + }else { + this.ctx.canvas.focus(); // 聚焦 + // 用wasd控制方向 + 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; + this.store.state.pk.socket.send(JSON.stringify({ + event: "move", + direction: d, + })); + }); + } + + } + + + + start() { + this.create_walls(); + this.add_listening_events(); + } + + update_size() { // 每过一帧重新生成新的地图尺寸 + // 取整数 + this.L = parseInt(Math.min(this.parent.clientWidth / this.cols, this.parent.clientHeight / this.rows)); + this.ctx.canvas.width = this.L * this.cols; + this.ctx.canvas.height = this.L * this.rows; + } + + update() { // 更新地图:每隔一帧都要重新渲染 + this.update_size(); // 更新地图大小 + if (this.check_ready()) { + this.next_step(); // 进入下一回合 + } + this.render(); // 重新渲染 + } + + check_ready() { // 判断两条蛇是否准备好进行下一步 + for (const snake of this.snakes) { + if (snake.status !== "idle") return false; + if (snake.direction === -1) return false; + } + return true; + } + + next_step() { // 让两条蛇进入下一回合 + for (const snake of this.snakes) { + snake.next_step(); + } + } + + check_vaild(cell) { // 检测蛇的目标位置是否合法:是否撞到两条蛇的身体和障碍物 + for (const wall of this.walls) { + if (wall.r === cell.r && wall.c === cell.c) { // 撞到墙了 + return false; + } + } + + for (const snake of this.snakes) { + let k = snake.cells.length; + // 特殊处理!!! + if (!snake.check_tail_increasing()) { // 当蛇为会前进的时候,蛇尾不需要判断,因为蛇头可以占据蛇尾的位置 + k--; + } + for (let i = 0; i < k; i++) { // 依次判断是否会撞到两条蛇的身体 + if (snake.cells[i].r === cell.r && snake.cells[i].c === cell.c) { // 撞到蛇的身体了 + return false; + } + } + } + + return true; + } + + render() { + const color_even = "#AAD751", color_odd = "#A2D149"; + for (let r = 0; r < this.rows; r++) { + for (let c = 0; c < this.cols; c++) { + if ((r + c) % 2 == 0) { + this.ctx.fillStyle = color_even; + } else { + this.ctx.fillStyle = color_odd; + } + this.ctx.fillRect(c * this.L, r * this.L, this.L, this.L); + } + } + } +} \ No newline at end of file diff --git a/acapp/src/assets/scripts/Snake.js b/acapp/src/assets/scripts/Snake.js new file mode 100644 index 0000000..fe3bb11 --- /dev/null +++ b/acapp/src/assets/scripts/Snake.js @@ -0,0 +1,168 @@ +import { AcGameObject } from "./AcGameObject"; +import { Cell } from "./Cell"; + +export class Snake extends AcGameObject { + constructor(info,gamemap) { // 传入蛇的信息和地图 + super(); + + this.id = info.id; // 哪条蛇 + this.color = info.color; // 蛇的颜色 + this.gamemap = gamemap; // 方便调用地图的有关参数 + this.cells = [new Cell(info.r,info.c)] // cells存放蛇的坐标,cells[0]存放蛇头 + + this.speed = 5; // 定义蛇的速度,每秒走5个格子 + this.direction = -1; // -1表示没有指令,0,1,2,3分别表示左右下左 + this.status = "idle"; // idle表示静止,move表示移动,die表示死亡 + this.next_cell = null; // 下一步的目标位置 + + // 0,1,2,3:上右下左 + this.dr = [-1,0,1,0]; // 行方向的偏移量 + this.dc = [0,1,0,-1]; // 列方向的偏移量 + + this.step = 0; // 蛇当前的回合数 + this.eps = 1e-2; // 允许误差为0.01 + + this.eye_direction = 0; // 左下角的蛇的眼睛朝上 + + if (this.id === 1) this.eye_direction = 2; // 右上角的蛇的眼睛朝下 + + // 定义蛇左右眼的x和y坐标的偏移量 + this.eyes_dx = [ + [-1,1], + [1,1], + [-1,1], + [-1,-1] + ]; + + this.eyes_dy = [ + [-1,-1], + [-1,1], + [1,1], + [-1,1] + ]; + + } + + start() { // 第一帧调用该函数 + + } + + update() { // 更新下一帧 + if (this.status === "move") { // 只有下一步的状态为移动才可以移动 + this.update_move(); // 蛇进行移动 + } + this.render(); + } + + check_tail_increasing() { // 前10步每步增加1,后面每三步增加1 + if (this.step <= 10) return true; + if (this.step % 3 === 1) return true; + return false; + } + + next_step() { // 将蛇的状态置为下一步 + const d = this.direction; + // 更新下一步的位置 + this.next_cell = new Cell(this.cells[0].r+this.dr[d],this.cells[0].c + this.dc[d]); + this.direction = -1; // 清空当前方向 + this.status = "move"; // 改为移动 + this.eye_direction = d; // 更新蛇的眼睛方向 + this.step++; // 回合数加一 + + const k = this.cells.length; + for (let i = k; i > 0; i--) { + // 每个数组元素先后移动一位,相当于复制了一份第一个元素 + // 1,2,3 => 1,1,2,3 + this.cells[i] = JSON.parse(JSON.stringify(this.cells[i-1])); + } + } + + set_direction(d) { // 通过读取用户输入来设置方向 + this.direction = d; + } + + update_move() { // 蛇进行移动 + // // 蛇向右移动5个格子 + // this.cells[0].x += this.speed * this.timedelta / 1000; // 注意每一帧不一定是1s + + const dx = this.next_cell.x - this.cells[0].x; + const dy = this.next_cell.y - this.cells[0].y; + + const distance = Math.sqrt(dx * dx+dy * dy); + if (distance < this.eps) { // 移动到了目标点 + this.cells[0] = this.next_cell; // 添加一个新蛇头 + this.next_cell = null; + this.status = "idle"; // 走完了,停下来 + if (!this.check_tail_increasing()) { // 蛇不变长,去掉蛇尾 + this.cells.pop(); // 弹出蛇尾 + } + } + else { + const move_distance = this.speed * this.timedelta / 1000; //移动距离 + this.cells[0].x += move_distance * dx / distance; + this.cells[0].y += move_distance * dy / distance; + + if (!this.check_tail_increasing()) { // 蛇不变长,蛇尾需要移动到目标位置 + const k = this.cells.length; + const tail = this.cells[k-1],tail_target = this.cells[k-2]; + const tail_dx = tail_target.x - tail.x; + const tail_dy = tail_target.y - tail.y; + tail.x += move_distance * tail_dx / distance; + tail.y += move_distance * tail_dy / distance; + } + } + + } + + + render() { + const L = this.gamemap.L; // 单位距离 + const ctx = this.gamemap.ctx; // 画布 + + ctx.fillStyle = this.color; // 填充颜色 + + if (this.status === "die") { + ctx.fillStyle = "white"; + } + + for (const cell of this.cells) { // 将蛇用圆表示 + ctx.beginPath(); + // 前两个参数时圆心坐标,后面的 + ctx.arc(cell.x * L,cell.y * L,L / 2 * 0.8, 0, Math.PI * 2); + ctx.fill(); + } + + for (let i = 1; i < this.cells.length; i++) { + const a = this.cells[i-1],b = this.cells[i]; + if (Math.abs(a.x - b.x) < this.eps && Math.abs(a.y-b.y) < this.eps) { + continue; + } + + if (Math.abs(a.x - b.x) < this.eps) { + // 矩形起始点的 x轴坐标、y轴坐标、矩形width、矩形height + // 用Math.min()是因为这两个矩阵上下关系不知道 + ctx.fillRect((a.x - 0.4) * L,Math.min(a.y,b.y) * L,L*0.8,Math.abs(a.y-b.y) * L); + } + else { + ctx.fillRect(Math.min(a.x,b.x) * L,(a.y - 0.4) * L,Math.abs(a.x-b.x) * L,L*0.8); + } + } + + + // 画出蛇的眼睛 + + ctx.fillStyle = "black"; + + for (let i = 0; i < 2 ; i++) { + const eye_x = (this.cells[0].x + this.eyes_dx[this.eye_direction][i] * 0.2) * L; + const eye_y = (this.cells[0].y + this.eyes_dy[this.eye_direction][i] * 0.2) * L; + + // 画眼睛 + + ctx.beginPath(); + ctx.arc(eye_x,eye_y,L*0.05,0,Math.PI*2); + ctx.fill(); + } + + } +} \ No newline at end of file diff --git a/acapp/src/assets/scripts/Wall.js b/acapp/src/assets/scripts/Wall.js new file mode 100644 index 0000000..f3b0096 --- /dev/null +++ b/acapp/src/assets/scripts/Wall.js @@ -0,0 +1,24 @@ +import { AcGameObject } from "./AcGameObject"; + +export class Wall extends AcGameObject { + constructor(r, c, gamemap) { + super(); + + this.r = r; + this.c = c; + this.gamemap = gamemap; + this.color = "#B37226"; // 障碍物的颜色 + } + + update() { + this.render(); + } + + render() { + const L = this.gamemap.L; + const ctx = this.gamemap.ctx; + + ctx.fillStyle = this.color; + ctx.fillRect(this.c * L, this.r * L, L, L); // 将对应位置填充为障碍物 + } +} \ No newline at end of file diff --git a/acapp/src/components/ContentField.vue b/acapp/src/components/ContentField.vue new file mode 100644 index 0000000..e3777ba --- /dev/null +++ b/acapp/src/components/ContentField.vue @@ -0,0 +1,49 @@ + + + + + + \ No newline at end of file diff --git a/acapp/src/components/GameMap.vue b/acapp/src/components/GameMap.vue new file mode 100644 index 0000000..30a72d6 --- /dev/null +++ b/acapp/src/components/GameMap.vue @@ -0,0 +1,38 @@ + + + + + \ No newline at end of file diff --git a/acapp/src/components/HelloWorld.vue b/acapp/src/components/HelloWorld.vue deleted file mode 100644 index 879051a..0000000 --- a/acapp/src/components/HelloWorld.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - diff --git a/acapp/src/components/MatchGround.vue b/acapp/src/components/MatchGround.vue new file mode 100644 index 0000000..a41f932 --- /dev/null +++ b/acapp/src/components/MatchGround.vue @@ -0,0 +1,199 @@ + + + + + + \ No newline at end of file diff --git a/acapp/src/components/PlayGround.vue b/acapp/src/components/PlayGround.vue new file mode 100644 index 0000000..c54c9df --- /dev/null +++ b/acapp/src/components/PlayGround.vue @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/acapp/src/components/ResultBoard.vue b/acapp/src/components/ResultBoard.vue new file mode 100644 index 0000000..0929fd4 --- /dev/null +++ b/acapp/src/components/ResultBoard.vue @@ -0,0 +1,78 @@ + + + + + + \ No newline at end of file diff --git a/acapp/src/components/UserInfo.vue b/acapp/src/components/UserInfo.vue new file mode 100644 index 0000000..503f153 --- /dev/null +++ b/acapp/src/components/UserInfo.vue @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/acapp/src/main.js b/acapp/src/main.js index 452f11a..3a11b0b 100644 --- a/acapp/src/main.js +++ b/acapp/src/main.js @@ -2,4 +2,4 @@ import { createApp } from 'vue' import App from './App.vue' import store from './store' -createApp(App).use(store).use(store).mount('#app') +createApp(App).use(store).mount('#app') diff --git a/acapp/src/store/index.js b/acapp/src/store/index.js index 7f5b89c..c209b99 100644 --- a/acapp/src/store/index.js +++ b/acapp/src/store/index.js @@ -1,4 +1,8 @@ import { createStore } from 'vuex' +import ModuleUser from "./user" +import ModulePk from "./pk" +import ModuleRecord from "./record" +import ModuleRouter from "./router" export default createStore({ state: { @@ -10,5 +14,9 @@ export default createStore({ actions: { }, modules: { + user: ModuleUser, + pk: ModulePk, + record: ModuleRecord, + router: ModuleRouter, } }) diff --git a/acapp/src/store/pk.js b/acapp/src/store/pk.js new file mode 100644 index 0000000..c48b847 --- /dev/null +++ b/acapp/src/store/pk.js @@ -0,0 +1,52 @@ +export default { + state: { // 全局变量 + status: "matching", // matching表示匹配界面,playing表示对战界面 + socket: null, + opponent_username: "", //对手的用户名 + opponent_photo: "", // 对手的头像 + gamemap: null, // 对战的地图 + a_id: 0, + a_sx: 0, + a_sy: 0, + b_id: 0, + b_sx: 0, + b_sy: 0, + gameObject: null, + loser: "none" //"none","all","A","B" none表示还没有结果 + }, + 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; + + }, + updateGameObject(state, gameObject) { + state.gameObject = gameObject; + }, + updateLoser(state, loser) { + state.loser = loser; + } + }, + actions: { // 在actions中调用修改全局变量的函数 + + }, + modules: { + } +} \ No newline at end of file diff --git a/acapp/src/store/record.js b/acapp/src/store/record.js new file mode 100644 index 0000000..9392f4a --- /dev/null +++ b/acapp/src/store/record.js @@ -0,0 +1,29 @@ +export default { + state: { + is_record: false, + a_steps: "", + 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,record_loser) { + state.record_loser = record_loser; + } + + }, + actions: { + + }, + modules: { + } +} \ No newline at end of file diff --git a/acapp/src/store/router.js b/acapp/src/store/router.js new file mode 100644 index 0000000..656ae30 --- /dev/null +++ b/acapp/src/store/router.js @@ -0,0 +1,18 @@ +export default { + state: { + router_name: "menu", // menu,pk,record,record_content,ranklist,user_bot + }, + getters: { + + }, + mutations: { + updateRouterName(state, router_name) { + state.router_name = router_name; + }, + }, + actions: { + + }, + modules: { + } +} \ No newline at end of file diff --git a/acapp/src/store/user.js b/acapp/src/store/user.js new file mode 100644 index 0000000..e27b62b --- /dev/null +++ b/acapp/src/store/user.js @@ -0,0 +1,94 @@ +import $ from 'jquery' + +export default { + state: { // 全局变量 + id: "", + username: "", + photo: "", + token: "", + is_login: false, // 默认未登录状态 + pulling_info: true, // 是否正在拉取信息 + }, + getters: { + }, + mutations: { // 用于修改全局数据 + updateUser(state,user) { // 更新用户信息 + state.id = user.id; + state.username = user.username; + state.photo = user.photo; + state.is_login = user.is_login; + }, + updateToken(state,token) { // 更新token + state.token = token; + }, + logout(state) { + state.id = ""; + state.username = ""; + state.photo = ""; + state.token = ""; + state.is_login = false; + }, + updatePullingInfo(state,pulling_info) { + state.pulling_info = pulling_info; + } + }, + actions: { // 在actions中调用修改全局变量的函数 + login(context,data) { + $.ajax({ + url: "https://kob.bnblogs.cc/api/user/account/token/", + type : "POST", + data: { + username: data.username, + password: data.password, + }, + success(resp) { + // resp对应的就是后端的map的json格式,每个字段对应键值对中的key + // 登陆成功 + if(resp.error_msg === "success"){ + // 调用updateToken函数,形参中的data为resp.token + // 将登陆信息的token存入localStorage实现持久化(浏览器刷新退出登陆) + localStorage.setItem("jwt_token",resp.token); + context.commit("updateToken",resp.token); + data.success(resp); + }else { + data.error(resp); + } + }, + error(resp) { + data.error(resp); + }, + }); + }, + getInfo(context,data) { + $.ajax({ + url: "https://kob.bnblogs.cc/api/user/account/info/", + type: "GET", + headers: { + Authorization: "Bearer " + context.state.token, + }, + success(resp) { + if (resp.error_msg === "success"){ + // 登陆成功后,更新当前用户信息 + context.commit("updateUser",{ + ...resp, + is_login: true, + }); + data.success(resp); // 调用UserLoginView中的success()回调函数 + }else { + data.error(resp); // 调用UserLoginView中的error()回调函数 + } + }, + error(resp) { + data.error(resp); + }, + }); + }, + logout(context) { + // 退出时删除localStorage保存的token + localStorage.removeItem("jwt_token"); + context.commit("logout"); // 退出登录 + } + }, + modules: { + } +} \ No newline at end of file diff --git a/acapp/src/views/MenuView.vue b/acapp/src/views/MenuView.vue new file mode 100644 index 0000000..0ca135a --- /dev/null +++ b/acapp/src/views/MenuView.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/acapp/src/views/pk/PkIndexView.vue b/acapp/src/views/pk/PkIndexView.vue new file mode 100644 index 0000000..77017b3 --- /dev/null +++ b/acapp/src/views/pk/PkIndexView.vue @@ -0,0 +1,99 @@ + + + + + diff --git a/acapp/src/views/ranklist/RanklistIndexView.vue b/acapp/src/views/ranklist/RanklistIndexView.vue new file mode 100644 index 0000000..38a0695 --- /dev/null +++ b/acapp/src/views/ranklist/RanklistIndexView.vue @@ -0,0 +1,133 @@ + + + + + \ No newline at end of file diff --git a/acapp/src/views/record/RecordContentView.vue b/acapp/src/views/record/RecordContentView.vue new file mode 100644 index 0000000..9c61d15 --- /dev/null +++ b/acapp/src/views/record/RecordContentView.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/acapp/src/views/record/RecordIndexView.vue b/acapp/src/views/record/RecordIndexView.vue new file mode 100644 index 0000000..efb56fc --- /dev/null +++ b/acapp/src/views/record/RecordIndexView.vue @@ -0,0 +1,197 @@ + + + + + \ No newline at end of file diff --git a/acapp/src/views/user/bot/UserBotIndexView.vue b/acapp/src/views/user/bot/UserBotIndexView.vue new file mode 100644 index 0000000..9d5c6cd --- /dev/null +++ b/acapp/src/views/user/bot/UserBotIndexView.vue @@ -0,0 +1,279 @@ + + + + + \ No newline at end of file diff --git a/acapp/vue.config.js b/acapp/vue.config.js index 910e297..3541360 100644 --- a/acapp/vue.config.js +++ b/acapp/vue.config.js @@ -1,4 +1,9 @@ const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ - transpileDependencies: true -}) + transpileDependencies: true, + configureWebpack: { + optimization: { + splitChunks: false + } + } +}) \ No newline at end of file