地图+障碍物+导航栏

master
barney 2 years ago
parent 2e0afa1d33
commit 03d1906bea
  1. 2
      web/jsconfig.json
  2. 60
      web/package-lock.json
  3. BIN
      web/public/favicon.ico
  4. 38
      web/src/App.vue
  5. 0
      web/src/assets/images/background.png
  6. 50
      web/src/assets/scripts/AcGameObject.js
  7. 116
      web/src/assets/scripts/GameMap.js
  8. 24
      web/src/assets/scripts/Wall.js
  9. 18
      web/src/components/ContentField.vue
  10. 36
      web/src/components/GameMap.vue
  11. 54
      web/src/components/NavBar.vue
  12. 24
      web/src/components/PlayGround.vue
  13. 42
      web/src/router/index.js
  14. 19
      web/src/views/error/NotFound.vue
  15. 16
      web/src/views/pk/PkIndexView.vue
  16. 18
      web/src/views/ranklist/RanklistIndexView.vue
  17. 18
      web/src/views/record/RecordIndexView.vue
  18. 18
      web/src/views/user/bot/UserBotIndexView.vue

@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "es5",
"target": "es6",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",

@ -1928,6 +1928,12 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true
},
"node_modules/@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
"peer": true
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz",
@ -12219,6 +12225,12 @@
"integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==",
"dev": true
},
"@popperjs/core": {
"version": "2.11.5",
"resolved": "https://registry.npmmirror.com/@popperjs/core/-/core-2.11.5.tgz",
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
"peer": true
},
"@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmmirror.com/@sideway/address/-/address-4.1.4.tgz",
@ -12739,7 +12751,8 @@
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-5.0.8.tgz",
"integrity": "sha512-HSYWPqrunRE5ZZs8kVwiY6oWcn95qf/OQabwLfprhdpFWAGtLStShjsGED2aDpSSeGAskQETrtR/5h7VqgIlBA==",
"dev": true
"dev": true,
"requires": {}
},
"@vue/cli-service": {
"version": "5.0.8",
@ -13250,13 +13263,15 @@
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"dev": true
"dev": true,
"requires": {}
},
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "8.2.0",
@ -13315,7 +13330,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmmirror.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"dev": true,
"requires": {}
},
"ansi-colors": {
"version": "4.1.3",
@ -13603,7 +13619,8 @@
"bootstrap": {
"version": "5.1.3",
"resolved": "https://registry.npmmirror.com/bootstrap/-/bootstrap-5.1.3.tgz",
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q=="
"integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==",
"requires": {}
},
"brace-expansion": {
"version": "1.1.11",
@ -14131,7 +14148,8 @@
"version": "6.3.0",
"resolved": "https://registry.npmmirror.com/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz",
"integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==",
"dev": true
"dev": true,
"requires": {}
},
"css-loader": {
"version": "6.7.1",
@ -14302,7 +14320,8 @@
"version": "3.1.0",
"resolved": "https://registry.npmmirror.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz",
"integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==",
"dev": true
"dev": true,
"requires": {}
},
"csso": {
"version": "4.2.0",
@ -15758,7 +15777,8 @@
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
"dev": true,
"requires": {}
},
"ieee754": {
"version": "1.2.1",
@ -17130,25 +17150,29 @@
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz",
"integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-discard-duplicates": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz",
"integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-discard-empty": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz",
"integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-discard-overridden": {
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz",
"integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-loader": {
"version": "6.2.1",
@ -17238,7 +17262,8 @@
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
@ -17273,7 +17298,8 @@
"version": "5.1.0",
"resolved": "https://registry.npmmirror.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz",
"integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-normalize-display-values": {
"version": "5.1.0",
@ -19110,7 +19136,8 @@
"version": "8.8.0",
"resolved": "https://registry.npmmirror.com/ws/-/ws-8.8.0.tgz",
"integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==",
"dev": true
"dev": true,
"requires": {}
}
}
},
@ -19237,7 +19264,8 @@
"version": "7.5.8",
"resolved": "https://registry.npmmirror.com/ws/-/ws-7.5.8.tgz",
"integrity": "sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==",
"dev": true
"dev": true,
"requires": {}
},
"y18n": {
"version": "5.0.8",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 49 KiB

@ -1,39 +1,23 @@
<template>
<div>
<h1>bot昵称: {{bot_name}} </h1>
<h1>bot战力: {{bot_rating}} </h1>
</div>
<router-view></router-view>
<NavBar /> <!--在前端页面中展示NavBar组件---->
<router-view></router-view>
</template>
<script>
import $ from 'jquery';
import { ref } from 'vue';
export default{
name:"app",
setup:() => {
let bot_name = ref("");
let bot_rating = ref("");
$.ajax({
url: "http://localhost:3000/pk/getInfo2/",
type: 'get',
success: resp => {
console.log(resp);
bot_name.value = resp.name;
bot_rating.value = resp.rating;
}
});
return {
bot_name,
bot_rating
}
import NavBar from './components/NavBar.vue' // NavBar(.vue)
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap/dist/js/bootstrap"
export default {
components: { // 使NavBar
NavBar
}
}
</script>
<style>
body{
background-image: url("@/assets/background.png");
body {
background-image: url("@/assets/images/background.png");
background-size: cover;
}
</style>

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

@ -0,0 +1,50 @@
const AC_GAME_OBJECTS = [];
export class AcGameObject {
constructor() {
AC_GAME_OBJECTS.push(this); // 数组添加元素
this.timedelta = 0;
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)

@ -0,0 +1,116 @@
import { AcGameObject } from "./AcGameObject"; // 导入js的export class
import { Wall } from "./Wall";
export class GameMap extends AcGameObject {
constructor(ctx, parent) {
super(); // 继承类一直要先调用父类的构造函数
this.ctx = ctx;
this.parent = parent;
this.L = 0;
this.rows = 13;
this.cols = 13;
this.inner_walls_count = 20; // 障碍物的数量(最大建议80)
this.walls = []; // 所有障碍物组成的数组
}
check_connectivity(g, sx, sy, tx, ty) { // 判断生成的地图是否可以连通
if (sx == tx && sy == ty) return true;
g[sx][sy] = true;
let dx = [-1, 0, 1, 0], dy = [0, 1, 0, -1];
for (let i = 0; i < 4; i ++ ) {
let x = sx + dx[i], y = sy + dy[i];
if (!g[x][y] && this.check_connectivity(g, x, y, tx, ty))
return true;
}
return false;
}
create_walls() { // 判断是否生成有效的地图
const g = [];
for (let r = 0; r < this.rows; r ++ ) {
g[r] = [];
for (let c = 0; c < this.cols; c ++ ) {
g[r][c] = false;
}
}
// 给四周加上障碍物
for (let r = 0; r < this.rows; r ++ ) {
g[r][0] = g[r][this.cols - 1] = true;
}
for (let c = 0; c < this.cols; c ++ ) {
g[0][c] = g[this.rows - 1][c] = true;
}
// 创建随机障碍物
for (let i = 0; i < this.inner_walls_count / 2; i ++ ) {
for (let j = 0; j < 1000; j ++ ) {
let r = parseInt(Math.random() * this.rows);
let c = parseInt(Math.random() * this.cols);
if (g[r][c] || g[c][r]) continue;
// 不能将障碍物生成到两条蛇的起始位置处(左下,右上)
if (r == this.rows - 2 && c == 1 || r == 1 && c == this.cols - 2)
continue;
// 每次将障碍物生成到对称的两个位置
g[r][c] = g[c][r] = true;
break;
}
}
const copy_g = JSON.parse(JSON.stringify(g));
// 如果生成的地图无法连通,则返回false,需要重新生成
if (!this.check_connectivity(copy_g, this.rows - 2, 1, 1, this.cols - 2))
return false;
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));
}
}
}
return true;
}
start() {
// 尝试1000次,直到找到符合条件的地图为止
for (let i = 0; i < 1000; i ++ )
if (this.create_walls())
break;
}
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(); // 更新地图大小
this.render(); // 重新渲染
}
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);
}
}
}
}

