├── 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 |
4 |
5 |
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 | 
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 |
--------------------------------------------------------------------------------