├── .gitignore ├── src ├── main.js ├── models.js ├── store.js ├── input.js ├── map.js ├── snake.svelte └── snake.js ├── .vscode └── settings.json ├── screenshot.png ├── docs ├── favicon.png ├── 404.html ├── index.html └── bundle.js ├── public ├── favicon.png └── index.html ├── package.json ├── ts ├── store.ts ├── models.ts ├── input.ts ├── map.ts └── snake.ts ├── README.md ├── rollup.config.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /public/build/ 3 | 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Snake from './snake.svelte'; 2 | 3 | export default Snake; 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "svelte.plugin.typescript.diagnostics.enable": false 3 | } -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gogakoreli/svelte-snake-web-component/HEAD/screenshot.png -------------------------------------------------------------------------------- /docs/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gogakoreli/svelte-snake-web-component/HEAD/docs/favicon.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gogakoreli/svelte-snake-web-component/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Svelte Web Component 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/models.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var Direction; 4 | (function (Direction) { 5 | Direction[Direction["None"] = -1] = "None"; 6 | Direction[Direction["North"] = 0] = "North"; 7 | Direction[Direction["East"] = 1] = "East"; 8 | Direction[Direction["South"] = 2] = "South"; 9 | Direction[Direction["West"] = 3] = "West"; 10 | })(Direction = exports.Direction || (exports.Direction = {})); 11 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte Web Component 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Svelte Web Component 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const snake_1 = require("./snake"); 4 | const store_1 = require("svelte/store"); 5 | const DEFAULT_GAME_STATE = snake_1.defaultGameState(); 6 | class Store { 7 | constructor() { 8 | this.state = store_1.writable(DEFAULT_GAME_STATE); 9 | } 10 | select() { 11 | return this.state; 12 | } 13 | reduce(reducer) { 14 | this.state.update(reducer); 15 | } 16 | reset() { 17 | this.state.set(DEFAULT_GAME_STATE); 18 | } 19 | } 20 | exports.Store = Store; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "build": "rollup -c", 6 | "dev": "rollup -c -w", 7 | "ts-dev": "tsc --watch", 8 | "start": "sirv public" 9 | }, 10 | "devDependencies": { 11 | "@rollup/plugin-commonjs": "11.0.2", 12 | "@rollup/plugin-node-resolve": "^7.0.0", 13 | "rollup": "^1.20.0", 14 | "rollup-plugin-livereload": "^1.0.0", 15 | "rollup-plugin-svelte": "^5.0.3", 16 | "rollup-plugin-terser": "^5.1.2", 17 | "svelte": "^3.0.0" 18 | }, 19 | "dependencies": { 20 | "sirv-cli": "^0.4.4" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ts/store.ts: -------------------------------------------------------------------------------- 1 | import { GameState } from './models'; 2 | import { defaultGameState } from './snake'; 3 | import { writable, Writable, Readable } from 'svelte/store' 4 | 5 | const DEFAULT_GAME_STATE = defaultGameState(); 6 | 7 | export class Store { 8 | private state: Writable = writable(DEFAULT_GAME_STATE); 9 | 10 | public select(): Readable { 11 | return this.state; 12 | } 13 | 14 | public reduce(reducer: (state: GameState) => GameState) { 15 | this.state.update(reducer); 16 | } 17 | 18 | public reset() { 19 | this.state.set(DEFAULT_GAME_STATE); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Svelte Snake Web Component 2 | 3 | Snake Game Web Component created with Svelte 4 | 5 | ## Article 6 | 7 | 📝 Read Medium article about this project: [Svelte Web Component - 5.4KB](https://medium.com/@gogakoreli/svelte-web-component-5-4kb-4afe46590d99) 8 | 9 | 📝 Read similar Medium article about the Angular project: [Angular 9 Snake Web Component - 51KB](https://itnext.io/angular-9-snake-web-component-96f61e63b158) 10 | 11 | ## Game 12 | 13 | bundle.js final size is: ~5.4 KB (Gzipped) 14 | 15 | 🎮 Play game at https://gogakoreli.github.io/svelte-snake-web-component 16 | 17 | ⌨️ Use W A S D or Arrow Keys for movement 18 | 19 | ![Alt text](/screenshot.png?raw=true 'Svelte Snake Game Snapshot') 20 | 21 | by Goga Koreli 22 | -------------------------------------------------------------------------------- /ts/models.ts: -------------------------------------------------------------------------------- 1 | export interface SnakePart { 2 | i: number; 3 | j: number; 4 | } 5 | 6 | export interface Snake { 7 | head: SnakePart; 8 | parts: SnakePart[]; 9 | direction: Direction; 10 | length: number; 11 | foodEaten: boolean; 12 | } 13 | 14 | export enum Direction { 15 | None = -1, 16 | North = 0, 17 | East = 1, 18 | South = 2, 19 | West = 3, 20 | } 21 | 22 | export interface Food { 23 | i: number; 24 | j: number; 25 | } 26 | 27 | export interface Map { 28 | grid: Tile[][]; 29 | } 30 | 31 | export interface Tile { 32 | isFood: boolean; 33 | isSnake: boolean; 34 | isSnakeHead: boolean; 35 | } 36 | 37 | export interface Game { 38 | snake: Snake; 39 | map: Map; 40 | food: Food; 41 | gameOver: boolean; 42 | } 43 | 44 | export interface GameState { 45 | game: Game; 46 | directions: Direction[]; 47 | shouldRender: boolean; 48 | } 49 | -------------------------------------------------------------------------------- /ts/input.ts: -------------------------------------------------------------------------------- 1 | const UP_ARR_KEY_CODE = 38; 2 | const RIGHT_ARR_KEY_CODE = 39; 3 | const DOWN_ARR_KEY_CODE = 40; 4 | const LEFT_ARR_KEY_CODE = 37; 5 | 6 | const W_KEY_CODE = 87; 7 | const A_KEY_CODE = 65; 8 | const S_KEY_CODE = 83; 9 | const D_KEY_CODE = 68; 10 | 11 | const SPACE_KEY_CODE = 32; 12 | 13 | function isUpPressed(keyCode: number) { 14 | return keyCode === W_KEY_CODE || keyCode === UP_ARR_KEY_CODE; 15 | } 16 | 17 | function isRightPressed(keyCode: number) { 18 | return keyCode === D_KEY_CODE || keyCode === RIGHT_ARR_KEY_CODE; 19 | } 20 | 21 | function isDownPressed(keyCode: number) { 22 | return keyCode === S_KEY_CODE || keyCode === DOWN_ARR_KEY_CODE; 23 | } 24 | 25 | function isLeftPressed(keyCode: number) { 26 | return keyCode === A_KEY_CODE || keyCode === LEFT_ARR_KEY_CODE; 27 | } 28 | 29 | function isSpacePressed(keyCode: number) { 30 | return keyCode === SPACE_KEY_CODE; 31 | } 32 | 33 | export enum InputKey { 34 | None = -1, 35 | Up = 0, 36 | Right = 1, 37 | Down = 2, 38 | Left = 3, 39 | Space = 4, 40 | } 41 | 42 | export function getInputKey(keyCode: number): InputKey { 43 | let result = InputKey.None; 44 | if (isUpPressed(keyCode)) { 45 | result = InputKey.Up; 46 | } else if (isRightPressed(keyCode)) { 47 | result = InputKey.Right; 48 | } else if (isDownPressed(keyCode)) { 49 | result = InputKey.Down; 50 | } else if (isLeftPressed(keyCode)) { 51 | result = InputKey.Left; 52 | } else if (isSpacePressed(keyCode)) { 53 | result = InputKey.Space; 54 | } 55 | return result; 56 | } 57 | -------------------------------------------------------------------------------- /src/input.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const UP_ARR_KEY_CODE = 38; 4 | const RIGHT_ARR_KEY_CODE = 39; 5 | const DOWN_ARR_KEY_CODE = 40; 6 | const LEFT_ARR_KEY_CODE = 37; 7 | const W_KEY_CODE = 87; 8 | const A_KEY_CODE = 65; 9 | const S_KEY_CODE = 83; 10 | const D_KEY_CODE = 68; 11 | const SPACE_KEY_CODE = 32; 12 | function isUpPressed(keyCode) { 13 | return keyCode === W_KEY_CODE || keyCode === UP_ARR_KEY_CODE; 14 | } 15 | function isRightPressed(keyCode) { 16 | return keyCode === D_KEY_CODE || keyCode === RIGHT_ARR_KEY_CODE; 17 | } 18 | function isDownPressed(keyCode) { 19 | return keyCode === S_KEY_CODE || keyCode === DOWN_ARR_KEY_CODE; 20 | } 21 | function isLeftPressed(keyCode) { 22 | return keyCode === A_KEY_CODE || keyCode === LEFT_ARR_KEY_CODE; 23 | } 24 | function isSpacePressed(keyCode) { 25 | return keyCode === SPACE_KEY_CODE; 26 | } 27 | var InputKey; 28 | (function (InputKey) { 29 | InputKey[InputKey["None"] = -1] = "None"; 30 | InputKey[InputKey["Up"] = 0] = "Up"; 31 | InputKey[InputKey["Right"] = 1] = "Right"; 32 | InputKey[InputKey["Down"] = 2] = "Down"; 33 | InputKey[InputKey["Left"] = 3] = "Left"; 34 | InputKey[InputKey["Space"] = 4] = "Space"; 35 | })(InputKey = exports.InputKey || (exports.InputKey = {})); 36 | function getInputKey(keyCode) { 37 | let result = InputKey.None; 38 | if (isUpPressed(keyCode)) { 39 | result = InputKey.Up; 40 | } 41 | else if (isRightPressed(keyCode)) { 42 | result = InputKey.Right; 43 | } 44 | else if (isDownPressed(keyCode)) { 45 | result = InputKey.Down; 46 | } 47 | else if (isLeftPressed(keyCode)) { 48 | result = InputKey.Left; 49 | } 50 | else if (isSpacePressed(keyCode)) { 51 | result = InputKey.Space; 52 | } 53 | return result; 54 | } 55 | exports.getInputKey = getInputKey; 56 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import svelte from 'rollup-plugin-svelte'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import commonjs from '@rollup/plugin-commonjs'; 4 | import livereload from 'rollup-plugin-livereload'; 5 | import { terser } from 'rollup-plugin-terser'; 6 | 7 | const production = !process.env.ROLLUP_WATCH; 8 | 9 | export default { 10 | input: 'src/main.js', 11 | output: { 12 | sourcemap: true, 13 | format: 'iife', 14 | name: 'app', 15 | file: 'public/build/bundle.js' 16 | }, 17 | plugins: [ 18 | svelte({ 19 | customElement: true, 20 | // enable run-time checks when not in production 21 | dev: !production, 22 | // we'll extract any component CSS out into 23 | // a separate file - better for performance 24 | css: css => { 25 | css.write('public/build/bundle.css'); 26 | } 27 | }), 28 | 29 | // If you have external dependencies installed from 30 | // npm, you'll most likely need these plugins. In 31 | // some cases you'll need additional configuration - 32 | // consult the documentation for details: 33 | // https://github.com/rollup/plugins/tree/master/packages/commonjs 34 | resolve({ 35 | browser: true, 36 | dedupe: ['svelte'] 37 | }), 38 | commonjs(), 39 | 40 | // In dev mode, call `npm run start` once 41 | // the bundle has been generated 42 | !production && serve(), 43 | 44 | // Watch the `public` directory and refresh the 45 | // browser on changes when not in production 46 | !production && livereload('public'), 47 | 48 | // If we're building for production (npm run build 49 | // instead of npm run dev), minify 50 | production && terser() 51 | ], 52 | watch: { 53 | clearScreen: false 54 | } 55 | }; 56 | 57 | function serve() { 58 | let started = false; 59 | 60 | return { 61 | writeBundle() { 62 | if (!started) { 63 | started = true; 64 | 65 | require('child_process').spawn('npm', ['run', 'start', '--', '--dev'], { 66 | stdio: ['ignore', 'inherit', 'inherit'], 67 | shell: true 68 | }); 69 | } 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /ts/map.ts: -------------------------------------------------------------------------------- 1 | import { Food, Snake, Map, Game, Tile } from './models'; 2 | 3 | export const MAP_WIDTH = 20; 4 | export const MAP_HEIGHT = 20; 5 | 6 | const SNAKE_FOOD_TILE = { 7 | isFood: true, 8 | isSnake: false, 9 | isSnakeHead: false, 10 | }; 11 | 12 | const SNAKE_PART_TILE = { 13 | isFood: false, 14 | isSnake: true, 15 | isSnakeHead: false, 16 | }; 17 | 18 | const SNAKE_HEAD_TILE = { 19 | isFood: false, 20 | isSnake: true, 21 | isSnakeHead: true, 22 | }; 23 | 24 | export function defaultMap(): Map { 25 | const grid = emptyGrid(); 26 | 27 | return { 28 | grid, 29 | }; 30 | } 31 | 32 | export function updateMap(map: Map, snake: Snake, food: Food): Map { 33 | const grid = emptyGrid(); 34 | grid[food.i][food.j] = SNAKE_FOOD_TILE; 35 | snake.parts.forEach((part) => { 36 | grid[part.i][part.j] = SNAKE_PART_TILE; 37 | }); 38 | 39 | grid[snake.head.i][snake.head.j] = SNAKE_HEAD_TILE; 40 | 41 | return { 42 | ...map, 43 | grid, 44 | }; 45 | } 46 | 47 | export function isInBorders(map: Map, i: number, j: number): boolean { 48 | let inBorders = i >= 0 && i < MAP_HEIGHT && j >= 0 && j < MAP_WIDTH; 49 | return inBorders; 50 | } 51 | 52 | export function isSnakeTile(map: Map, i: number, j: number): boolean { 53 | const tile = map.grid[i][j]; 54 | return tile.isSnake; 55 | } 56 | 57 | function isEmptyTile(map: Map, i: number, j: number): boolean { 58 | const tile = map.grid[i][j]; 59 | return !tile.isFood && !tile.isSnake && !tile.isSnakeHead; 60 | } 61 | 62 | function emptyGrid(): Tile[][] { 63 | return initGrid((i, j) => { 64 | return { isFood: false, isSnake: false, isSnakeHead: false }; 65 | }); 66 | } 67 | 68 | function initGrid(setItem: (i: number, j: number) => Tile): Tile[][] { 69 | const grid: Tile[][] = []; 70 | for (let i = 0; i < MAP_HEIGHT; i++) { 71 | grid[i] = []; 72 | for (let j = 0; j < MAP_WIDTH; j++) { 73 | grid[i][j] = setItem(i, j); 74 | } 75 | } 76 | return grid; 77 | } 78 | 79 | export function randomFood(game: Game, findNew = true): Food { 80 | let food = game.food; 81 | if (findNew) { 82 | while (true) { 83 | const i = Math.floor(Math.random() * MAP_HEIGHT); 84 | const j = Math.floor(Math.random() * MAP_WIDTH); 85 | if (isEmptyTile(game.map, i, j)) { 86 | food = { i, j }; 87 | break; 88 | } 89 | } 90 | } 91 | return food; 92 | } 93 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.MAP_WIDTH = 20; 4 | exports.MAP_HEIGHT = 20; 5 | const SNAKE_FOOD_TILE = { 6 | isFood: true, 7 | isSnake: false, 8 | isSnakeHead: false, 9 | }; 10 | const SNAKE_PART_TILE = { 11 | isFood: false, 12 | isSnake: true, 13 | isSnakeHead: false, 14 | }; 15 | const SNAKE_HEAD_TILE = { 16 | isFood: false, 17 | isSnake: true, 18 | isSnakeHead: true, 19 | }; 20 | function defaultMap() { 21 | const grid = emptyGrid(); 22 | return { 23 | grid, 24 | }; 25 | } 26 | exports.defaultMap = defaultMap; 27 | function updateMap(map, snake, food) { 28 | const grid = emptyGrid(); 29 | grid[food.i][food.j] = SNAKE_FOOD_TILE; 30 | snake.parts.forEach((part) => { 31 | grid[part.i][part.j] = SNAKE_PART_TILE; 32 | }); 33 | grid[snake.head.i][snake.head.j] = SNAKE_HEAD_TILE; 34 | return Object.assign(Object.assign({}, map), { grid }); 35 | } 36 | exports.updateMap = updateMap; 37 | function isInBorders(map, i, j) { 38 | let inBorders = i >= 0 && i < exports.MAP_HEIGHT && j >= 0 && j < exports.MAP_WIDTH; 39 | return inBorders; 40 | } 41 | exports.isInBorders = isInBorders; 42 | function isSnakeTile(map, i, j) { 43 | const tile = map.grid[i][j]; 44 | return tile.isSnake; 45 | } 46 | exports.isSnakeTile = isSnakeTile; 47 | function isEmptyTile(map, i, j) { 48 | const tile = map.grid[i][j]; 49 | return !tile.isFood && !tile.isSnake && !tile.isSnakeHead; 50 | } 51 | function emptyGrid() { 52 | return initGrid((i, j) => { 53 | return { isFood: false, isSnake: false, isSnakeHead: false }; 54 | }); 55 | } 56 | function initGrid(setItem) { 57 | const grid = []; 58 | for (let i = 0; i < exports.MAP_HEIGHT; i++) { 59 | grid[i] = []; 60 | for (let j = 0; j < exports.MAP_WIDTH; j++) { 61 | grid[i][j] = setItem(i, j); 62 | } 63 | } 64 | return grid; 65 | } 66 | function randomFood(game, findNew = true) { 67 | let food = game.food; 68 | if (findNew) { 69 | while (true) { 70 | const i = Math.floor(Math.random() * exports.MAP_HEIGHT); 71 | const j = Math.floor(Math.random() * exports.MAP_WIDTH); 72 | if (isEmptyTile(game.map, i, j)) { 73 | food = { i, j }; 74 | break; 75 | } 76 | } 77 | } 78 | return food; 79 | } 80 | exports.randomFood = randomFood; 81 | -------------------------------------------------------------------------------- /src/snake.svelte: -------------------------------------------------------------------------------- 1 | 45 | 46 | 132 | 133 | 134 | 135 | 136 |
137 |
138 |

Snake

139 | Score: {score} 140 |
141 | {#if !running} 142 | 143 | {:else} 144 | 145 | {/if} 146 |
147 |
148 | 149 |
150 |
151 | {#each grid as row, rowIndex} 152 | {#each row as tile} 153 |
159 | {/each} 160 | {/each} 161 |
162 | 163 | {#if gameOver} 164 |
165 | Game Over 166 | Score: {score} 167 | 168 |
169 | {/if} 170 |
171 |
172 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | // "allowJs": true, /* Allow javascript files to be compiled. */ 9 | // "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | "outDir": "./src", /* Redirect output structure to the directory. */ 16 | "rootDir": "./ts", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | // "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | /* Module Resolution Options */ 39 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 40 | "baseUrl": "./ts", /* Base directory to resolve non-absolute module names. */ 41 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 42 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 43 | // "typeRoots": [], /* List of folders to include type definitions from. */ 44 | // "types": [], /* Type declaration files to be included in compilation. */ 45 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 46 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 47 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 48 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 49 | /* Source Map Options */ 50 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 51 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 52 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 53 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 54 | /* Experimental Options */ 55 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 56 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 57 | /* Advanced Options */ 58 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 59 | } 60 | } -------------------------------------------------------------------------------- /ts/snake.ts: -------------------------------------------------------------------------------- 1 | import { Direction, Food, Snake, SnakePart, Game, GameState } from './models'; 2 | import { InputKey, getInputKey } from './input'; 3 | import { defaultMap, randomFood, updateMap, isInBorders } from './map'; 4 | 5 | export function defaultGameState(): GameState { 6 | return { 7 | game: defaultGame(), 8 | directions: [Direction.East], 9 | shouldRender: true, 10 | }; 11 | } 12 | 13 | function defaultGame(): Game { 14 | const food = defaultFood(); 15 | const snake = defaultSnake(); 16 | const map = updateMap(defaultMap(), snake, food); 17 | const gameOver = false; 18 | return { snake, map, food, gameOver }; 19 | } 20 | 21 | function defaultSnake(): Snake { 22 | const parts = [{ i: 0, j: 0 }, { i: 0, j: 1 }, { i: 0, j: 2 }]; 23 | return { 24 | direction: Direction.East, 25 | head: parts[parts.length - 1], 26 | length: parts.length, 27 | parts, 28 | foodEaten: false, 29 | }; 30 | } 31 | 32 | function defaultFood(): Food { 33 | return { i: 0, j: 10 }; 34 | } 35 | 36 | function validNextDirection(curr: Direction, next: Direction): boolean { 37 | let result = false; 38 | if (next !== Direction.None) { 39 | switch (curr) { 40 | case Direction.North: 41 | result = next !== Direction.South; 42 | break; 43 | case Direction.East: 44 | result = next !== Direction.West; 45 | break; 46 | case Direction.South: 47 | result = next !== Direction.North; 48 | break; 49 | case Direction.West: 50 | result = next !== Direction.East; 51 | break; 52 | case Direction.None: 53 | result = false; 54 | break; 55 | } 56 | } 57 | return result; 58 | } 59 | 60 | function getNewHead(snake: Snake): SnakePart { 61 | const head = snake.head; 62 | let newHead = head; 63 | switch (snake.direction) { 64 | case Direction.North: 65 | newHead = { i: head.i - 1, j: head.j }; 66 | break; 67 | case Direction.East: 68 | newHead = { i: head.i, j: head.j + 1 }; 69 | break; 70 | case Direction.South: 71 | newHead = { i: head.i + 1, j: head.j }; 72 | break; 73 | case Direction.West: 74 | newHead = { i: head.i, j: head.j - 1 }; 75 | break; 76 | } 77 | return newHead; 78 | } 79 | 80 | function moveToDirection(snake: Snake, direction: Direction): Snake { 81 | if (validNextDirection(snake.direction, direction)) { 82 | snake = { 83 | ...snake, 84 | direction, 85 | }; 86 | } 87 | 88 | const newHead = getNewHead(snake); 89 | snake.parts = [...snake.parts, newHead]; 90 | 91 | return { 92 | ...snake, 93 | head: newHead, 94 | parts: snake.parts, 95 | length: snake.parts.length, 96 | }; 97 | } 98 | 99 | function snakeFoodEaten(snake: Snake, food: Food): Snake { 100 | const foodEaten = snake.head.i === food.i && snake.head.j === food.j; 101 | let parts = snake.parts; 102 | 103 | let [tail, ...rest] = snake.parts; 104 | if (!foodEaten) { 105 | parts = rest; 106 | } 107 | 108 | return { 109 | ...snake, 110 | foodEaten, 111 | parts, 112 | length: parts.length, 113 | }; 114 | } 115 | 116 | function isGameOver(game: Game): boolean { 117 | const { snake, snake: { head } } = game; 118 | return !isInBorders(game.map, head.i, head.j) || 119 | snake.parts.some(part => part !== head && part.i === head.i && part.j === head.j); 120 | } 121 | 122 | function tick(game: Game, direction: Direction): Game { 123 | game = { ...game, snake: moveToDirection(game.snake, direction) }; 124 | game = { ...game, snake: snakeFoodEaten(game.snake, game.food) }; 125 | game = { ...game, food: randomFood(game, game.snake.foodEaten) }; 126 | game = { ...game, gameOver: isGameOver(game) }; 127 | if (!game.gameOver) { 128 | game = { ...game, map: updateMap(game.map, game.snake, game.food) }; 129 | } 130 | return game; 131 | } 132 | 133 | export function tickReducer(state: GameState): GameState { 134 | if (state.game.gameOver) { 135 | return state; 136 | } 137 | 138 | const [curDirection, nextDirection, ...rest] = state.directions; 139 | let direction = curDirection; 140 | if (nextDirection !== undefined) { 141 | direction = nextDirection; 142 | } 143 | const directions = state.directions.length === 1 144 | ? state.directions 145 | : [nextDirection, ...rest]; 146 | const game = tick(state.game, direction); 147 | return { 148 | ...state, 149 | game, 150 | directions, 151 | shouldRender: true, 152 | }; 153 | } 154 | 155 | function inputToDirection(inputKey: InputKey): Direction { 156 | let res: Direction = Direction.None; 157 | switch (inputKey) { 158 | case InputKey.Left: 159 | res = Direction.West; 160 | break; 161 | case InputKey.Right: 162 | res = Direction.East; 163 | break; 164 | case InputKey.Down: 165 | res = Direction.South; 166 | break; 167 | case InputKey.Up: 168 | res = Direction.North; 169 | break; 170 | } 171 | return res; 172 | } 173 | 174 | function getDirection(event: KeyboardEvent): Direction { 175 | const inputKey = getInputKey(event.keyCode); 176 | const newDirection = inputToDirection(inputKey); 177 | return newDirection; 178 | } 179 | 180 | export function directionReducer(state: GameState, event: KeyboardEvent): GameState { 181 | let result = state; 182 | const newDirection = getDirection(event); 183 | const lastDirection = state.directions[state.directions.length - 1]; 184 | if (newDirection !== Direction.None && newDirection !== lastDirection) { 185 | result = { 186 | ...state, 187 | directions: [...state.directions, newDirection], 188 | shouldRender: false, 189 | }; 190 | } 191 | return result; 192 | } 193 | 194 | export function renderConsole(state: GameState) { 195 | if (state.shouldRender && !state.game.gameOver) { 196 | const map = state.game.map; 197 | const strGrid = map.grid 198 | .map((row) => 199 | row 200 | .map((item) => 201 | item.isSnakeHead ? '@' : item.isSnake ? 'x' : item.isFood ? '*' : '.', 202 | ) 203 | .join(' '), 204 | ) 205 | .join('\n'); 206 | console.log(strGrid + '\n'); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/snake.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const models_1 = require("./models"); 4 | const input_1 = require("./input"); 5 | const map_1 = require("./map"); 6 | function defaultGameState() { 7 | return { 8 | game: defaultGame(), 9 | directions: [models_1.Direction.East], 10 | shouldRender: true, 11 | }; 12 | } 13 | exports.defaultGameState = defaultGameState; 14 | function defaultGame() { 15 | const food = defaultFood(); 16 | const snake = defaultSnake(); 17 | const map = map_1.updateMap(map_1.defaultMap(), snake, food); 18 | const gameOver = false; 19 | return { snake, map, food, gameOver }; 20 | } 21 | function defaultSnake() { 22 | const parts = [{ i: 0, j: 0 }, { i: 0, j: 1 }, { i: 0, j: 2 }]; 23 | return { 24 | direction: models_1.Direction.East, 25 | head: parts[parts.length - 1], 26 | length: parts.length, 27 | parts, 28 | foodEaten: false, 29 | }; 30 | } 31 | function defaultFood() { 32 | return { i: 0, j: 10 }; 33 | } 34 | function validNextDirection(curr, next) { 35 | let result = false; 36 | if (next !== models_1.Direction.None) { 37 | switch (curr) { 38 | case models_1.Direction.North: 39 | result = next !== models_1.Direction.South; 40 | break; 41 | case models_1.Direction.East: 42 | result = next !== models_1.Direction.West; 43 | break; 44 | case models_1.Direction.South: 45 | result = next !== models_1.Direction.North; 46 | break; 47 | case models_1.Direction.West: 48 | result = next !== models_1.Direction.East; 49 | break; 50 | case models_1.Direction.None: 51 | result = false; 52 | break; 53 | } 54 | } 55 | return result; 56 | } 57 | function getNewHead(snake) { 58 | const head = snake.head; 59 | let newHead = head; 60 | switch (snake.direction) { 61 | case models_1.Direction.North: 62 | newHead = { i: head.i - 1, j: head.j }; 63 | break; 64 | case models_1.Direction.East: 65 | newHead = { i: head.i, j: head.j + 1 }; 66 | break; 67 | case models_1.Direction.South: 68 | newHead = { i: head.i + 1, j: head.j }; 69 | break; 70 | case models_1.Direction.West: 71 | newHead = { i: head.i, j: head.j - 1 }; 72 | break; 73 | } 74 | return newHead; 75 | } 76 | function moveToDirection(snake, direction) { 77 | if (validNextDirection(snake.direction, direction)) { 78 | snake = Object.assign(Object.assign({}, snake), { direction }); 79 | } 80 | const newHead = getNewHead(snake); 81 | snake.parts = [...snake.parts, newHead]; 82 | return Object.assign(Object.assign({}, snake), { head: newHead, parts: snake.parts, length: snake.parts.length }); 83 | } 84 | function snakeFoodEaten(snake, food) { 85 | const foodEaten = snake.head.i === food.i && snake.head.j === food.j; 86 | let parts = snake.parts; 87 | let [tail, ...rest] = snake.parts; 88 | if (!foodEaten) { 89 | parts = rest; 90 | } 91 | return Object.assign(Object.assign({}, snake), { foodEaten, 92 | parts, length: parts.length }); 93 | } 94 | function isGameOver(game) { 95 | const { snake, snake: { head } } = game; 96 | return !map_1.isInBorders(game.map, head.i, head.j) || 97 | snake.parts.some(part => part !== head && part.i === head.i && part.j === head.j); 98 | } 99 | function tick(game, direction) { 100 | game = Object.assign(Object.assign({}, game), { snake: moveToDirection(game.snake, direction) }); 101 | game = Object.assign(Object.assign({}, game), { snake: snakeFoodEaten(game.snake, game.food) }); 102 | game = Object.assign(Object.assign({}, game), { food: map_1.randomFood(game, game.snake.foodEaten) }); 103 | game = Object.assign(Object.assign({}, game), { gameOver: isGameOver(game) }); 104 | if (!game.gameOver) { 105 | game = Object.assign(Object.assign({}, game), { map: map_1.updateMap(game.map, game.snake, game.food) }); 106 | } 107 | return game; 108 | } 109 | function tickReducer(state) { 110 | if (state.game.gameOver) { 111 | return state; 112 | } 113 | const [curDirection, nextDirection, ...rest] = state.directions; 114 | let direction = curDirection; 115 | if (nextDirection !== undefined) { 116 | direction = nextDirection; 117 | } 118 | const directions = state.directions.length === 1 119 | ? state.directions 120 | : [nextDirection, ...rest]; 121 | const game = tick(state.game, direction); 122 | return Object.assign(Object.assign({}, state), { game, 123 | directions, shouldRender: true }); 124 | } 125 | exports.tickReducer = tickReducer; 126 | function inputToDirection(inputKey) { 127 | let res = models_1.Direction.None; 128 | switch (inputKey) { 129 | case input_1.InputKey.Left: 130 | res = models_1.Direction.West; 131 | break; 132 | case input_1.InputKey.Right: 133 | res = models_1.Direction.East; 134 | break; 135 | case input_1.InputKey.Down: 136 | res = models_1.Direction.South; 137 | break; 138 | case input_1.InputKey.Up: 139 | res = models_1.Direction.North; 140 | break; 141 | } 142 | return res; 143 | } 144 | function getDirection(event) { 145 | const inputKey = input_1.getInputKey(event.keyCode); 146 | const newDirection = inputToDirection(inputKey); 147 | return newDirection; 148 | } 149 | function directionReducer(state, event) { 150 | let result = state; 151 | const newDirection = getDirection(event); 152 | const lastDirection = state.directions[state.directions.length - 1]; 153 | if (newDirection !== models_1.Direction.None && newDirection !== lastDirection) { 154 | result = Object.assign(Object.assign({}, state), { directions: [...state.directions, newDirection], shouldRender: false }); 155 | } 156 | return result; 157 | } 158 | exports.directionReducer = directionReducer; 159 | function renderConsole(state) { 160 | if (state.shouldRender && !state.game.gameOver) { 161 | const map = state.game.map; 162 | const strGrid = map.grid 163 | .map((row) => row 164 | .map((item) => item.isSnakeHead ? '@' : item.isSnake ? 'x' : item.isFood ? '*' : '.') 165 | .join(' ')) 166 | .join('\n'); 167 | console.log(strGrid + '\n'); 168 | } 169 | } 170 | exports.renderConsole = renderConsole; 171 | -------------------------------------------------------------------------------- /docs/bundle.js: -------------------------------------------------------------------------------- 1 | var app=function(){"use strict";function e(){}function t(e){return e()}function n(){return Object.create(null)}function o(e){e.forEach(t)}function r(e){return"function"==typeof e}function i(e,t){return e!=e?t==t:e!==t}function a(t,...n){if(null==t)return e;const o=t.subscribe(...n);return o.unsubscribe?()=>o.unsubscribe():o}function s(e,t){e.appendChild(t)}function c(e,t,n){e.insertBefore(t,n||null)}function u(e){e.parentNode.removeChild(e)}function d(e,t){for(let n=0;ne.removeEventListener(t,n,o)}function h(e,t,n){null==n?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n)}function m(e,t){t=""+t,e.data!==t&&(e.data=t)}function b(e,t,n){e.classList[n?"add":"remove"](t)}let k;function y(e){k=e}function j(e){(function(){if(!k)throw new Error("Function called outside component initialization");return k})().$$.on_destroy.push(e)}const x=[],v=[],$=[],_=[],O=Promise.resolve();let S=!1;function D(e){$.push(e)}let M=!1;const w=new Set;function E(){if(!M){M=!0;do{for(let e=0;e{const o=n.length?n[0]:t;return h.ctx&&d(h.ctx[e],h.ctx[e]=o)&&(h.bound[e]&&h.bound[e](o),m&&N(i,e)),t}):[],h.update(),m=!0,o(h.before_update),h.fragment=!!c&&c(h.ctx),a.target){if(a.hydrate){const e=function(e){return Array.from(e.childNodes)}(a.target);h.fragment&&h.fragment.l(e),e.forEach(u)}else h.fragment&&h.fragment.c();a.intro&&((b=i.$$.fragment)&&b.i&&(I.delete(b),b.i(j))),function(e,n,i){const{fragment:a,on_mount:s,on_destroy:c,after_update:u}=e.$$;a&&a.m(n,i),D(()=>{const n=s.map(t).filter(r);c?c.push(...n):o(n),e.$$.on_mount=[]}),u.forEach(D)}(i,a.target,a.anchor),E()}var b,j;y(p)}let A;function T(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function R(e,t){return e(t={exports:{}},t.exports),t.exports}"function"==typeof HTMLElement&&(A=class extends HTMLElement{constructor(){super(),this.attachShadow({mode:"open"})}connectedCallback(){for(const e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(e,t,n){this[e]=n}$destroy(){!function(e,t){const n=e.$$;null!==n.fragment&&(o(n.on_destroy),n.fragment&&n.fragment.d(t),n.on_destroy=n.fragment=null,n.ctx=[])}(this,1),this.$destroy=e}$on(e,t){const n=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return n.push(t),()=>{const e=n.indexOf(t);-1!==e&&n.splice(e,1)}}$set(){}});var C=R((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});var n;!function(e){e[e.None=-1]="None",e[e.Up=0]="Up",e[e.Right=1]="Right",e[e.Down=2]="Down",e[e.Left=3]="Left",e[e.Space=4]="Space"}(n=t.InputKey||(t.InputKey={})),t.getInputKey=function(e){let t=n.None;return!function(e){return 87===e||38===e}(e)?!function(e){return 68===e||39===e}(e)?!function(e){return 83===e||40===e}(e)?!function(e){return 65===e||37===e}(e)?function(e){return 32===e}(e)&&(t=n.Space):t=n.Left:t=n.Down:t=n.Right:t=n.Up,t}}));T(C);C.InputKey,C.getInputKey;var F=R((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),function(e){e[e.None=-1]="None",e[e.North=0]="North",e[e.East=1]="East",e[e.South=2]="South",e[e.West=3]="West"}(t.Direction||(t.Direction={}))}));T(F);F.Direction;var G=R((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.MAP_WIDTH=20,t.MAP_HEIGHT=20;const n={isFood:!0,isSnake:!1,isSnakeHead:!1},o={isFood:!1,isSnake:!0,isSnakeHead:!1},r={isFood:!1,isSnake:!0,isSnakeHead:!0};function i(e,t,n){const o=e.grid[t][n];return!o.isFood&&!o.isSnake&&!o.isSnakeHead}function a(){return function(e){const n=[];for(let o=0;o({isFood:!1,isSnake:!1,isSnakeHead:!1}))}t.defaultMap=function(){return{grid:a()}},t.updateMap=function(e,t,i){const s=a();return s[i.i][i.j]=n,t.parts.forEach(e=>{s[e.i][e.j]=o}),s[t.head.i][t.head.j]=r,Object.assign(Object.assign({},e),{grid:s})},t.isInBorders=function(e,n,o){return n>=0&&n=0&&oe!==n&&e.i===n.i&&e.j===n.j)}function a(e){return function(e){let t=F.Direction.None;switch(e){case C.InputKey.Left:t=F.Direction.West;break;case C.InputKey.Right:t=F.Direction.East;break;case C.InputKey.Down:t=F.Direction.South;break;case C.InputKey.Up:t=F.Direction.North}return t}(C.getInputKey(e.keyCode))}Object.defineProperty(t,"__esModule",{value:!0}),t.defaultGameState=function(){return{game:n(),directions:[F.Direction.East],shouldRender:!0}},t.tickReducer=function(e){if(e.game.gameOver)return e;const[t,n,...a]=e.directions;let s=t;void 0!==n&&(s=n);const c=1===e.directions.length?e.directions:[n,...a],u=function(e,t){return e=Object.assign(Object.assign({},e),{snake:o(e.snake,t)}),e=Object.assign(Object.assign({},e),{snake:r(e.snake,e.food)}),e=Object.assign(Object.assign({},e),{food:G.randomFood(e,e.snake.foodEaten)}),(e=Object.assign(Object.assign({},e),{gameOver:i(e)})).gameOver||(e=Object.assign(Object.assign({},e),{map:G.updateMap(e.map,e.snake,e.food)})),e}(e.game,s);return Object.assign(Object.assign({},e),{game:u,directions:c,shouldRender:!0})},t.directionReducer=function(e,t){let n=e;const o=a(t),r=e.directions[e.directions.length-1];return o!==F.Direction.None&&o!==r&&(n=Object.assign(Object.assign({},e),{directions:[...e.directions,o],shouldRender:!1})),n},t.renderConsole=function(e){if(e.shouldRender&&!e.game.gameOver){const t=e.game.map.grid.map(e=>e.map(e=>e.isSnakeHead?"@":e.isSnake?"x":e.isFood?"*":".").join(" ")).join("\n");console.log(t+"\n")}}}));T(W);W.defaultGameState;var K=W.tickReducer,L=W.directionReducer;W.renderConsole;const B=[];function U(e,t){return{subscribe:z(e,t).subscribe}}function z(t,n=e){let o;const r=[];function i(e){if(i=e,((n=t)!=n?i==i:n!==i||n&&"object"==typeof n||"function"==typeof n)&&(t=e,o)){const e=!B.length;for(let e=0;e{const e=r.indexOf(c);-1!==e&&r.splice(e,1),0===r.length&&(o(),o=null)}}}}var q,J=(q=Object.freeze({__proto__:null,derived:function(t,n,i){const s=!Array.isArray(t),c=s?[t]:t,u=n.length<2;return U(i,t=>{let i=!1;const d=[];let l=0,f=e;const p=()=>{if(l)return;f();const o=n(s?d[0]:d,t);u?t(o):f=r(o)?o:e},g=c.map((e,t)=>a(e,e=>{d[t]=e,l&=~(1<{l|=1<t=e)(),t}}))&&q.default||q,Q=R((function(e,t){Object.defineProperty(t,"__esModule",{value:!0});const n=W.defaultGameState();t.Store=class{constructor(){this.state=J.writable(n)}select(){return this.state}reduce(e){this.state.update(e)}reset(){this.state.set(n)}}}));T(Q);var V=Q.Store;function X(e,t,n){const o=e.slice();return o[15]=t[n],o}function Y(e,t,n){const o=e.slice();return o[12]=t[n],o[14]=n,o}function Z(t){let n,o;return{c(){n=l("button"),n.textContent="Pause Game"},m(e,r,i){c(e,n,r),i&&o(),o=g(n,"click",t[6])},p:e,d(e){e&&u(n),o()}}}function ee(t){let n,o;return{c(){n=l("button"),n.textContent="Start Game"},m(e,r,i){c(e,n,r),i&&o(),o=g(n,"click",t[5])},p:e,d(e){e&&u(n),o()}}}function te(e){let t;return{c(){var n,o,r,i;t=l("div"),h(t,"class","tile"),n=t,o="grid-row",r=e[14]+1,n.style.setProperty(o,r,i?"important":""),b(t,"food",e[15].isFood),b(t,"snake",e[15].isSnake),b(t,"head",e[15].isSnakeHead)},m(e,n){c(e,t,n)},p(e,n){2&n&&b(t,"food",e[15].isFood),2&n&&b(t,"snake",e[15].isSnake),2&n&&b(t,"head",e[15].isSnakeHead)},d(e){e&&u(t)}}}function ne(e){let t,n=e[12],o=[];for(let t=0;t{n(8,r=e)})),e.$$.update=()=>{256&e.$$.dirty&&n(1,c=r.game.map.grid),256&e.$$.dirty&&n(2,u=r.game.snake.length-3),256&e.$$.dirty&&n(3,d=r.game.gameOver)},[a,c,u,d,function(e){a&&o.reduce(t=>L(t,e))},function(){n(0,a=!0),i=setInterval(()=>{o.reduce(K)},150)},s,function(){s(),o.reset()}]}class ae extends A{constructor(e){super(),this.shadowRoot.innerHTML="",P(this,{target:this.shadowRoot},ie,re,i,{}),e&&e.target&&c(e.target,this,e.anchor)}}return customElements.define("svelte-snake",ae),ae}(); 2 | //# sourceMappingURL=bundle.js.map 3 | --------------------------------------------------------------------------------