@ -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); // 将对应位置填充为障碍物
}
}

@ -0,0 +1,18 @@
<template>
<div class="container content-field">
<div class="card">
<div class="card-body">
<slot></slot>
</div>
</div>
</div>
</template>
<script>
</script>
<style scoped>
div.content-field {
margin-top: 40px;
}
</style>

@ -0,0 +1,36 @@
<template>
<div ref="parent" class="gamemap"> <!--使用下面返回的parent-->
<canvas ref="canvas"></canvas> <!--使用下面返回的canvas-->
</div>
</template>
<script>
import { GameMap } from "@/assets/scripts/GameMap"; // jsGameMap(public)
import { ref, onMounted } from 'vue'
export default {
setup() {
let parent = ref(null);
let canvas = ref(null);
onMounted(() => {
new GameMap(canvas.value.getContext('2d'), parent.value)
});
return {
parent,
canvas
}
}
}
</script>
<style scoped>
div.gamemap {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
</style>

@ -0,0 +1,54 @@
<template>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<router-link class="navbar-brand" :to="{name: 'home'}">King Of Bots</router-link>
<div class="collapse navbar-collapse" id="navbarText">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<router-link :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'pk_index'}">对战</router-link>
</li>
<li class="nav-item">
<router-link :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</router-link>
</li>
<li class="nav-item">
<router-link :class="route_name == 'ranklist_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'ranklist_index'}">排行榜</router-link>
</li>
</ul>
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
zhangfp
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li>
<router-link class="dropdown-item" :to="{name: 'user_bot_index'}">我的Bot</router-link>
</li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#">退出</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
import { useRoute } from 'vue-router'
import { computed } from 'vue'
export default {
setup() {
const route = useRoute();
let route_name = computed(() => route.name)
return {
route_name
}
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,24 @@
<template>
<div class="playground">
<GameMap />
</div>
</template>
<script>
import GameMap from './GameMap.vue' // GameMap(.vue)
export default {
components: { // 使GameMap
GameMap,
}
}
</script>
<style scoped>
/* div标签的样式 */
div.playground {
width: 60vw;
height: 70vh;
margin: 40px auto;
}
</style>

@ -1,6 +1,46 @@
import { createRouter, createWebHistory } from 'vue-router'
import PkIndexView from '../views/pk/PkIndexView'
import RecordIndexView from '../views/record/RecordIndexView'
import RanklistIndexView from '../views/ranklist/RanklistIndexView'
import UserBotIndexView from '../views/user/bot/UserBotIndexView'
import NotFound from '../views/error/NotFound'
const routes = []
const routes = [
{
path: "/",
name: "home",
redirect: "/pk/"
},
{
path: "/pk/",
name: "pk_index",
component: PkIndexView,
},
{
path: "/record/",
name: "record_index",
component: RecordIndexView,
},
{
path: "/ranklist/",
name: "ranklist_index",
component: RanklistIndexView,
},
{
path: "/user/bot/",
name: "user_bot_index",
component: UserBotIndexView,
},
{
path: "/404/",
name: "404",
component: NotFound,
},
{
path: "/:catchAll(.*)",
redirect: "/404/"
}
]
const router = createRouter({
history: createWebHistory(),

@ -0,0 +1,19 @@
<template>
<ContentField>
404 Not Found
</ContentField>
</template>
<script>
import ContentField from '../../components/ContentField.vue'
export default {
components: {
ContentField
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,16 @@
<template>
<PlayGround/>
</template>
<script>
import PlayGround from '../../components/PlayGround.vue'
export default {
components: {
PlayGround
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,18 @@
<template>
<ContentField>
排行榜
</ContentField>
</template>
<script>
import ContentField from '../../components/ContentField.vue'
export default {
components: {
ContentField
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,18 @@
<template>
<ContentField>
对局列表
</ContentField>
</template>
<script>
import ContentField from '../../components/ContentField.vue'
export default {
components: {
ContentField
}
}
</script>
<style scoped>
</style>

@ -0,0 +1,18 @@
<template>
<ContentField>
我的Bot
</ContentField>
</template>
<script>
import ContentField from '../../../components/ContentField.vue'
export default {
components: {
ContentField
}
}
</script>
<style scoped>
</style>
Loading…
Cancel
Save