├── env.d.ts ├── docs ├── favicon.ico ├── out-of-the-maze.png ├── index.html └── assets │ ├── index.11e74c05.css │ └── index.e28c893c.js ├── public ├── favicon.ico └── out-of-the-maze.png ├── .vscode └── extensions.json ├── src ├── App.vue ├── main.ts ├── assets │ ├── logo.svg │ └── base.css ├── stores │ └── counter.ts ├── utils │ ├── record.ts │ ├── generator.ts │ ├── index.ts │ └── route.ts ├── router │ └── index.ts ├── views │ └── HomeView.vue ├── maps │ └── index.ts └── components │ ├── MazeSprite │ └── index.vue │ ├── CodeEditor │ └── index.vue │ ├── MazeView │ └── index.vue │ └── GameCanvas │ └── index.vue ├── tsconfig.vite-config.json ├── tsconfig.json ├── .eslintrc.cjs ├── index.html ├── .gitignore ├── vite.config.ts ├── README.md └── package.json /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DakerHub/coding-game-maze/HEAD/docs/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DakerHub/coding-game-maze/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /docs/out-of-the-maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DakerHub/coding-game-maze/HEAD/docs/out-of-the-maze.png -------------------------------------------------------------------------------- /public/out-of-the-maze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DakerHub/coding-game-maze/HEAD/public/out-of-the-maze.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["johnsoncodehk.volar", "johnsoncodehk.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /tsconfig.vite-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.node.json", 3 | "include": ["vite.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "types": ["node"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { createPinia } from "pinia"; 3 | 4 | import App from "./App.vue"; 5 | import router from "./router"; 6 | 7 | const app = createApp(App); 8 | 9 | app.use(createPinia()); 10 | app.use(router); 11 | 12 | app.mount("#app"); 13 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/stores/counter.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | 3 | export const useCounterStore = defineStore({ 4 | id: "counter", 5 | state: () => ({ 6 | counter: 0, 7 | }), 8 | getters: { 9 | doubleCount: (state) => state.counter * 2, 10 | }, 11 | actions: { 12 | increment() { 13 | this.counter++; 14 | }, 15 | }, 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.web.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "paths": { 7 | "@/*": ["./src/*"] 8 | } 9 | }, 10 | 11 | "references": [ 12 | { 13 | "path": "./tsconfig.vite-config.json" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/utils/record.ts: -------------------------------------------------------------------------------- 1 | import type { Position } from "."; 2 | 3 | export interface Step { 4 | pos: Position; 5 | } 6 | 7 | export default class Record { 8 | history: Step[] = []; 9 | 10 | add(step: Step) { 11 | this.history.push(step); 12 | } 13 | 14 | getAll() { 15 | return this.history; 16 | } 17 | 18 | clear() { 19 | this.history.length = 0; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require("@rushstack/eslint-patch/modern-module-resolution"); 3 | 4 | module.exports = { 5 | root: true, 6 | extends: [ 7 | "plugin:vue/vue3-essential", 8 | "eslint:recommended", 9 | "@vue/eslint-config-typescript/recommended", 10 | "@vue/eslint-config-prettier", 11 | ], 12 | env: { 13 | "vue/setup-compiler-macros": true, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Out of the maze 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist-ssr 13 | coverage 14 | *.local 15 | 16 | /cypress/videos/ 17 | /cypress/screenshots/ 18 | 19 | # Editor directories and files 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createRouter, 3 | createWebHistory, 4 | createWebHashHistory, 5 | } from "vue-router"; 6 | import HomeView from "../views/HomeView.vue"; 7 | 8 | const router = createRouter({ 9 | history: createWebHashHistory(import.meta.env.BASE_URL), 10 | routes: [ 11 | { 12 | path: "/", 13 | name: "home", 14 | component: HomeView, 15 | }, 16 | ], 17 | }); 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from "url"; 2 | 3 | import { defineConfig } from "vite"; 4 | import vue from "@vitejs/plugin-vue"; 5 | import vueJsx from "@vitejs/plugin-vue-jsx"; 6 | 7 | // https://vitejs.dev/config/ 8 | export default defineConfig({ 9 | plugins: [vue(), vueJsx()], 10 | resolve: { 11 | alias: { 12 | "@": fileURLToPath(new URL("./src", import.meta.url)), 13 | }, 14 | }, 15 | base: "./", 16 | build: { 17 | outDir: "docs", 18 | }, 19 | }); 20 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Out of the maze 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Out-of-The-Maze 2 | 3 | 迷宫编程游戏 [out-of-the-maze](https://dakerhub.github.io/coding-game-maze/#/) 4 | 5 | ![out-of-the-maze](./public//out-of-the-maze.png) 6 | 7 | ## Play game 8 | 9 | 打开链接 [out-of-the-maze](https://dakerhub.github.io/coding-game-maze/#/),编写`findNext`函数,然后点击界面 Play 按钮开始游戏! 10 | 11 | - 点击`Toggle Mask`查看迷宫 12 | - 点击`Play`开始自动执行查找程序 13 | - 点击`Reset`重置当前进度 14 | - 点击`Stop`暂停当前动作,支持继续编辑 15 | - 点击`Daker's solution`填充我的代码 16 | 17 | ## Development 18 | 19 | ```sh 20 | npm install 21 | ``` 22 | 23 | ### Compile and Hot-Reload for Development 24 | 25 | ```sh 26 | npm run dev 27 | ``` 28 | -------------------------------------------------------------------------------- /src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 43 | -------------------------------------------------------------------------------- /src/maps/index.ts: -------------------------------------------------------------------------------- 1 | export type Grid = number[][]; 2 | 3 | export const map1 = { 4 | name: "simple1", 5 | map: [ 6 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -2, 1, 1], 7 | [1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1], 8 | [1, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1], 9 | [1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1], 10 | [1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1], 11 | [1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 1], 12 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1], 13 | [-1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1], 14 | [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 15 | [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 16 | [1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1], 17 | ], 18 | entry: {}, 19 | out: {}, 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-out-of-the-maze", 3 | "version": "1.1.0", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vue-tsc --noEmit && vite build", 7 | "preview": "vite preview --port 5050", 8 | "typecheck": "vue-tsc --noEmit", 9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" 10 | }, 11 | "dependencies": { 12 | "@codemirror/basic-setup": "^0.19.1", 13 | "@codemirror/lang-javascript": "^0.19.7", 14 | "pinia": "^2.0.11", 15 | "vue": "^3.2.31", 16 | "vue-router": "^4.0.12", 17 | "generate-maze": "1.1.0" 18 | }, 19 | "devDependencies": { 20 | "@rushstack/eslint-patch": "^1.1.0", 21 | "@types/node": "^16.11.25", 22 | "@vitejs/plugin-vue": "^2.2.2", 23 | "@vitejs/plugin-vue-jsx": "^1.3.7", 24 | "@vue/eslint-config-prettier": "^7.0.0", 25 | "@vue/eslint-config-typescript": "^10.0.0", 26 | "@vue/tsconfig": "^0.1.3", 27 | "eslint": "^8.5.0", 28 | "eslint-plugin-vue": "^8.2.0", 29 | "prettier": "^2.5.1", 30 | "typescript": "~4.5.5", 31 | "vite": "^2.8.4", 32 | "vue-tsc": "^0.31.4" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/generator.ts: -------------------------------------------------------------------------------- 1 | import type { Grid } from "@/maps"; 2 | import generateMaze from "generate-maze"; 3 | 4 | export function generateRandomMaze( 5 | width = Math.ceil(Math.random() * 100), 6 | height = Math.ceil(Math.random() * 60), 7 | closed = true, 8 | seed = Math.ceil(Math.random() * 1000000) 9 | ) { 10 | width = Math.min(24, Math.max(3, Math.ceil(width / 2 + 0.5))); 11 | height = Math.min(20, Math.max(3, Math.ceil(height / 2 + 0.5))); 12 | 13 | const generated = generateMaze(width, height, closed, seed); 14 | const converted: Grid = []; 15 | for (let h = 0; h < generated.length; h++) { 16 | const lineOfRight = []; 17 | const lineOfBottom = []; 18 | for (let w = 0; w < generated[h].length; w++) { 19 | lineOfRight.push(0); 20 | lineOfRight.push(generated[h][w].right ? 1 : 0); 21 | lineOfBottom.push(generated[h][w].bottom ? 1 : 0); 22 | lineOfBottom.push(1); 23 | } 24 | converted.push(lineOfRight.slice(0, -1)); 25 | converted.push(lineOfBottom.slice(0, -1)); 26 | } 27 | converted[0][0] = -1; 28 | converted.splice(-1, 1); 29 | converted[converted.length - 1][converted[0].length - 1] = -2; 30 | console.debug("generated", generated); 31 | console.debug("converted", converted); 32 | return { 33 | map: converted, 34 | width: converted[0].length, 35 | height: converted.length, 36 | closed, 37 | seed, 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/MazeSprite/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 65 | -------------------------------------------------------------------------------- /docs/assets/index.11e74c05.css: -------------------------------------------------------------------------------- 1 | body{margin:0}.sprite[data-v-0f13d2ad]{position:absolute;width:20px;height:20px;display:flex;justify-content:center;align-items:center;transition:all .3s}.sprite__inner[data-v-0f13d2ad]{width:10px;height:10px;border-radius:50%;background-color:#00f;animation:wave-0f13d2ad .3s infinite alternate-reverse}@keyframes wave-0f13d2ad{0%{outline:4px solid #9ad59c}to{outline:0px solid #9ad59c}}.maze-view{display:flex;justify-content:center;padding:10px;box-sizing:border-box}.maze-view-center{position:relative}.maze-view-center{outline:4px solid #333}.cell{width:20px;height:20px;border:1px solid #e8e8e8;box-sizing:border-box;background-color:#e8e8e8}.block{background-color:#333}.entry,.out{background-color:#4caf50}.row{display:flex}.maze-view-sprite-container,.maze-view-mask{position:absolute;left:0;right:0;top:0;bottom:0;pointer-events:none}.code-editor[data-v-7287226d]{overflow:auto;background-color:#282c34}.game-canvas[data-v-8f6bbbe0]{height:100vh;display:grid;grid-template-columns:1fr 1fr;grid-template-rows:60px 1fr;background-color:#000}.header[data-v-8f6bbbe0]{grid-column:1 / 3;background-color:#000;text-align:center;border-bottom:2px solid #333}.main[data-v-8f6bbbe0]{padding:10px}h1[data-v-8f6bbbe0]{margin:0;padding:0;height:100%;display:flex;justify-content:center;align-items:center;color:#eee}.sidebar[data-v-8f6bbbe0]{display:flex;flex-direction:column;height:100%;padding:10px;min-width:600px;border-left:2px solid #333;box-sizing:border-box;overflow:hidden}.code-editor[data-v-8f6bbbe0]{flex-grow:1}.tool[data-v-8f6bbbe0]{margin-bottom:10px;color:#eee}input[data-v-8f6bbbe0]{background-color:transparent;border:thin solid #999;margin-right:10px;height:22px;border-radius:2px;color:#eee;outline:none;width:80px}input[data-v-8f6bbbe0]:focus{border-color:#2196f3}button[data-v-8f6bbbe0]{background:#333;border:thin solid #999;margin-right:10px;color:#fff;padding:4px 10px;cursor:pointer;transition:all .2s}button[data-v-8f6bbbe0]:hover{background-color:#eee;color:#333;border:thin solid #fff}.github[data-v-752350c6]{position:fixed;right:10px;top:10px;background-color:#333;border-radius:4px;padding:6px 10px}.github a[data-v-752350c6]{color:#ccc;text-decoration:none;transition:all .2s}.github a[data-v-752350c6]:hover{color:#2196f3} 2 | -------------------------------------------------------------------------------- /src/assets/base.css: -------------------------------------------------------------------------------- 1 | /* color palette from */ 2 | :root { 3 | --vt-c-white: #ffffff; 4 | --vt-c-white-soft: #f8f8f8; 5 | --vt-c-white-mute: #f2f2f2; 6 | 7 | --vt-c-black: #181818; 8 | --vt-c-black-soft: #222222; 9 | --vt-c-black-mute: #282828; 10 | 11 | --vt-c-indigo: #2c3e50; 12 | 13 | --vt-c-divider-light-1: rgba(60, 60, 60, 0.29); 14 | --vt-c-divider-light-2: rgba(60, 60, 60, 0.12); 15 | --vt-c-divider-dark-1: rgba(84, 84, 84, 0.65); 16 | --vt-c-divider-dark-2: rgba(84, 84, 84, 0.48); 17 | 18 | --vt-c-text-light-1: var(--vt-c-indigo); 19 | --vt-c-text-light-2: rgba(60, 60, 60, 0.66); 20 | --vt-c-text-dark-1: var(--vt-c-white); 21 | --vt-c-text-dark-2: rgba(235, 235, 235, 0.64); 22 | } 23 | 24 | /* semantic color variables for this project */ 25 | :root { 26 | --color-background: var(--vt-c-white); 27 | --color-background-soft: var(--vt-c-white-soft); 28 | --color-background-mute: var(--vt-c-white-mute); 29 | 30 | --color-border: var(--vt-c-divider-light-2); 31 | --color-border-hover: var(--vt-c-divider-light-1); 32 | 33 | --color-heading: var(--vt-c-text-light-1); 34 | --color-text: var(--vt-c-text-light-1); 35 | 36 | --section-gap: 160px; 37 | } 38 | 39 | @media (prefers-color-scheme: dark) { 40 | :root { 41 | --color-background: var(--vt-c-black); 42 | --color-background-soft: var(--vt-c-black-soft); 43 | --color-background-mute: var(--vt-c-black-mute); 44 | 45 | --color-border: var(--vt-c-divider-dark-2); 46 | --color-border-hover: var(--vt-c-divider-dark-1); 47 | 48 | --color-heading: var(--vt-c-text-dark-1); 49 | --color-text: var(--vt-c-text-dark-2); 50 | } 51 | } 52 | 53 | *, 54 | *::before, 55 | *::after { 56 | box-sizing: border-box; 57 | margin: 0; 58 | position: relative; 59 | font-weight: normal; 60 | } 61 | 62 | body { 63 | min-height: 100vh; 64 | color: var(--color-text); 65 | background: var(--color-background); 66 | transition: color 0.5s, background-color 0.5s; 67 | line-height: 1.6; 68 | font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, 69 | Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; 70 | font-size: 15px; 71 | text-rendering: optimizeLegibility; 72 | -webkit-font-smoothing: antialiased; 73 | -moz-osx-font-smoothing: grayscale; 74 | } 75 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import type { Grid } from "@/maps"; 2 | export type Position = { 3 | x: number; 4 | y: number; 5 | }; 6 | 7 | export type Step = { 8 | dx: -1 | 0 | 1; 9 | dy: -1 | 0 | 1; 10 | }; 11 | 12 | export function findCellByValue(grid: Grid, num: number): Position | undefined { 13 | for (let i = 0; i < grid.length; i++) { 14 | const cols = grid[i]; 15 | for (let j = 0; j < cols.length; j++) { 16 | if (cols[j] === num) { 17 | return { 18 | x: i, 19 | y: j, 20 | }; 21 | } 22 | } 23 | } 24 | } 25 | 26 | export function walkGrid(gird: Grid) { 27 | if (!gird[0]) return {}; 28 | 29 | const maxY = gird.length - 1; 30 | const maxX = gird[0].length - 1; 31 | let entryPos = { x: 0, y: 0 }; 32 | let outPos = { x: 0, y: 0 }; 33 | 34 | gird.forEach((cols, i) => { 35 | cols.forEach((cell, j) => { 36 | if (cell === -1) { 37 | entryPos = { 38 | x: j, 39 | y: i, 40 | }; 41 | } 42 | 43 | if (cell === -2) { 44 | outPos = { 45 | x: j, 46 | y: i, 47 | }; 48 | } 49 | }); 50 | }); 51 | 52 | return { 53 | maxX, 54 | maxY, 55 | entryPos, 56 | outPos, 57 | }; 58 | } 59 | 60 | export function isBlocked(grid: Grid, postion: Position) { 61 | return grid[postion.y][postion.x] >= 1; 62 | } 63 | 64 | export function minmax(value: number, max = 0, min = 0) { 65 | return Math.min(max, Math.max(min, value)); 66 | } 67 | 68 | export function randomStep() { 69 | const dir = Math.floor(Math.random() * 10) % 6; 70 | const stepMaps = [ 71 | { dx: 0, dy: 1 }, 72 | { dx: -1, dy: 0 }, 73 | { dx: 1, dy: 0 }, 74 | { dx: 0, dy: -1 }, 75 | { dx: 1, dy: 0 }, 76 | { dx: 1, dy: 0 }, 77 | ]; 78 | 79 | return stepMaps[dir]; 80 | } 81 | 82 | export function diffPos(pos1: Position, pos2: Position) { 83 | return { 84 | dx: pos1.x - pos2.x, 85 | dy: pos1.y - pos2.y, 86 | }; 87 | } 88 | 89 | export function getAroundValue(curPos: Position, grid: Grid) { 90 | const { x, y } = curPos; 91 | const top = { x, y: y - 1 }; 92 | const right = { x: x + 1, y }; 93 | const bottom = { x, y: y + 1 }; 94 | const left = { x: x - 1, y }; 95 | 96 | return [ 97 | getPosValue(top, grid), 98 | getPosValue(right, grid), 99 | getPosValue(bottom, grid), 100 | getPosValue(left, grid), 101 | ]; 102 | } 103 | 104 | export function getPosValue(pos: Position, grid: Grid) { 105 | const { x, y } = pos; 106 | if (!grid[y]) return undefined; 107 | 108 | if (grid[y][x] === undefined) return undefined; 109 | 110 | return grid[y][x]; 111 | } 112 | 113 | export * from "./generator"; -------------------------------------------------------------------------------- /src/components/CodeEditor/index.vue: -------------------------------------------------------------------------------- 1 | 192 | 193 | 199 | -------------------------------------------------------------------------------- /src/components/MazeView/index.vue: -------------------------------------------------------------------------------- 1 | 197 | 198 | 241 | -------------------------------------------------------------------------------- /src/utils/route.ts: -------------------------------------------------------------------------------- 1 | import type { Position, Step } from "."; 2 | 3 | export const mySolution = ` 4 | // 接受当前格子坐标,返回下一步行动方向,游戏有自动判赢机制,到达终点自动停止 5 | // 打开DevTools可查看行走步数 6 | // 提交自己的解法和查看其他解法 https://github.com/DakerHub/coding-game-maze/issues/1 7 | // 8 | // curPos { x:0, y:0 } 当前格子坐标 9 | // around [1, 0, 0, 1], 当前格子周围的格子通行情况,依次为上右下左,0为可通信,1为墙壁,-1起点,-2终点 10 | // payload 全局存储挂载对象 11 | // helpers 帮助函数 12 | // - helpers.setCellStyle("background-color: red"); 可设置当前格子的样式 13 | // - helpers.diffPos(pos1, pos2) // { dx: 0, dy: 1 }; 返回两个坐标的相对坐标 14 | // 15 | // return { dx: 1, dy: 0 } dx 表示横向位移,1向右,-1向左,0保持不动 16 | // dy表示纵向位移,1向下,-1向上,0保持不动。每一步只能朝一个方向位移一步,不能斜着走。 17 | 18 | const findNext = function findNext(curPos, around, payload, helpers) { 19 | if (!payload.visited) { 20 | payload.visited = {}; 21 | } 22 | 23 | const { visited } = payload; 24 | const key = getKey(curPos); 25 | const movalePos = getAroundMovablePos(curPos, around); 26 | 27 | if (!visited[key]) { 28 | const prev = movalePos.find((pos) => !!(pos && visited[getKey(pos)])); 29 | visited[key] = { 30 | prev, 31 | next: movalePos.filter((pos) => pos && getKey(pos) !== getKey(prev)), 32 | }; 33 | } 34 | 35 | const { prev, next } = visited[key]; 36 | let nextPos = prev; 37 | if (next.length === 0) { 38 | Reflect.deleteProperty(visited, key); 39 | } else { 40 | [nextPos] = next.splice(randomIntBetween(0, next.length - 1), 1); 41 | } 42 | 43 | setVisitedCountStlye(curPos, payload, helpers); 44 | 45 | return helpers.diffPos(nextPos, curPos); 46 | } 47 | 48 | 49 | function getKey(pos) { 50 | if (!pos) { 51 | return ""; 52 | } 53 | 54 | return \`\${pos.x}_\${pos.y}\`; 55 | } 56 | 57 | const colors = ["#ffcdd2", "#ef9a9a", "#e57373", "#ef5350", "#b71c1c"]; 58 | 59 | function setVisitedCountStlye(curPos, payload, helpers) { 60 | if (!payload.visitCount) { 61 | payload.visitCount = {}; 62 | } 63 | 64 | const count = payload.visitCount[getKey(curPos)] || 0; 65 | const color = colors[Math.min(colors.length - 1, count)]; 66 | 67 | payload.visitCount[getKey(curPos)] = count + 1; 68 | 69 | helpers.setCellStyle(\`background-color: \${color};\`); 70 | } 71 | 72 | function getAroundMovablePos(curPos, around) { 73 | const { x, y } = curPos; 74 | const [top, right, bottom, left] = around; 75 | const movalePos = []; 76 | 77 | const blockedOrOutbound = (val) => val == undefined || val >= 1; 78 | 79 | movalePos.push( 80 | blockedOrOutbound(top) 81 | ? undefined 82 | : { 83 | x, 84 | y: y - 1, 85 | } 86 | ); 87 | movalePos.push( 88 | blockedOrOutbound(right) 89 | ? undefined 90 | : { 91 | x: x + 1, 92 | y, 93 | } 94 | ); 95 | movalePos.push( 96 | blockedOrOutbound(bottom) 97 | ? undefined 98 | : { 99 | x, 100 | y: y + 1, 101 | } 102 | ); 103 | movalePos.push( 104 | blockedOrOutbound(left) 105 | ? undefined 106 | : { 107 | x: x - 1, 108 | y, 109 | } 110 | ); 111 | 112 | return movalePos; 113 | } 114 | 115 | function randomIntBetween(min, max) { 116 | return Math.floor(Math.random() * (max - min + 1) + min); 117 | } 118 | `; 119 | 120 | export const initialCodeText = ` 121 | // 接受当前格子坐标,返回下一步行动方向,游戏有自动判赢机制,到达终点自动停止 122 | // 打开DevTools可查看行走步数 123 | // 提交自己的解法和查看其他解法 https://github.com/DakerHub/coding-game-maze/issues/1 124 | // 125 | // curPos { x:0, y:0 } 当前格子坐标 126 | // around [1, 0, 0, 1], 当前格子周围的格子通行情况,依次为上右下左,0为可通信,1为墙壁,-1起点,-2终点 127 | // payload 全局存储挂载对象 128 | // helpers 帮助函数 129 | // - helpers.setCellStyle("background-color: red"); 可设置当前格子的样式 130 | // - helpers.diffPos(pos1, pos2) // { dx: 0, dy: 1 }; 返回两个坐标的相对坐标 131 | // 132 | // return { dx: 1, dy: 0 } dx 表示横向位移,1向右,-1向左,0保持不动 133 | // dy表示纵向位移,1向下,-1向上,0保持不动。每一步只能朝一个方向位移一步,不能斜着走。 134 | 135 | const findNext = function findNext(curPos, around, payload, helpers) { 136 | return { dx: 1, dy: 0 }; 137 | } 138 | `; 139 | 140 | // 接受当前格子坐标,返回下一个格子坐标,游戏有自动判赢机制,到达终点自动停止 141 | // curPos 当前格子坐标 142 | // around 当前格子周围的格子通行情况,依次为上右下左[top, right, bottom, left],0为可通信,1为墙壁,-1起点,-2终点 143 | // payload 全局存储挂载对象 144 | // helpers 帮助函数 145 | // - helpers.setCellStyle("background-color: red"); 可设置当前格子的样式 146 | export function findNext( 147 | curPos: Position, 148 | around: (number | undefined)[], 149 | payload: any, 150 | helpers: any 151 | ): Step { 152 | return { dx: 1, dy: 0 }; 153 | } 154 | 155 | // import { diffPos, type Position } from "."; 156 | // export function findNext( 157 | // curPos: Position, 158 | // around: (number | undefined)[], 159 | // payload: any, 160 | // helpers: any 161 | // ) { 162 | // if (!payload.visited) { 163 | // payload.visited = {}; 164 | // } 165 | 166 | // const { visited } = payload; 167 | // const key = getKey(curPos); 168 | // const movalePos = getAroundMovablePos(curPos, around); 169 | 170 | // if (!visited[key]) { 171 | // const prev = movalePos.find((pos) => !!(pos && visited[getKey(pos)])); 172 | // visited[key] = { 173 | // prev, 174 | // next: movalePos.filter((pos) => pos && getKey(pos) !== getKey(prev)), 175 | // }; 176 | // } 177 | 178 | // const { prev, next } = visited[key]; 179 | // let nextPos = prev; 180 | // if (next.length === 0) { 181 | // Reflect.deleteProperty(visited, key); 182 | // } else { 183 | // [nextPos] = next.splice(randomIntBetween(0, next.length - 1), 1); 184 | // } 185 | 186 | // setVisitedCountStlye(curPos, payload, helpers); 187 | 188 | // return diffPos(nextPos, curPos); 189 | // } 190 | 191 | function getKey(pos: Position | undefined) { 192 | if (!pos) { 193 | return ""; 194 | } 195 | 196 | return `${pos.x}_${pos.y}`; 197 | } 198 | 199 | const colors = ["#ffcdd2", "#ef9a9a", "#e57373", "#ef5350", "#b71c1c"]; 200 | 201 | function setVisitedCountStlye(curPos: Position, payload: any, helpers: any) { 202 | if (!payload.visitCount) { 203 | payload.visitCount = {}; 204 | } 205 | 206 | const count = payload.visitCount[getKey(curPos)] || 0; 207 | const color = colors[Math.min(colors.length - 1, count)]; 208 | 209 | payload.visitCount[getKey(curPos)] = count + 1; 210 | 211 | helpers.setCellStyle(`background-color: ${color};`); 212 | } 213 | 214 | function getAroundMovablePos(curPos: Position, around: (number | undefined)[]) { 215 | const { x, y } = curPos; 216 | const [top, right, bottom, left] = around; 217 | const movalePos = []; 218 | 219 | const blockedOrOutbound = (val: number | undefined) => 220 | val == undefined || val >= 1; 221 | 222 | movalePos.push( 223 | blockedOrOutbound(top) 224 | ? undefined 225 | : { 226 | x, 227 | y: y - 1, 228 | } 229 | ); 230 | movalePos.push( 231 | blockedOrOutbound(right) 232 | ? undefined 233 | : { 234 | x: x + 1, 235 | y, 236 | } 237 | ); 238 | movalePos.push( 239 | blockedOrOutbound(bottom) 240 | ? undefined 241 | : { 242 | x, 243 | y: y + 1, 244 | } 245 | ); 246 | movalePos.push( 247 | blockedOrOutbound(left) 248 | ? undefined 249 | : { 250 | x: x - 1, 251 | y, 252 | } 253 | ); 254 | 255 | return movalePos; 256 | } 257 | 258 | function randomIntBetween(min: number, max: number) { 259 | return Math.floor(Math.random() * (max - min + 1) + min); 260 | } 261 | -------------------------------------------------------------------------------- /src/components/GameCanvas/index.vue: -------------------------------------------------------------------------------- 1 | 305 | 306 | 379 | -------------------------------------------------------------------------------- /docs/assets/index.e28c893c.js: -------------------------------------------------------------------------------- 1 | var S=Object.defineProperty;var E=Object.getOwnPropertySymbols;var M=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var C=(e,t,o)=>t in e?S(e,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[t]=o,x=(e,t)=>{for(var o in t||(t={}))M.call(t,o)&&C(e,o,t[o]);if(E)for(var o of E(t))$.call(t,o)&&C(e,o,t[o]);return e};var D=(e,t,o)=>(C(e,typeof t!="symbol"?t+"":t,o),o);import{c as createBlock,r as resolveComponent,o as openBlock,d as defineComponent,a as computed,b as createVNode,e as ref,w as watch,f as reactive,E as EditorView,H as HighlightStyle,t as tags,T as Text,g as onMounted,h as EditorState,i as basicSetup,k as keymap,j as indentWithTab,l as javascript,m as i,n as onBeforeUnmount,p as createTextVNode,q as nextTick,F as Fragment,s as createRouter,u as createWebHashHistory,v as createApp,x as createPinia}from"./vendor.52037158.js";const p=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const u of document.querySelectorAll('link[rel="modulepreload"]'))a(u);new MutationObserver(u=>{for(const r of u)if(r.type==="childList")for(const n of r.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&a(n)}).observe(document,{childList:!0,subtree:!0});function o(u){const r={};return u.integrity&&(r.integrity=u.integrity),u.referrerpolicy&&(r.referrerPolicy=u.referrerpolicy),u.crossorigin==="use-credentials"?r.credentials="include":u.crossorigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function a(u){if(u.ep)return;u.ep=!0;const r=o(u);fetch(u.href,r)}};p();var App_vue_vue_type_style_index_0_lang="",_export_sfc=(e,t)=>{const o=e.__vccOpts||e;for(const[a,u]of t)o[a]=u;return o};const _sfc_main$5={};function _sfc_render(e,t){const o=resolveComponent("router-view");return openBlock(),createBlock(o)}var App=_export_sfc(_sfc_main$5,[["render",_sfc_render]]),_sfc_main$4=defineComponent({name:"MazeSprite",props:{grid:{type:Array,default:()=>[]},curPos:{type:Object,default:()=>({x:0,y:0})},interval:{type:Number,default:200}},setup(e){const t=computed(()=>`top: ${e.curPos.y*20}px; left: ${e.curPos.x*20}px; transition-duration: ${e.interval}ms;`);return()=>createVNode("div",{class:"sprite",style:t.value},[createVNode("div",{class:"sprite__inner"},null)])}}),index_vue_vue_type_style_index_0_scoped_true_lang$2="",MazeSprite=_export_sfc(_sfc_main$4,[["__scopeId","data-v-0f13d2ad"]]),_sfc_main$3=defineComponent({name:"MazeView",emits:["cellChange"],props:{grid:{type:Array,default:()=>[]},gridProps:{type:Object,default:()=>({})},cellStyles:{type:Object,default:()=>({})},curPos:{type:Object,default:()=>({x:0,y:0})},mutable:{type:Boolean,default:!1},hasMask:{type:Boolean,default:!0},interval:{type:Number,default:200}},setup(e,{emit:t}){const o=ref(),a=ref(),u=ref("");watch(e.gridProps,s=>{o.value=(s.maxX+1)*20,a.value=(s.maxY+1)*20,u.value=`0 0 ${(s.maxX+1)*20} ${(s.maxY+1)*20}`},{immediate:!0});const r=(s,l,h)=>{e.mutable&&t("cellChange",s,l,h===1?0:1)};let n=!1;const c=()=>{e.mutable&&(n=!0)},d=()=>{e.mutable&&(n=!1)},m=(s,l)=>{e.mutable&&n&&t("cellChange",s,l,0)},B=computed(()=>e.grid.map((s,l)=>{const h=s.map((f,v)=>{const g=[];f>=1?g.push("block"):f===-1?g.push("entry"):f===-2?g.push("out"):g.push("road");const y=e.cellStyles[`${v}_${l}`];return createVNode("div",{class:`col cell ${g.join(" ")}`,style:y,onClick:()=>r(v,l,f),onMousedown:()=>c(),onMouseenter:()=>m(v,l),onMouseup:()=>d()},null)});return createVNode("div",{class:"row"},[h])})),b=reactive({x:-99,y:-99}),F=ref(""),P=(s,l)=>{const{x:h,y:f}=s,{x:v,y:g}=l,y=h*20,N=f*20,w=`M ${y} ${N} ${k()}`;let _="";if(v!==void 0&&g!==void 0){const A=v*20,V=g*20;_=`M ${A} ${V} ${k()}`}F.value=`${w} ${_}`},k=()=>`h -${20} v ${20} h ${20} v ${20} h20 v -${20} h20 v -${20} h -${20} v -${20} h -${20} v ${20}`;return watch(e.curPos,s=>{P(e.curPos,b),Object.assign(b,s)},{immediate:!0}),()=>createVNode("div",{class:"maze-view"},[createVNode("div",{class:"maze-view-center"},[createVNode("div",{class:"maze-view-grid"},[B.value]),createVNode("div",{class:"maze-view-sprite-container"},[createVNode(MazeSprite,{grid:e.grid,curPos:e.curPos,interval:e.interval},null)]),e.hasMask&&createVNode("div",{class:"maze-view-mask"},[createVNode("svg",{viewBox:u.value},[createVNode("mask",{id:"mask"},[createVNode("rect",{x:"0",y:"0",width:o.value,height:a.value,fill:"white"},null),createVNode("path",{d:F.value,fill:"black"},null)]),createVNode("rect",{mask:"url(#mask)",x:"0",y:"0",width:o.value,height:a.value},null)])])])])}}),index_vue_vue_type_style_index_0_lang="";const chalky="#e5c07b",coral="#e06c75",cyan="#56b6c2",invalid="#ffffff",ivory="#abb2bf",stone="#7d8799",malibu="#61afef",sage="#98c379",whiskey="#d19a66",violet="#c678dd",darkBackground="#21252b",highlightBackground="#2c313a",background="#282c34",tooltipBackground="#353a42",selection="#3E4451",cursor="#528bff",oneDarkTheme=EditorView.theme({"&":{color:ivory,backgroundColor:background},".cm-content":{caretColor:cursor},".cm-cursor, .cm-dropCursor":{borderLeftColor:cursor},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":{backgroundColor:selection},".cm-panels":{backgroundColor:darkBackground,color:ivory},".cm-panels.cm-panels-top":{borderBottom:"2px solid black"},".cm-panels.cm-panels-bottom":{borderTop:"2px solid black"},".cm-searchMatch":{backgroundColor:"#72a1ff59",outline:"1px solid #457dff"},".cm-searchMatch.cm-searchMatch-selected":{backgroundColor:"#6199ff2f"},".cm-activeLine":{backgroundColor:highlightBackground},".cm-selectionMatch":{backgroundColor:"#aafe661a"},"&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket":{backgroundColor:"#bad0f847",outline:"1px solid #515a6b"},".cm-gutters":{backgroundColor:background,color:stone,border:"none"},".cm-activeLineGutter":{backgroundColor:highlightBackground},".cm-foldPlaceholder":{backgroundColor:"transparent",border:"none",color:"#ddd"},".cm-tooltip":{border:"none",backgroundColor:tooltipBackground},".cm-tooltip .cm-tooltip-arrow:before":{borderTopColor:"transparent",borderBottomColor:"transparent"},".cm-tooltip .cm-tooltip-arrow:after":{borderTopColor:tooltipBackground,borderBottomColor:tooltipBackground},".cm-tooltip-autocomplete":{"& > ul > li[aria-selected]":{backgroundColor:highlightBackground,color:ivory}}},{dark:!0}),oneDarkHighlightStyle=HighlightStyle.define([{tag:tags.keyword,color:violet},{tag:[tags.name,tags.deleted,tags.character,tags.propertyName,tags.macroName],color:coral},{tag:[tags.function(tags.variableName),tags.labelName],color:malibu},{tag:[tags.color,tags.constant(tags.name),tags.standard(tags.name)],color:whiskey},{tag:[tags.definition(tags.name),tags.separator],color:ivory},{tag:[tags.typeName,tags.className,tags.number,tags.changed,tags.annotation,tags.modifier,tags.self,tags.namespace],color:chalky},{tag:[tags.operator,tags.operatorKeyword,tags.url,tags.escape,tags.regexp,tags.link,tags.special(tags.string)],color:cyan},{tag:[tags.meta,tags.comment],color:stone},{tag:tags.strong,fontWeight:"bold"},{tag:tags.emphasis,fontStyle:"italic"},{tag:tags.strikethrough,textDecoration:"line-through"},{tag:tags.link,color:stone,textDecoration:"underline"},{tag:tags.heading,fontWeight:"bold",color:coral},{tag:[tags.atom,tags.bool,tags.special(tags.variableName)],color:whiskey},{tag:[tags.processingInstruction,tags.string,tags.inserted],color:sage},{tag:tags.invalid,color:invalid}]);var _sfc_main$2=defineComponent({name:"CodeEidtor",props:{value:{type:String,default:""}},setup(e,{expose:t}){const o=Text.of(e.value.split(` 2 | `)||[""]);let a;return onMounted(()=>{a=new EditorView({state:EditorState.create({doc:o,extensions:[basicSetup,keymap.of([indentWithTab]),javascript(),oneDarkTheme,oneDarkHighlightStyle]}),parent:document.querySelector("#editor")})}),t({getDoc:()=>a&&a.state.doc.toJSON(),updateDoc:u=>{let r=a.state.doc.toString();a.dispatch({changes:{from:0,to:r.length,insert:u}})}}),()=>createVNode("div",{class:"code-editor"},[createVNode("div",{id:"editor"},null)])}}),index_vue_vue_type_style_index_0_scoped_true_lang$1="",CodeEditor=_export_sfc(_sfc_main$2,[["__scopeId","data-v-7287226d"]]);const map1={name:"simple1",map:[[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,-2,1,1],[1,1,0,0,1,0,0,0,1,1,1,1,1,1,1,0,0,0,0,0,0,0,1,1],[1,0,1,1,1,1,1,0,0,0,0,0,0,0,1,0,1,1,1,1,1,1,1,1],[1,0,0,0,1,1,1,1,1,1,0,1,0,1,1,0,0,0,0,1,1,1,1,1],[1,1,1,0,1,1,0,0,0,1,0,1,0,0,0,0,1,0,1,1,1,1,1,1],[1,1,1,0,1,1,0,1,1,1,0,1,1,1,1,0,1,1,1,0,0,0,0,1],[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1],[-1,0,1,0,1,1,1,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,0,1],[1,1,1,0,1,1,1,1,1,1,1,0,1,1,0,0,0,0,0,0,0,0,0,1],[1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1],[1,1,1,0,1,1,1,1,1,1,1,0,1,1,1,0,1,0,0,0,0,0,0,1]],entry:{},out:{}};function generateRandomMaze(e=Math.ceil(Math.random()*100),t=Math.ceil(Math.random()*60),o=!0,a=Math.ceil(Math.random()*1e6)){e=Math.min(24,Math.max(3,Math.ceil(e/2+.5))),t=Math.min(20,Math.max(3,Math.ceil(t/2+.5)));const u=i(e,t,o,a),r=[];for(let n=0;n{r.forEach((c,d)=>{c===-1&&(a={x:d,y:n}),c===-2&&(u={x:d,y:n})})}),{maxX:o,maxY:t,entryPos:a,outPos:u}}function isBlocked(e,t){return e[t.y][t.x]>=1}function minmax(e,t=0,o=0){return Math.min(t,Math.max(o,e))}function diffPos(e,t){return{dx:e.x-t.x,dy:e.y-t.y}}function getAroundValue(e,t){const{x:o,y:a}=e,u={x:o,y:a-1},r={x:o+1,y:a},n={x:o,y:a+1},c={x:o-1,y:a};return[getPosValue(u,t),getPosValue(r,t),getPosValue(n,t),getPosValue(c,t)]}function getPosValue(e,t){const{x:o,y:a}=e;if(!!t[a]&&t[a][o]!==void 0)return t[a][o]}class Record{constructor(){D(this,"history",[])}add(t){this.history.push(t)}getAll(){return this.history}clear(){this.history.length=0}}const mySolution=` 3 | // \u63A5\u53D7\u5F53\u524D\u683C\u5B50\u5750\u6807\uFF0C\u8FD4\u56DE\u4E0B\u4E00\u6B65\u884C\u52A8\u65B9\u5411\uFF0C\u6E38\u620F\u6709\u81EA\u52A8\u5224\u8D62\u673A\u5236\uFF0C\u5230\u8FBE\u7EC8\u70B9\u81EA\u52A8\u505C\u6B62 4 | // \u6253\u5F00DevTools\u53EF\u67E5\u770B\u884C\u8D70\u6B65\u6570 5 | // \u63D0\u4EA4\u81EA\u5DF1\u7684\u89E3\u6CD5\u548C\u67E5\u770B\u5176\u4ED6\u89E3\u6CD5 https://github.com/DakerHub/coding-game-maze/issues/1 6 | // 7 | // curPos { x:0, y:0 } \u5F53\u524D\u683C\u5B50\u5750\u6807 8 | // around [1, 0, 0, 1], \u5F53\u524D\u683C\u5B50\u5468\u56F4\u7684\u683C\u5B50\u901A\u884C\u60C5\u51B5\uFF0C\u4F9D\u6B21\u4E3A\u4E0A\u53F3\u4E0B\u5DE6\uFF0C0\u4E3A\u53EF\u901A\u4FE1\uFF0C1\u4E3A\u5899\u58C1\uFF0C-1\u8D77\u70B9\uFF0C-2\u7EC8\u70B9 9 | // payload \u5168\u5C40\u5B58\u50A8\u6302\u8F7D\u5BF9\u8C61 10 | // helpers \u5E2E\u52A9\u51FD\u6570 11 | // - helpers.setCellStyle("background-color: red"); \u53EF\u8BBE\u7F6E\u5F53\u524D\u683C\u5B50\u7684\u6837\u5F0F 12 | // - helpers.diffPos(pos1, pos2) // { dx: 0, dy: 1 }; \u8FD4\u56DE\u4E24\u4E2A\u5750\u6807\u7684\u76F8\u5BF9\u5750\u6807 13 | // 14 | // return { dx: 1, dy: 0 } dx \u8868\u793A\u6A2A\u5411\u4F4D\u79FB\uFF0C1\u5411\u53F3\uFF0C-1\u5411\u5DE6\uFF0C0\u4FDD\u6301\u4E0D\u52A8 15 | // dy\u8868\u793A\u7EB5\u5411\u4F4D\u79FB\uFF0C1\u5411\u4E0B\uFF0C-1\u5411\u4E0A\uFF0C0\u4FDD\u6301\u4E0D\u52A8\u3002\u6BCF\u4E00\u6B65\u53EA\u80FD\u671D\u4E00\u4E2A\u65B9\u5411\u4F4D\u79FB\u4E00\u6B65\uFF0C\u4E0D\u80FD\u659C\u7740\u8D70\u3002 16 | 17 | const findNext = function findNext(curPos, around, payload, helpers) { 18 | if (!payload.visited) { 19 | payload.visited = {}; 20 | } 21 | 22 | const { visited } = payload; 23 | const key = getKey(curPos); 24 | const movalePos = getAroundMovablePos(curPos, around); 25 | 26 | if (!visited[key]) { 27 | const prev = movalePos.find((pos) => !!(pos && visited[getKey(pos)])); 28 | visited[key] = { 29 | prev, 30 | next: movalePos.filter((pos) => pos && getKey(pos) !== getKey(prev)), 31 | }; 32 | } 33 | 34 | const { prev, next } = visited[key]; 35 | let nextPos = prev; 36 | if (next.length === 0) { 37 | Reflect.deleteProperty(visited, key); 38 | } else { 39 | [nextPos] = next.splice(randomIntBetween(0, next.length - 1), 1); 40 | } 41 | 42 | setVisitedCountStlye(curPos, payload, helpers); 43 | 44 | return helpers.diffPos(nextPos, curPos); 45 | } 46 | 47 | 48 | function getKey(pos) { 49 | if (!pos) { 50 | return ""; 51 | } 52 | 53 | return \`\${pos.x}_\${pos.y}\`; 54 | } 55 | 56 | const colors = ["#ffcdd2", "#ef9a9a", "#e57373", "#ef5350", "#b71c1c"]; 57 | 58 | function setVisitedCountStlye(curPos, payload, helpers) { 59 | if (!payload.visitCount) { 60 | payload.visitCount = {}; 61 | } 62 | 63 | const count = payload.visitCount[getKey(curPos)] || 0; 64 | const color = colors[Math.min(colors.length - 1, count)]; 65 | 66 | payload.visitCount[getKey(curPos)] = count + 1; 67 | 68 | helpers.setCellStyle(\`background-color: \${color};\`); 69 | } 70 | 71 | function getAroundMovablePos(curPos, around) { 72 | const { x, y } = curPos; 73 | const [top, right, bottom, left] = around; 74 | const movalePos = []; 75 | 76 | const blockedOrOutbound = (val) => val == undefined || val >= 1; 77 | 78 | movalePos.push( 79 | blockedOrOutbound(top) 80 | ? undefined 81 | : { 82 | x, 83 | y: y - 1, 84 | } 85 | ); 86 | movalePos.push( 87 | blockedOrOutbound(right) 88 | ? undefined 89 | : { 90 | x: x + 1, 91 | y, 92 | } 93 | ); 94 | movalePos.push( 95 | blockedOrOutbound(bottom) 96 | ? undefined 97 | : { 98 | x, 99 | y: y + 1, 100 | } 101 | ); 102 | movalePos.push( 103 | blockedOrOutbound(left) 104 | ? undefined 105 | : { 106 | x: x - 1, 107 | y, 108 | } 109 | ); 110 | 111 | return movalePos; 112 | } 113 | 114 | function randomIntBetween(min, max) { 115 | return Math.floor(Math.random() * (max - min + 1) + min); 116 | } 117 | `,initialCodeText=` 118 | // \u63A5\u53D7\u5F53\u524D\u683C\u5B50\u5750\u6807\uFF0C\u8FD4\u56DE\u4E0B\u4E00\u6B65\u884C\u52A8\u65B9\u5411\uFF0C\u6E38\u620F\u6709\u81EA\u52A8\u5224\u8D62\u673A\u5236\uFF0C\u5230\u8FBE\u7EC8\u70B9\u81EA\u52A8\u505C\u6B62 119 | // \u6253\u5F00DevTools\u53EF\u67E5\u770B\u884C\u8D70\u6B65\u6570 120 | // \u63D0\u4EA4\u81EA\u5DF1\u7684\u89E3\u6CD5\u548C\u67E5\u770B\u5176\u4ED6\u89E3\u6CD5 https://github.com/DakerHub/coding-game-maze/issues/1 121 | // 122 | // curPos { x:0, y:0 } \u5F53\u524D\u683C\u5B50\u5750\u6807 123 | // around [1, 0, 0, 1], \u5F53\u524D\u683C\u5B50\u5468\u56F4\u7684\u683C\u5B50\u901A\u884C\u60C5\u51B5\uFF0C\u4F9D\u6B21\u4E3A\u4E0A\u53F3\u4E0B\u5DE6\uFF0C0\u4E3A\u53EF\u901A\u4FE1\uFF0C1\u4E3A\u5899\u58C1\uFF0C-1\u8D77\u70B9\uFF0C-2\u7EC8\u70B9 124 | // payload \u5168\u5C40\u5B58\u50A8\u6302\u8F7D\u5BF9\u8C61 125 | // helpers \u5E2E\u52A9\u51FD\u6570 126 | // - helpers.setCellStyle("background-color: red"); \u53EF\u8BBE\u7F6E\u5F53\u524D\u683C\u5B50\u7684\u6837\u5F0F 127 | // - helpers.diffPos(pos1, pos2) // { dx: 0, dy: 1 }; \u8FD4\u56DE\u4E24\u4E2A\u5750\u6807\u7684\u76F8\u5BF9\u5750\u6807 128 | // 129 | // return { dx: 1, dy: 0 } dx \u8868\u793A\u6A2A\u5411\u4F4D\u79FB\uFF0C1\u5411\u53F3\uFF0C-1\u5411\u5DE6\uFF0C0\u4FDD\u6301\u4E0D\u52A8 130 | // dy\u8868\u793A\u7EB5\u5411\u4F4D\u79FB\uFF0C1\u5411\u4E0B\uFF0C-1\u5411\u4E0A\uFF0C0\u4FDD\u6301\u4E0D\u52A8\u3002\u6BCF\u4E00\u6B65\u53EA\u80FD\u671D\u4E00\u4E2A\u65B9\u5411\u4F4D\u79FB\u4E00\u6B65\uFF0C\u4E0D\u80FD\u659C\u7740\u8D70\u3002 131 | 132 | const findNext = function findNext(curPos, around, payload, helpers) { 133 | return { dx: 1, dy: 0 }; 134 | } 135 | `;var _sfc_main$1=defineComponent({name:"GameCanvas",setup(props){const hasMask=ref(!0),interval=ref(200),grid=ref(map1.map),gridProps=ref();watch(grid,e=>gridProps.value=walkGrid(e),{immediate:!0});const state=reactive({manual:!1,gridMutable:!1,emptyDoc:!0}),record=new Record,cellStyles=ref({}),codeText=ref(initialCodeText),editor=ref(null),randomSeed=ref(Math.ceil(Math.random()*1e4)),randomWidth=ref(map1.map[0].length),randomHeight=ref(map1.map.length);let timer=0;const curPos=reactive(x({},gridProps.value.entryPos));window.payload={};const changeGrid=(e,t,o)=>{grid.value[t][e]=o},toggleDoc=()=>{var e;(e=editor.value)==null||e.updateDoc(state.emptyDoc?mySolution:initialCodeText),state.emptyDoc=!state.emptyDoc},checkGame=()=>{const{x:e,y:t}=curPos,{outPos:o}=gridProps.value;e===(o==null?void 0:o.x)&&t===o.y&&(console.log("%cDaker","color:#fff;background:#333;padding:2px 4px;border-radius:2px;border-left:4px solid red","win!"),console.log("%cDaker","color:#fff;background:#333;padding:2px 4px;border-radius:2px;border-left:4px solid red",record.getAll().length),clearInterval(timer))},setCellStyle=e=>{cellStyles.value[`${curPos.x}_${curPos.y}`]=e},toggleMask=()=>{hasMask.value=!hasMask.value},generate=()=>{const e=generateRandomMaze(randomWidth.value,randomHeight.value,!0,randomSeed.value);grid.value=e.map,randomSeed.value=e.seed,randomWidth.value=e.width,randomHeight.value=e.height,reset()},generateRandom=()=>{randomSeed.value=void 0,randomWidth.value=void 0,randomHeight.value=void 0,generate()},changeInterval=e=>{interval.value=+e.target.value},move=(e=0,t=0)=>{const o=[-1,0,1];if(!(o.includes(e)&&o.includes(t)))throw new Error("dx, dy must be -1/0/-1");if(e*t!==0)throw new Error("only allow movement in one direction");if(e===0&&t===0)throw new Error("the spirte stop to move");let a=x({},curPos);if(a.x=minmax(a.x+e,gridProps.value.maxX,0),a.y=minmax(a.y+t,gridProps.value.maxY,0),a.x===curPos.x&&a.y===curPos.y)throw new Error("the sprite stop to move");if(!isBlocked(grid.value,a))Object.assign(curPos,a);else throw new Error("can't go through the wall");record.add({pos:curPos}),checkGame()},reset=()=>{stop(),Object.assign(curPos,{x:-99,y:-99}),nextTick(()=>{Object.assign(curPos,gridProps.value.entryPos)}),cellStyles.value={},record.clear(),window.payload={}},play=()=>{clearInterval(timer),timer=setInterval(()=>{var e;try{const around=getAroundValue(curPos,grid.value),codes=(e=editor.value)==null?void 0:e.getDoc(),_diffPos=diffPos,_setCellStyle=setCellStyle,execCode=` 136 | const { dx, dy } = findNext( 137 | curPos, 138 | ${JSON.stringify(around)}, 139 | window.payload, 140 | { 141 | setCellStyle: _setCellStyle, 142 | diffPos: _diffPos 143 | } 144 | ); 145 | move(dx, dy); 146 | `;codes.push(execCode),eval(codes.join(` 147 | `))}catch(t){console.error(t),clearInterval(timer)}},interval.value)},stop=()=>{clearInterval(timer)},updatePositon=e=>{let t,o=0;switch(e.key){case"ArrowUp":o=-1;break;case"ArrowRight":t=1;break;case"ArrowDown":o=1;break;case"ArrowLeft":t=-1;break}move(t,o)};return onMounted(()=>{state.manual&&window.addEventListener("keydown",updatePositon)}),onBeforeUnmount(()=>{clearInterval(timer),state.manual&&window.removeEventListener("keydown",updatePositon)}),()=>createVNode("div",{class:"game-canvas"},[createVNode("div",{class:"header"},[createVNode("h1",null,[createTextVNode("Out of the maze")])]),createVNode("div",{class:"main"},[createVNode("div",{class:"tool"},[createVNode("span",null,[createTextVNode("Seed: ")]),createVNode("input",{value:randomSeed.value,onChange:e=>randomSeed.value=e.target.value?+e.target.value:void 0,type:"number"},null),createVNode("span",null,[createTextVNode("Width: ")]),createVNode("input",{value:randomWidth.value,onChange:e=>randomWidth.value=+e.target.value,type:"number"},null),createVNode("span",null,[createTextVNode("Height: ")]),createVNode("input",{value:randomHeight.value,onChange:e=>randomHeight.value=+e.target.value,type:"number"},null),createVNode("button",{onClick:generate},[createTextVNode("Generate")]),createVNode("button",{onClick:generateRandom},[createTextVNode("Random")])]),createVNode(_sfc_main$3,{grid:grid.value,gridProps,curPos,onCellChange:changeGrid,mutable:state.gridMutable,cellStyles:cellStyles.value,hasMask:hasMask.value,interval:interval.value},null)]),createVNode("div",{class:"sidebar"},[createVNode("div",{class:"tool"},[createVNode("span",null,[createTextVNode("Interval(ms): ")]),createVNode("input",{value:interval.value,onChange:changeInterval,type:"number"},null),createVNode("button",{onClick:play},[createTextVNode("Play")]),createVNode("button",{onClick:reset},[createTextVNode("Reset")]),createVNode("button",{onClick:stop},[createTextVNode("Stop")]),createVNode("button",{onClick:toggleMask},[createTextVNode("Toggle Mask")]),createVNode("button",{onClick:toggleDoc},[state.emptyDoc?"Daker's solution":"Do it yourself"])]),createVNode(CodeEditor,{ref:editor,value:codeText.value},null)])])}}),index_vue_vue_type_style_index_0_scoped_true_lang="",GameCanvas=_export_sfc(_sfc_main$1,[["__scopeId","data-v-8f6bbbe0"]]),_sfc_main=defineComponent({name:"HomeView",setup(e){return()=>createVNode(Fragment,null,[createVNode(GameCanvas,null,null),createVNode("div",{class:"github"},[createVNode("a",{href:"https://github.com/DakerHub/coding-game-maze",target:"__blank"},[createTextVNode("Github - DakerHub")])])])}}),HomeView_vue_vue_type_style_index_0_scoped_true_lang="",HomeView=_export_sfc(_sfc_main,[["__scopeId","data-v-752350c6"]]);const router=createRouter({history:createWebHashHistory("./"),routes:[{path:"/",name:"home",component:HomeView}]}),app=createApp(App);app.use(createPinia());app.use(router);app.mount("#app"); 148 | --------------------------------------------------------------------------------