├── static ├── .nojekyll └── favicon.png ├── .npmrc ├── src ├── routes │ ├── +layout.ts │ ├── +page.ts │ └── +page.svelte ├── index.test.ts ├── components │ ├── maze │ │ ├── explorers │ │ │ ├── transitions.ts │ │ │ ├── ActorTemplate.svelte │ │ │ ├── ActorLocator.svelte │ │ │ ├── Minotaur.svelte │ │ │ ├── actorStores.ts │ │ │ ├── utils.ts │ │ │ └── Theseus.svelte │ │ ├── utils │ │ │ ├── maze.worker.ts │ │ │ ├── debounce.ts │ │ │ ├── maze.ts │ │ │ └── alphabet.ts │ │ ├── Placeholder.svelte │ │ ├── svg │ │ │ └── GateSVG.svelte │ │ ├── Maze.svelte │ │ ├── Cell.svelte │ │ └── MazeLoader.svelte │ ├── controls │ │ ├── svg │ │ │ ├── PrintSVG.svelte │ │ │ ├── CopySVG.svelte │ │ │ ├── TextModeSVG.svelte │ │ │ ├── MapSVG.svelte │ │ │ ├── RefreshSVG.svelte │ │ │ └── GameSVG.svelte │ │ ├── utils.ts │ │ ├── IconButton.svelte │ │ └── Controls.svelte │ └── qr │ │ └── QR.svelte ├── app.d.ts ├── app.html ├── types.ts └── stores.ts ├── .gitignore ├── .eslintignore ├── .prettierignore ├── .prettierrc ├── vite.config.ts ├── tsconfig.json ├── svelte.config.js ├── .eslintrc.cjs ├── README.md ├── package.json ├── .github └── workflows │ └── deploy.yml ├── LICENSE └── pnpm-lock.yaml /static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | resolution-mode=highest 3 | -------------------------------------------------------------------------------- /src/routes/+layout.ts: -------------------------------------------------------------------------------- 1 | export const prerender = true; 2 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nedredmond/a-maze/HEAD/static/favicon.png -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest'; 2 | 3 | describe('sum test', () => { 4 | it('adds 1 + 2 to equal 3', () => { 5 | expect(1 + 2).toBe(3); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/components/maze/explorers/transitions.ts: -------------------------------------------------------------------------------- 1 | import { crossfade } from 'svelte/transition'; 2 | 3 | export const crossfadeTransition = crossfade({ 4 | duration: (d) => Math.sqrt(d * 200), 5 | }); 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /.vscode 6 | /package 7 | .env 8 | .env.* 9 | !.env.example 10 | vite.config.js.timestamp-* 11 | vite.config.ts.timestamp-* 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /src/components/maze/utils/maze.worker.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare const self: DedicatedWorkerGlobalScope; 3 | 4 | import { generateMaze } from './maze'; 5 | 6 | self.onmessage = ({ data }) => self.postMessage(generateMaze(data)); 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 100, 6 | "plugins": ["prettier-plugin-svelte"], 7 | "pluginSearchDirs": ["."], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /src/routes/+page.ts: -------------------------------------------------------------------------------- 1 | export const ssr = false; 2 | import { isTextMode, text } from '../stores.js'; 3 | 4 | export function load({ url }) { 5 | const textParam = url.searchParams.get('text'); 6 | if (textParam) { 7 | isTextMode.set(true); 8 | text.set(decodeURI(textParam)); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface Platform {} 9 | } 10 | } 11 | 12 | export {}; 13 | -------------------------------------------------------------------------------- /src/components/controls/svg/PrintSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/maze/Placeholder.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | {#each { length: $area } as _} 8 |
9 | {/each} 10 | 11 | 17 | -------------------------------------------------------------------------------- /src/components/controls/svg/CopySVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /src/components/controls/svg/TextModeSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/controls/svg/MapSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/controls/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Dimensions } from '../../types'; 2 | 3 | export const MAX_SIZE = 75; 4 | export const MIN_SIZE = 5; 5 | 6 | export const clamp = (value: number, min: number, max: number) => 7 | Math.min(Math.max(value, min), max); 8 | 9 | export const clampDimensions = (dimensions: Dimensions) => ({ 10 | height: clamp(dimensions.height, MIN_SIZE, MAX_SIZE), 11 | width: clamp(dimensions.width, MIN_SIZE, MAX_SIZE), 12 | }); 13 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | // Emulated web worker does not work in vite dev on Firefox 5 | process.env.BROWSER = 'chromium'; 6 | 7 | export default defineConfig({ 8 | plugins: [sveltekit()], 9 | define: { 10 | 'import.meta.vitest': 'undefined', 11 | }, 12 | test: { 13 | include: ['src/**/*.{test,spec}.{js,ts}', 'src/utils/**/*.{js,ts}'], 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/maze/svg/GateSVG.svelte: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/components/controls/svg/RefreshSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /src/components/maze/explorers/ActorTemplate.svelte: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | {actor} 12 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.svelte-kit/tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "checkJs": true, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "resolveJsonModule": true, 9 | "skipLibCheck": true, 10 | "sourceMap": true, 11 | "strict": true, 12 | "types": ["vitest/importMeta"] 13 | } 14 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias 15 | // 16 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes 17 | // from the referenced tsconfig.json - TypeScript does not merge them in 18 | } 19 | -------------------------------------------------------------------------------- /src/components/controls/svg/GameSVG.svelte: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /src/components/qr/QR.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | 22 | 35 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export const Directions = ['top', 'right', 'bottom', 'left'] as const; 2 | export type Direction = (typeof Directions)[number]; 3 | 4 | export type Walls = { 5 | [direction in Direction]?: boolean; 6 | }; 7 | 8 | export type Cell = { 9 | x: number; 10 | y: number; 11 | visited?: boolean; 12 | fill?: Fill; 13 | } & Walls; 14 | 15 | export type Grid = Cell[][]; 16 | 17 | export type Maze = { 18 | dimensions: Dimensions; 19 | grid: Grid; 20 | cells: Cell[]; 21 | }; 22 | 23 | export interface Dimensions { 24 | height: number; 25 | width: number; 26 | } 27 | 28 | export interface Position { 29 | x: number; 30 | y: number; 31 | } 32 | 33 | export type MazeInput = Dimensions | TextMazeInput; 34 | 35 | export type TextMazeInput = { 36 | lines: string[]; 37 | dimensions: Dimensions; 38 | }; 39 | 40 | export type Fill = 'black' | 'white' | null; 41 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import adapter from '@sveltejs/adapter-static'; 2 | import { vitePreprocess } from '@sveltejs/kit/vite'; 3 | 4 | const dev = process.argv.includes('dev'); 5 | 6 | /** @type {import('@sveltejs/kit').Config} */ 7 | const config = { 8 | // Consult https://kit.svelte.dev/docs/integrations#preprocessors 9 | // for more information about preprocessors 10 | preprocess: vitePreprocess(), 11 | 12 | kit: { 13 | // adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list. 14 | // If your environment is not supported or you settled on a specific environment, switch out the adapter. 15 | // See https://kit.svelte.dev/docs/adapters for more information about adapters. 16 | adapter: adapter(), 17 | paths: { 18 | base: dev ? '' : process.env.BASE_PATH, 19 | }, 20 | }, 21 | }; 22 | 23 | export default config; 24 | -------------------------------------------------------------------------------- /src/components/controls/IconButton.svelte: -------------------------------------------------------------------------------- 1 | 8 | 9 | 21 | 22 | 45 | -------------------------------------------------------------------------------- /src/components/maze/explorers/ActorLocator.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | 26 | {#if $theseusIndex === index} 27 | 28 | {/if} 29 | {#if $minotaurIndex === index && showMinotaur} 30 | 31 | {/if} 32 | -------------------------------------------------------------------------------- /src/components/maze/utils/debounce.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | export const debouncePromise = ) => ReturnType>( 3 | func: F, 4 | waitFor: number, 5 | ) => { 6 | let timeout: NodeJS.Timeout | number = 0; 7 | 8 | const debounced = (...args: Parameters) => 9 | new Promise((resolve) => { 10 | clearTimeout(timeout); 11 | timeout = setTimeout(() => resolve(func(...args)), waitFor); 12 | }); 13 | 14 | return debounced as (...args: Parameters) => ReturnType; 15 | }; 16 | 17 | export const debounce = ) => ReturnType>( 18 | func: F, 19 | waitFor: number, 20 | ) => { 21 | let timeout: NodeJS.Timeout | number = 0; 22 | 23 | const debounced = (...args: Parameters) => { 24 | clearTimeout(timeout); 25 | timeout = setTimeout(() => func(...args), waitFor); 26 | }; 27 | 28 | return debounced as (...args: Parameters) => ReturnType; 29 | }; 30 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: [ 4 | 'eslint:recommended', 5 | 'plugin:@typescript-eslint/recommended', 6 | 'plugin:svelte/recommended', 7 | 'prettier', 8 | ], 9 | parser: '@typescript-eslint/parser', 10 | plugins: ['@typescript-eslint'], 11 | ignorePatterns: ['*.cjs'], 12 | parserOptions: { 13 | sourceType: 'module', 14 | ecmaVersion: 2020, 15 | extraFileExtensions: ['.svelte'], 16 | }, 17 | env: { 18 | browser: true, 19 | es2017: true, 20 | node: true, 21 | }, 22 | overrides: [ 23 | { 24 | files: ['*.svelte'], 25 | parser: 'svelte-eslint-parser', 26 | parserOptions: { 27 | parser: '@typescript-eslint/parser', 28 | }, 29 | }, 30 | ], 31 | rules: { 32 | 'no-console': ['warn', {}], 33 | '@typescript-eslint/no-non-null-assertion': 'off', 34 | 'no-unused-vars': 'off', 35 | '@typescript-eslint/no-unused-vars': [ 36 | 'warn', 37 | { 38 | argsIgnorePattern: '^_', 39 | varsIgnorePattern: '^_', 40 | caughtErrorsIgnorePattern: '^_', 41 | }, 42 | ], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # a-maze 2 | 3 | This is a simple maze generator I made to amuse my son. Maybe it will amuse you as well. 4 | 5 | ![Screenshot 2023-05-07 at 11-56-31 A Maze](https://user-images.githubusercontent.com/23404711/236688536-49abf224-93e9-4e25-a565-c7817396f6ee.png) 6 | 7 | ## Features 8 | 9 | - Make a randomized maze (using [DFS](https://en.wikipedia.org/wiki/Depth-first_search)) with any dimensions between 5 cells and 75 cells. 10 | - Anything greater than 75×75 becomes sluggish to render and difficult to represent in most screen resolutions. 11 | - [Text Maze](https://nedredmond.github.io/a-maze/?text=text%0Amaze): Click the [H] icon to generate a maze around letters. 12 | - So far, I've only mapped A-Z. When I have time, I may add numbers and some special characters. 13 | - Print mode will hide the controls, maximize the maze size, and include a QR code that contains a link to the text maze page with the same text. 14 | - If not in text maze mode, will link just to the main site. 15 | - Explore mode will allow you to traverse the maze as a cute little guy. 😃 16 | - Don't let the minotaur catch you on its patrol! 🐮 17 | -------------------------------------------------------------------------------- /src/components/maze/Maze.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 36 | 37 | {#each maze.cells as cell, index} 38 | 39 | {#if $isExplorerMode} 40 | 41 | {/if} 42 | 43 | {/each} 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "a-maze", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite dev --open", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", 10 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", 11 | "test": "vitest", 12 | "lint": "pnpm format && prettier --plugin-search-dir . --check . && eslint . --fix", 13 | "format": "prettier --plugin-search-dir . --write ." 14 | }, 15 | "devDependencies": { 16 | "@sveltejs/adapter-auto": "^2.0.0", 17 | "@sveltejs/adapter-static": "^2.0.2", 18 | "@sveltejs/kit": "^1.5.0", 19 | "@types/node": "^20.1.0", 20 | "@types/qrcode": "^1.5.0", 21 | "@typescript-eslint/eslint-plugin": "^5.45.0", 22 | "@typescript-eslint/parser": "^5.45.0", 23 | "eslint": "^8.28.0", 24 | "eslint-config-prettier": "^8.5.0", 25 | "eslint-plugin-svelte": "^2.26.0", 26 | "prettier": "^2.8.0", 27 | "prettier-plugin-svelte": "^2.8.1", 28 | "svelte": "^3.54.0", 29 | "svelte-check": "^3.0.1", 30 | "tslib": "^2.4.1", 31 | "typescript": "^5.0.0", 32 | "vite": "^4.3.0", 33 | "vitest": "^0.25.3" 34 | }, 35 | "type": "module", 36 | "dependencies": { 37 | "qrcode": "^1.5.3", 38 | "svelte-gestures": "^1.4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: 'main' 6 | 7 | jobs: 8 | build_site: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Install pnpm 15 | uses: pnpm/action-setup@v2 16 | with: 17 | version: 8 18 | 19 | - name: Install Node.js 20 | uses: actions/setup-node@v3 21 | with: 22 | node-version: 18 23 | cache: pnpm 24 | 25 | - name: Install dependencies 26 | run: pnpm install 27 | 28 | - name: build 29 | env: 30 | BASE_PATH: '/your-repo-name' 31 | run: | 32 | pnpm run build 33 | touch build/.nojekyll 34 | 35 | - name: Upload Artifacts 36 | uses: actions/upload-pages-artifact@v1 37 | with: 38 | # this should match the `pages` option in your adapter-static options 39 | path: 'build/' 40 | 41 | deploy: 42 | needs: build_site 43 | runs-on: ubuntu-latest 44 | 45 | permissions: 46 | pages: write 47 | id-token: write 48 | 49 | environment: 50 | name: github-pages 51 | url: ${{ steps.deployment.outputs.page_url }} 52 | 53 | steps: 54 | - name: Deploy 55 | id: deployment 56 | uses: actions/deploy-pages@v1 57 | -------------------------------------------------------------------------------- /src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 19 | 20 | 21 | A Maze 22 | 23 | 24 |
allowInput && onSwipe(e)} 29 | > 30 | 31 | 32 | 33 |
34 | 35 | 59 | -------------------------------------------------------------------------------- /src/components/maze/Cell.svelte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 27 | 28 |
29 | {#if egress} 30 |
31 | 32 |
33 | {/if} 34 | 35 |
36 | 37 | 60 | -------------------------------------------------------------------------------- /src/components/maze/explorers/Minotaur.svelte: -------------------------------------------------------------------------------- 1 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/stores.ts: -------------------------------------------------------------------------------- 1 | import { derived, writable, type Writable } from 'svelte/store'; 2 | import { getLines, getTextMazeDimensions } from './components/maze/utils/maze'; 3 | import type { Cell, Dimensions, Direction, TextMazeInput } from './types'; 4 | import { page } from '$app/stores'; 5 | 6 | export const mazeRef = writable(null); 7 | export const moveDirection = writable(null); 8 | 9 | export const grid = writable([]); 10 | export const dimensions = writable({ height: 9, width: 16 }); 11 | export const area = derived( 12 | dimensions, 13 | ($dimensions: Dimensions) => $dimensions.height * $dimensions.width, 14 | ); 15 | export const orientation = derived, 'portrait' | 'landscape'>( 16 | dimensions, 17 | ($dimensions: Dimensions) => ($dimensions.height > $dimensions.width ? 'portrait' : 'landscape'), 18 | ); 19 | 20 | export const isExplorerMode = writable(false); 21 | export const isTextMode = writable(false); 22 | export const text = writable(`Hello\nworld!`); 23 | 24 | export const textMazeInput = derived, TextMazeInput>(text, ($text: string) => { 25 | const lines = getLines($text); 26 | const textMazeDimensions = getTextMazeDimensions(lines); 27 | isTextMode.subscribe((mode) => mode && dimensions.set(textMazeDimensions)); 28 | return { 29 | lines, 30 | dimensions: textMazeDimensions, 31 | }; 32 | }); 33 | 34 | export const shareURL = derived([page, isTextMode, text], ([$page, $textMode, $text]) => { 35 | const url = $page.url.origin + $page.url.pathname; 36 | return $textMode ? `${url}?text=${encodeURIComponent($text)}` : url; 37 | }); 38 | 39 | export const controlsDisabled = derived(isExplorerMode, ($isExplorerMode) => !$isExplorerMode); 40 | -------------------------------------------------------------------------------- /src/components/maze/explorers/actorStores.ts: -------------------------------------------------------------------------------- 1 | import { derived, get, writable } from 'svelte/store'; 2 | import type { Direction, Position } from '../../../types'; 3 | import { grid, dimensions } from '../../../stores'; 4 | 5 | export const theseusPosition = writable({ x: 0, y: 0 }); 6 | export const theseusIndex = derived( 7 | [theseusPosition, dimensions], 8 | ([$theseusPosition, $dimensions]) => $theseusPosition.y * $dimensions.width + $theseusPosition.x, 9 | ); 10 | 11 | export const minotaurDisabled = writable(false); 12 | export const minotaurStartingPosition = derived(dimensions, ($dimensions) => ({ 13 | x: $dimensions.width - 1, 14 | y: $dimensions.height - 1, 15 | })); 16 | export const minotaurPosition = writable({ 17 | x: get(dimensions).width - 1, 18 | y: get(dimensions).height - 1, 19 | }); 20 | export const minotaurIndex = derived( 21 | [minotaurPosition, dimensions], 22 | ([$minotaurPosition, $dimensions]) => 23 | $minotaurPosition.y * $dimensions.width + $minotaurPosition.x, 24 | ); 25 | export const minotaurLastDirection = writable('top'); 26 | 27 | export const caughtByMinotaur = derived( 28 | [minotaurPosition, theseusPosition], 29 | ([$minotaurPosition, $theseusPosition]) => { 30 | if ($theseusPosition.x === $minotaurPosition.x && $theseusPosition.y === $minotaurPosition.y) { 31 | return true; 32 | } 33 | const { x: mX, y: mY } = $minotaurPosition; 34 | const { x: tX, y: tY } = $theseusPosition; 35 | const adjacentCells: { x: number; y: number; direction: Direction }[] = [ 36 | { x: mX, y: mY - 1, direction: 'top' }, 37 | { x: mX + 1, y: mY, direction: 'right' }, 38 | { x: mX, y: mY + 1, direction: 'bottom' }, 39 | { x: mX - 1, y: mY, direction: 'left' }, 40 | ]; 41 | for (const cell of adjacentCells) { 42 | const { x, y, direction } = cell; 43 | if (x === tX && y === tY && get(grid)[mY][mX][direction]) { 44 | return true; 45 | } 46 | } 47 | return false; 48 | }, 49 | ); 50 | 51 | export const stopGame = writable(false); 52 | -------------------------------------------------------------------------------- /src/components/maze/MazeLoader.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 | 53 |
allowInput && !e.repeat && onKeydown(e)} 60 | > 61 | {#if Worker} 62 | {#await getMaze($isTextMode ? $textMazeInput : $dimensions)} 63 | 64 | {:then maze} 65 | 66 | {:catch e} 67 |

{e.message}

68 | {/await} 69 | {:else} 70 | 71 | {/if} 72 |
73 | 74 | 82 | -------------------------------------------------------------------------------- /src/components/maze/explorers/utils.ts: -------------------------------------------------------------------------------- 1 | import { get } from 'svelte/store'; 2 | import type { Position, Direction, Cell } from '../../../types'; 3 | import { theseusPosition } from './actorStores'; 4 | 5 | export const updatePosition = ( 6 | position: Position, 7 | cell: Cell, 8 | direction?: Direction, 9 | ): Position | undefined => { 10 | switch (true) { 11 | case !direction: 12 | return; 13 | case direction === 'top' && cell.top: 14 | position.y--; 15 | return position; 16 | case direction === 'bottom' && cell.bottom: 17 | position.y++; 18 | return position; 19 | case direction === 'left' && cell.left: 20 | position.x--; 21 | return position; 22 | case direction === 'right' && cell.right: 23 | position.x++; 24 | return position; 25 | } 26 | }; 27 | 28 | // theseus 29 | export const moods = { 30 | default: ['😃', '🙂', '😀', '😄'], 31 | dead: ['😵', '🤕', '😵‍💫', '😭'], 32 | escaped: ['😎', '🏆', '🥳', '😜'], 33 | oops: ['😗', '😕', '😖', '😤'], 34 | idle: ['😪', '😴', '😑', '🙃'], 35 | }; 36 | export const getExpression = (mood: keyof typeof moods) => { 37 | const theseusState = moods[mood]; 38 | return theseusState[Math.floor(Math.random() * theseusState.length)]; 39 | }; 40 | export const restingExpression = ({ 41 | idle, 42 | escaped, 43 | gameOver, 44 | }: { 45 | idle: boolean; 46 | escaped: boolean; 47 | gameOver: boolean; 48 | }) => { 49 | switch (true) { 50 | case gameOver: 51 | return getExpression('dead'); 52 | case escaped: 53 | return getExpression('escaped'); 54 | case idle: 55 | return getExpression('idle'); 56 | default: 57 | return getExpression('default'); 58 | } 59 | }; 60 | export const KeyDirection: { [key: string]: Direction } = { 61 | ArrowUp: 'top', 62 | ArrowDown: 'bottom', 63 | ArrowLeft: 'left', 64 | ArrowRight: 'right', 65 | } as const; 66 | 67 | // minotaur 68 | export const RelativeDirection = { 69 | left: { 70 | top: 'left', 71 | bottom: 'right', 72 | left: 'bottom', 73 | right: 'top', 74 | }, 75 | right: { 76 | top: 'right', 77 | bottom: 'left', 78 | left: 'top', 79 | right: 'bottom', 80 | }, 81 | back: { 82 | top: 'bottom', 83 | bottom: 'top', 84 | left: 'right', 85 | right: 'left', 86 | }, 87 | } as const; 88 | export const updateForDirection = (nextPosition: Position, direction: Direction): Position => { 89 | const orignalPosition = { ...nextPosition }; 90 | switch (direction) { 91 | case 'top': 92 | nextPosition.y--; 93 | break; 94 | case 'bottom': 95 | nextPosition.y++; 96 | break; 97 | case 'left': 98 | nextPosition.x--; 99 | break; 100 | case 'right': 101 | nextPosition.x++; 102 | break; 103 | } 104 | const tPos = get(theseusPosition); 105 | if (nextPosition.x === tPos.x && nextPosition.y === tPos.y) return orignalPosition; 106 | return nextPosition; 107 | }; 108 | -------------------------------------------------------------------------------- /src/components/maze/explorers/Theseus.svelte: -------------------------------------------------------------------------------- 1 | 77 | 78 |
79 | 80 |
81 | 82 | 141 | -------------------------------------------------------------------------------- /src/components/controls/Controls.svelte: -------------------------------------------------------------------------------- 1 | 51 | 52 |
53 |
54 |
55 | {#if $isExplorerMode} 56 | {#if $stopGame} 57 | Game over! 58 | {:else} 59 | Escape the maze{$minotaurDisabled ? '' : ' and avoid the minotaur'}! 60 | {/if} 61 | 67 | {:else if $isTextMode} 68 | 69 | {:else} 70 | 71 | Maze dimensions: 72 | 73 | {$dimensions.width} 74 | × 75 | {$dimensions.height} 76 | 77 | 78 | {/if} 79 |
80 | 113 |
114 | {#if !$isExplorerMode} 115 | {#if $isTextMode} 116 |