思维导图
实现导航栏
老生常谈了,没啥好说的。随便去Bootstrap找个类改改构成基础部分
之后依据<router-link>
和v-bind
就能够实现基本的跳转。
组件NavBar.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 <template> <nav class="navbar navbar-expand-lg navbar-light bg-light"> <div class="container"> <routerLink :class="route_name == 'home' ? 'nav-link active' : 'nav-link'" :to="{name: 'home'}">SnakeGPT</routerLink> <div class="collapse navbar-collapse" id="navbarText"> <ul class="navbar-nav me-auto mb-2 mb-lg-0"> <li class="nav-item"> <routerLink :class="route_name == 'pk_index' ? 'nav-link active' : 'nav-link'" aria-current="page" :to="{name: 'pk_index'}">对战</routerLink> </li> <li class="nav-item"> <routerLink :class="route_name == 'record_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'record_index'}">对局列表</routerLink> </li> <li class="nav-item"> <routerLink :class="route_name == 'rank_index' ? 'nav-link active' : 'nav-link'" :to="{name: 'rank_index'}">排行榜</routerLink> </li> </ul> <ul class="navbar-nav "> <li class="nav-item dropdown"> <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false"> 沃斯尼蝶 </a> <ul class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink"> <li><routerLink class="dropdown-item" :to="{name: 'user_bot_index'}">我的蛇</routerLink></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/reactivity"; export default { setup() { const route = useRoute(); let route_name = computed(() => route.name); return { route_name } } } </script> <style scoped> </style>
router插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 import { createRouter, createWebHistory } from 'vue-router' import NotFoundView from '../views/error/NotFoundView.vue' import PkIndexView from '../views/pk/PkIndexView.vue' import RanklistIndexView from '../views/ranklist/RanklistIndexView.vue' import RecordIndexView from '../views/record/RecordIndexView.vue' import UserBotIndexView from '../views/user/bot/UserBotIndexView.vue' const routes = [ { path : "/" , name : "home" , }, { path : "/pk/" , name : "pk_index" , component : PkIndexView , }, { path : "/ranklist/" , name : "rank_index" , component : RanklistIndexView , }, { path : "/record/" , name : "record_index" , component : RecordIndexView , }, { path : "/user/bot/" , name : "user_bot_index" , component : UserBotIndexView , }, { path : "/404/" , name : "404_index" , component : NotFoundView , }, { path : "/:catchAll(.*)" , redirect : "/404/" , } ] const router = createRouter ({ history : createWebHistory (), routes }) export default router
修改根
在APP.vue
中引入Router
和NavBar
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <NavBar></NavBar> <RouterView></RouterView> </template> <script> // import $ from 'jquery'; // import { ref } from 'vue'; import NavBar from './components/NavBar.vue'; import "bootstrap/dist/css/bootstrap.min.css"; import "bootstrap/dist/js/bootstrap"; export default { components: { NavBar } } </script>
实现地图
游戏引擎
写一个游戏引擎,每秒刷新60次。所有游戏中的物品都必须继承自该类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 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)
实现地图类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import { AcGameObject } from "./AcGameObject" ;export class GameMap extends AcGameObject { constructor (ctx, parent ) { super (); this .ctx = ctx; this .parent = parent; this .L = 0 ; } start ( ) { } update ( ) { this .rander (); } render ( ) { } }
绘制游戏区域
pk界面中创建一个游戏区域,用来显示对战。
在components中写一个组件:PlayGround.vue
,并在PkIndexView
引入该组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <div class="playground"> </div> </template> <script> </script> <style scoped> div.playground { width: 60vw; height: 70vh; background: lightblue; } </style>
再写一个GameMap.vue
组件,来渲染游戏界面,并在PlayGround.vue
中引入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="gamemap" ref="parent"> <canvas ref="canvas"> </canvas> </div> </template> <script> import { GameMap} from '../assets/scripts/GameMap' 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>
绘制方格
把GameMap
分成13*13块方格,块与块之前实现深浅交接。
在GameMap.js
中修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 import { AcGameObject } from "./AcGameObject" ;export class GameMap extends AcGameObject { constructor (ctx, parent ) { super (); this .ctx = ctx; this .parent = parent; this .L = 0 ; this .rows = 13 ; this .cols = 13 ; } start ( ) { } update_size ( ) { this .L = 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_eve = "#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_eve; } else { this .ctx .fillStyle = color_odd; } this .ctx .fillRect (c * this .L , r * this .L , this .L , this .L ); } } }
绘制墙和障碍物
GameMap
的四周是墙,和普通的游戏方块区分开来
地图中要生成随机障碍物。随机障碍物要满足以下条件:
不能和起点重合(左下角和右上角)
保证左下角和右上角连通
首先新建Wall.js
,表示墙类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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); } }
利用奇偶性,可以实现块与块颜色不同。
利用n*m
的布尔数组,可以把最外围的部分标记出来特殊处理(就是跳过)
利用dfs
实现的连通块算法,即可实现判断连通性。
修改GameMap.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 import { AcGameObject } from "./AcGameObject" ;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 = 10 ; this .walls = []; } check_connectivity (g, sx, sy, ex, ey ) { if (sx == ex && sy == ey) return true ; g[sx][sy] = true ; let dx = [0 , 0 , 1 , -1 ]; let dy = [1 , -1 , 0 , 0 ]; for (let i = 0 ; i < 4 ; i++) { let a = sx + dx[i], b = sy + dy[i]; if (!g[a][b] && this .check_connectivity (g, a, b, ex, ey)) { return true ; } } return false ; } create_walls ( ) { const g = []; for (let i = 0 ; i < this .rows ; i++) { g[i] = []; for (let j = 0 ; j < this .cols ; j++) { g[i][j] = false ; } } for (let i = 0 ; i < this .rows ; i++) { g[i][0 ] = true ; g[i][this .cols - 1 ] = true ; } for (let i = 0 ; i < this .cols ; i++) { g[0 ][i] = true ; g[this .rows - 1 ][i] = true ; } for (let i = 0 ; i < this .inner_walls_count ; i++) { for (let j = 0 ; j < 100000 ; 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] = true ; g[c][r] = true ; break ; } } const copy_g = JSON .parse (JSON .stringify (g)); if (!this .check_connectivity (copy_g, this .rows - 2 , 1 , 1 , this .cols - 2 )) { return false ; } for (let i = 0 ; i < this .rows ; i++) { for (let j = 0 ; j < this .cols ; j++) { if (g[i][j]) { this .walls .push (new Wall (i, j, this )); } } } return true ; } start ( ) { 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_eve = "#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_eve; } else { this .ctx .fillStyle = color_odd; } this .ctx .fillRect (c * this .L , r * this .L , this .L , this .L ); } } }
最终效果
版权声明: 此文章版权归金晖のBlog所有,如有转载,请注明来自原作者