├── .gitignore ├── mfm-js ├── renderers │ └── pixi │ │ ├── element.png │ │ └── renderer.ts ├── tsconfig.json ├── dist │ ├── src │ │ ├── elements │ │ │ ├── core │ │ │ │ ├── Builder.d.ts │ │ │ │ ├── DReg.d.ts │ │ │ │ ├── Res.d.ts │ │ │ │ ├── ForkBomb.d.ts │ │ │ │ ├── AntiForkBomb.d.ts │ │ │ │ ├── Empty.d.ts │ │ │ │ ├── Water.d.ts │ │ │ │ ├── Goop.d.ts │ │ │ │ ├── Wall.d.ts │ │ │ │ ├── Sand.d.ts │ │ │ │ └── SwapLine.d.ts │ │ │ ├── agents │ │ │ │ ├── ofswamp │ │ │ │ │ ├── Swampling.d.ts │ │ │ │ │ ├── SwampWorker.d.ts │ │ │ │ │ └── Swamp.d.ts │ │ │ │ ├── Bein.d.ts │ │ │ │ ├── Sentry.d.ts │ │ │ │ ├── DirectorWall.d.ts │ │ │ │ ├── MeshNet.d.ts │ │ │ │ ├── DirectionalDirector.d.ts │ │ │ │ ├── LivingWall.d.ts │ │ │ │ ├── Looper.d.ts │ │ │ │ ├── Signal.d.ts │ │ │ │ ├── SwapWorm.d.ts │ │ │ │ ├── HardCell3.d.ts │ │ │ │ ├── Wanderer.d.ts │ │ │ │ └── Director.d.ts │ │ │ └── components │ │ │ │ └── WormTrap.d.ts │ │ ├── mfm │ │ │ ├── ElementRegistry.d.ts │ │ │ ├── Capability.d.ts │ │ │ ├── Site.d.ts │ │ │ ├── TileCoordinate.d.ts │ │ │ ├── Tile.d.ts │ │ │ ├── Element.d.ts │ │ │ ├── VirtualEventWindow.d.ts │ │ │ ├── EventWindow.d.ts │ │ │ └── Wayfinder.d.ts │ │ ├── capabilities │ │ │ ├── Decay.d.ts │ │ │ ├── Build.d.ts │ │ │ ├── Swap.d.ts │ │ │ ├── Perception.d.ts │ │ │ ├── Repel.d.ts │ │ │ └── Wayfinding.d.ts │ │ └── main.d.ts │ └── renderers │ │ └── pixi │ │ └── renderer.d.ts ├── vite.config.js ├── src │ ├── elements │ │ ├── agents │ │ │ ├── Bein.ts │ │ │ ├── Sentry.ts │ │ │ ├── ofswamp │ │ │ │ ├── Swampling.ts │ │ │ │ ├── SwampWorker.ts │ │ │ │ └── Swamp.ts │ │ │ ├── LivingWall.ts │ │ │ ├── DirectorWall.ts │ │ │ ├── DirectionalDirector.ts │ │ │ ├── Signal.ts │ │ │ ├── MeshNet.ts │ │ │ ├── Looper.ts │ │ │ ├── Director.ts │ │ │ ├── SwapWorm.ts │ │ │ └── HardCell3.ts │ │ ├── core │ │ │ ├── Res.ts │ │ │ ├── Empty.ts │ │ │ ├── ForkBomb.ts │ │ │ ├── AntiForkBomb.ts │ │ │ ├── DReg.ts │ │ │ ├── Builder.ts │ │ │ ├── Water.ts │ │ │ ├── Wall.ts │ │ │ ├── Sand.ts │ │ │ ├── Goop.ts │ │ │ └── SwapLine.ts │ │ └── components │ │ │ └── WormTrap.ts │ ├── mfm │ │ ├── Capability.ts │ │ ├── ElementRegistry.ts │ │ ├── TileCoordinate.ts │ │ ├── Site.ts │ │ ├── Element.ts │ │ └── Tile.ts │ ├── capabilities │ │ ├── Decay.ts │ │ ├── Swap.ts │ │ ├── Build.ts │ │ ├── Perception.ts │ │ ├── Wayfinding.ts │ │ └── Repel.ts │ └── main.ts └── package.json ├── mfmrocks ├── src │ ├── env.d.ts │ ├── scripts │ │ └── renderers │ │ │ └── pixi │ │ │ └── element.png │ ├── pages │ │ ├── lemmings │ │ │ ├── _game │ │ │ │ ├── renderer │ │ │ │ │ ├── element.png │ │ │ │ │ ├── sprites │ │ │ │ │ │ ├── CIRCLE.png │ │ │ │ │ │ ├── DIRT.png │ │ │ │ │ │ ├── LEMM.png │ │ │ │ │ │ ├── POWER.png │ │ │ │ │ │ ├── ROCK.png │ │ │ │ │ │ ├── SOLID.png │ │ │ │ │ │ ├── lemmings.png │ │ │ │ │ │ ├── LEMM_HEAD.png │ │ │ │ │ │ ├── lemm-test.png │ │ │ │ │ │ ├── lemm-test.json │ │ │ │ │ │ └── lemmings.json │ │ │ │ │ └── sprites.ts │ │ │ │ ├── elements │ │ │ │ │ ├── LemmingEmitter.ts │ │ │ │ │ ├── Dirt.ts │ │ │ │ │ ├── Power.ts │ │ │ │ │ ├── Exit.ts │ │ │ │ │ └── Lemming.ts │ │ │ │ ├── useGameState.js │ │ │ │ └── LemmingsGame.vue │ │ │ └── index.astro │ │ └── index.astro │ ├── styles │ │ └── main.scss │ └── components │ │ └── mfms.vue ├── public │ ├── v1 │ │ ├── gameFiles │ │ │ ├── ding.wav │ │ │ ├── qblip.wav │ │ │ ├── tick.wav │ │ │ ├── Dreaming.ogg │ │ │ ├── levelend.mp3 │ │ │ ├── turnblip.wav │ │ │ └── VoicesFromHeaven.ogg │ │ ├── resources │ │ │ ├── element.png │ │ │ ├── mfmrocks-logo.png │ │ │ └── art │ │ │ │ ├── mfm-mason-web.jpg │ │ │ │ ├── mfm-compute-web.jpg │ │ │ │ ├── mfm-compute2-web.jpg │ │ │ │ ├── mfm-dregres-web.jpg │ │ │ │ ├── mfm-forkbomb-web.jpg │ │ │ │ └── mfm-carpeeventwindow-web.jpg │ │ ├── eventwindow.html │ │ ├── webpack.config.js.map │ │ ├── webpack.config.js │ │ ├── TileConnector.js │ │ └── p2p.html │ ├── images │ │ ├── mfm-lemmings.png │ │ └── mfmrocks-logo.png │ └── favicon.svg ├── tsconfig.json ├── .vscode │ ├── extensions.json │ └── launch.json ├── .gitignore ├── astro.config.mjs ├── package.json └── README.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | .vercel 5 | env.d.ts -------------------------------------------------------------------------------- /mfm-js/renderers/pixi/element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfm-js/renderers/pixi/element.png -------------------------------------------------------------------------------- /mfmrocks/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/ding.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/ding.wav -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/qblip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/qblip.wav -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/tick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/tick.wav -------------------------------------------------------------------------------- /mfmrocks/public/images/mfm-lemmings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/images/mfm-lemmings.png -------------------------------------------------------------------------------- /mfmrocks/public/images/mfmrocks-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/images/mfmrocks-logo.png -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/Dreaming.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/Dreaming.ogg -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/levelend.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/levelend.mp3 -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/turnblip.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/turnblip.wav -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/element.png -------------------------------------------------------------------------------- /mfmrocks/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "jsx": "preserve" 5 | } 6 | } -------------------------------------------------------------------------------- /mfmrocks/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["astro-build.astro-vscode"], 3 | "unwantedRecommendations": [] 4 | } 5 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/mfmrocks-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/mfmrocks-logo.png -------------------------------------------------------------------------------- /mfmrocks/src/scripts/renderers/pixi/element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/scripts/renderers/pixi/element.png -------------------------------------------------------------------------------- /mfmrocks/public/v1/gameFiles/VoicesFromHeaven.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/gameFiles/VoicesFromHeaven.ogg -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-mason-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-mason-web.jpg -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-compute-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-compute-web.jpg -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-compute2-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-compute2-web.jpg -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-dregres-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-dregres-web.jpg -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-forkbomb-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-forkbomb-web.jpg -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/element.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/element.png -------------------------------------------------------------------------------- /mfmrocks/public/v1/resources/art/mfm-carpeeventwindow-web.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/public/v1/resources/art/mfm-carpeeventwindow-web.jpg -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/CIRCLE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/CIRCLE.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/DIRT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/DIRT.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/LEMM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/LEMM.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/POWER.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/POWER.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/ROCK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/ROCK.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/SOLID.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/SOLID.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemmings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemmings.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/LEMM_HEAD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/LEMM_HEAD.png -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemm-test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/walpolea/MFM-JS/HEAD/mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemm-test.png -------------------------------------------------------------------------------- /mfm-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib":["ES2019"], 4 | "emitDeclarationOnly": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "moduleResolution": "nodenext" 8 | } 9 | } -------------------------------------------------------------------------------- /mfmrocks/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "command": "./node_modules/.bin/astro dev", 6 | "name": "Development server", 7 | "request": "launch", 8 | "type": "node-terminal" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/eventwindow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MFM.ROCKS - Event Window 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Builder.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Builder extends Element { 5 | constructor(type: IElementType, state?: any); 6 | init(): void; 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfmrocks/.gitignore: -------------------------------------------------------------------------------- 1 | # build output 2 | dist/ 3 | # generated types 4 | .astro/ 5 | 6 | # dependencies 7 | node_modules/ 8 | 9 | # logs 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | 16 | # environment variables 17 | .env 18 | .env.production 19 | 20 | # macOS-specific files 21 | .DS_Store 22 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/ElementRegistry.d.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from './Element'; 2 | 3 | export declare class ElementRegistry { 4 | static TYPES: Map; 5 | static GROUPS: Map; 6 | static registerType(type: IElementType): void; 7 | static getType(type: string | IElementType): IElementType; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/Capability.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICapability { 2 | name: string; 3 | do: Function; 4 | } 5 | export declare class CapabilityRegistry { 6 | static CAPABILITIES: Map; 7 | static registerCapability(cap: ICapability): void; 8 | static getCapability(cap: string | ICapability): ICapability; 9 | } 10 | -------------------------------------------------------------------------------- /mfmrocks/astro.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'astro/config'; 2 | import vue from "@astrojs/vue"; 3 | 4 | // https://astro.build/config 5 | export default defineConfig({ 6 | redirects: { 7 | // '/v1': '/v1/index.html', 8 | // '/v1/game': '/v1/game/index.html', 9 | // '/v1/game/maker': '/v1/game/maker.html' 10 | }, 11 | integrations: [vue()] 12 | }); -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Decay.d.ts: -------------------------------------------------------------------------------- 1 | import { Element } from '../mfm/Element'; 2 | import { EventWindow } from '../mfm/EventWindow'; 3 | 4 | export declare class Decay { 5 | static MAKE_DECAY(lifeSpan: number, deathChance?: number): (ew: EventWindow, self: Element) => void; 6 | static DECAY(ew: EventWindow, self: Element, lifeSpan: number, deathChance?: number): boolean; 7 | } 8 | -------------------------------------------------------------------------------- /mfm-js/vite.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { defineConfig } = require('vite'); 3 | import dts from 'vite-plugin-dts'; 4 | 5 | module.exports = defineConfig({ 6 | build: { 7 | lib: { 8 | entry: path.resolve(__dirname, 'src/main.ts'), 9 | name: 'MFM-JS', 10 | fileName: (format) => `mfm.${format}.js` 11 | }, 12 | }, 13 | plugins: [dts()], 14 | }); -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/DReg.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class DReg extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Res.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Res extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/ForkBomb.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class ForkBomb extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/AntiForkBomb.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class AntiForkBomb extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/ofswamp/Swampling.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../../mfm/Element'; 2 | import { EventWindow } from '../../../mfm/EventWindow'; 3 | 4 | export declare class Swampling extends Element { 5 | static CREATE: (_typeDefinition?: import('../../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Bein.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Bein extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | init(): void; 8 | behave(ew: EventWindow): void; 9 | } 10 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/ofswamp/SwampWorker.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../../mfm/Element'; 2 | import { EventWindow } from '../../../mfm/EventWindow'; 3 | 4 | export declare class SwampWorker extends Element { 5 | static CREATE: (_typeDefinition?: import('../../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | behave(ew: EventWindow): void; 8 | } 9 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Empty.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Empty extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | init(): void; 8 | behave(ew: EventWindow): void; 9 | } 10 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Sentry.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Sentry extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | init(): void; 8 | behave(ew: EventWindow): void; 9 | } 10 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/DirectorWall.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class DirectorWall extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | constructor(type: IElementType, state?: any); 7 | init(): void; 8 | behave(ew: EventWindow): void; 9 | } 10 | -------------------------------------------------------------------------------- /mfmrocks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "type": "module", 4 | "version": "0.0.1", 5 | "scripts": { 6 | "dev": "astro dev", 7 | "start": "astro dev", 8 | "build": "astro build", 9 | "preview": "astro preview", 10 | "astro": "astro" 11 | }, 12 | "dependencies": { 13 | "@astrojs/vue": "^4.0.8", 14 | "astro": "^4.4.15", 15 | "mfm-js": "file:../mfm-js", 16 | "pixi.js": "^8.0.4", 17 | "sass": "^1.71.1", 18 | "vue": "^3.4.21" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemm-test.json: -------------------------------------------------------------------------------- 1 | {"frames":{"DIRT.png":{"frame":{"x":0,"y":0,"w":32,"h":32},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":32,"h":32},"sourceSize":{"w":32,"h":32}},"ROCK.png":{"frame":{"x":0,"y":32,"w":32,"h":32},"rotated":false,"trimmed":false,"spriteSourceSize":{"x":0,"y":0,"w":32,"h":32},"sourceSize":{"w":32,"h":32}}},"meta":{"app":"https://github.com/piskelapp/piskel/","version":"1.0","image":"./renderer/sprites/lemm-test.png","format":"RGBA8888","size":{"w":32,"h":64}}} -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/Site.d.ts: -------------------------------------------------------------------------------- 1 | import { Element } from './Element'; 2 | import { EventWindow } from './EventWindow'; 3 | import { TileCoordinate } from './TileCoordinate'; 4 | 5 | export declare class Site { 6 | location: TileCoordinate; 7 | id: string; 8 | atom: Element; 9 | baseAtom: Element; 10 | ew: EventWindow; 11 | constructor(_loc: TileCoordinate); 12 | create(): void; 13 | swapAtoms(targetSite: Site): boolean; 14 | mutate(atom: Element): void; 15 | mutateBase(atom: Element): void; 16 | } 17 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/Bein.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | 4 | export class Bein extends Element { 5 | static CREATE = Bein.CREATOR({ name: "BEIN", symbol: "BNG", class: Bein, color: 0xbe146f, groups: ["MFM"] }); 6 | 7 | constructor(type: IElementType, state: any = {}) { 8 | super(type, state); 9 | 10 | this.init(); 11 | } 12 | 13 | init() {} 14 | 15 | behave(ew: EventWindow) { 16 | super.behave(ew); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Res.ts: -------------------------------------------------------------------------------- 1 | import { Swap } from "../../capabilities/Swap"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow } from "../../mfm/EventWindow"; 4 | 5 | export class Res extends Element { 6 | static CREATE = Res.CREATOR({ name: "RES", class: Res, color: 0x0e5100, groups: ["MFM"] }); 7 | 8 | constructor(type: IElementType, state: any = {}) { 9 | super(type, state); 10 | } 11 | 12 | behave(ew: EventWindow) { 13 | super.behave(ew); 14 | 15 | Swap.PATROL(ew); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/TileCoordinate.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICoordinate { 2 | x: number; 3 | y: number; 4 | } 5 | export interface ITileCoordinate { 6 | id: string; 7 | coordinate: ICoordinate; 8 | } 9 | export declare class TileCoordinate { 10 | static CoordinateToId(c: ICoordinate): string; 11 | static IdToCoordinate(id: string): ICoordinate; 12 | static fromCoordinate(c: ICoordinate): TileCoordinate; 13 | static fromId(id: string): TileCoordinate; 14 | id: string; 15 | coordinate: ICoordinate; 16 | constructor(id: string); 17 | } 18 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Empty.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | 4 | export class Empty extends Element { 5 | static CREATE = Empty.CREATOR({ name: "EMPTY", symbol: "EMT", class: Empty, color: 0x2a2a2a, groups: ["MFM"] }); 6 | 7 | constructor(type: IElementType, state: any = {}) { 8 | super(type, state); 9 | 10 | this.init(); 11 | } 12 | 13 | init() { 14 | this.classifyAs("EMPTY"); 15 | } 16 | 17 | behave(ew: EventWindow) { 18 | super.behave(ew); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/Capability.ts: -------------------------------------------------------------------------------- 1 | export interface ICapability { 2 | name: string; 3 | do: Function; 4 | } 5 | 6 | export class CapabilityRegistry { 7 | static CAPABILITIES: Map = new Map(); 8 | 9 | static registerCapability(cap: ICapability) { 10 | this.CAPABILITIES.set(cap.name.toUpperCase(), cap); 11 | } 12 | 13 | static getCapability(cap: string | ICapability): ICapability { 14 | return typeof cap === "string" ? this.CAPABILITIES.get(cap.toUpperCase()) : this.CAPABILITIES.get(cap.name.toUpperCase()); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/MeshNet.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class MeshNet extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static ATTRACT: (ew: EventWindow, self: Element) => boolean; 7 | static AVOID: (ew: EventWindow, self: Element) => boolean; 8 | constructor(type: IElementType, state?: any); 9 | init(): void; 10 | behave(ew: EventWindow): void; 11 | } 12 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/DirectionalDirector.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class DirectionalDirector extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static DDIR_ONCE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | constructor(type: IElementType, state?: any); 8 | init(): void; 9 | behave(ew: EventWindow): void; 10 | } 11 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/ofswamp/Swamp.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../../mfm/Element'; 2 | import { EventWindow } from '../../../mfm/EventWindow'; 3 | 4 | export declare class Swamp extends Element { 5 | static CREATE: (_typeDefinition?: import('../../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static BOG: (_typeDefinition?: import('../../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | constructor(type: IElementType, state?: any); 8 | init(): void; 9 | behave(ew: EventWindow): void; 10 | grow(ew: EventWindow): void; 11 | } 12 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/components/WormTrap.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class WormTrap extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static BUILDER: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | constructor(type: IElementType, state?: any); 8 | init(): void; 9 | behave(ew: EventWindow): void; 10 | captureWorm(ew: EventWindow): Boolean; 11 | } 12 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/ForkBomb.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | 4 | export class ForkBomb extends Element { 5 | static CREATE = ForkBomb.CREATOR({ name: "FORKBOMB", symbol: "FKB", class: ForkBomb, color: 0xdd0000, groups: ["MFM"] }); 6 | 7 | constructor(type: IElementType, state: any = {}) { 8 | super(type, state); 9 | } 10 | 11 | behave(ew: EventWindow) { 12 | super.behave(ew); 13 | ew.replace(EventWindow.RANDOM(EventWindow.ADJACENT4WAY), ForkBomb.CREATE({ color: (this.rd("color") + 512) % 0xffffff })); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Build.d.ts: -------------------------------------------------------------------------------- 1 | import { EventWindow, EWIndex } from '../mfm/EventWindow'; 2 | 3 | export declare class Build { 4 | static GRID: (ew: EventWindow, multiplier: Function, creator: Function) => boolean; 5 | static SMALL_GRID: (ew: EventWindow, multiplier: Function, creator: Function) => boolean; 6 | static H_LINE: (ew: EventWindow, multiplier: Function, creator: Function) => boolean; 7 | static V_LINE: (ew: EventWindow, multiplier: Function, creator: Function) => boolean; 8 | static MAKE_REPEATER(destinations: EWIndex[], steps?: number): (ew: EventWindow, multiplier: Function, creator: Function) => boolean; 9 | } 10 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/LivingWall.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class LivingWall extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static MOVABLE_LIVING_WALL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | constructor(type: IElementType, state?: any); 8 | init(): void; 9 | behave(ew: EventWindow): void; 10 | populateRegenMap(ew: EventWindow): void; 11 | regen(ew: EventWindow): void; 12 | } 13 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/webpack.config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"webpack.config.js","sourceRoot":"","sources":["../webpack.config.js"],"names":[],"mappings":"AAAA,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAE7B,MAAM,CAAC,OAAO,GAAG;IACf,KAAK,EAAE,gBAAgB;IACvB,OAAO,EAAE,mBAAmB;IAC5B,MAAM,EAAE;QACN,KAAK,EAAE;YACL;gBACE,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,iCAAiC;gBAC1C,GAAG,EAAE;oBACH,MAAM,EAAE,cAAc;oBACtB,OAAO,EAAE;wBACP,OAAO,EAAE,CAAC,mBAAmB,CAAC;qBAC/B;iBACF;aACF;YACD;gBACE,IAAI,EAAE,SAAS;gBACf,GAAG,EAAE,WAAW;gBAChB,OAAO,EAAE,cAAc;aACxB;SACF;KACF;IACD,OAAO,EAAE;QACP,UAAU,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC;KACnC;IACD,MAAM,EAAE;QACN,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC;KACtC;CACF,CAAC"} -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Swap.d.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from '../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../mfm/EventWindow'; 3 | 4 | export declare class Swap { 5 | static DOWN: (ew: EventWindow, chance?: number) => boolean; 6 | static SIDE: (ew: EventWindow, chance?: number) => boolean; 7 | static SLIP: (ew: EventWindow, chance?: number) => boolean; 8 | static SINK: (ew: EventWindow, chance?: number) => boolean; 9 | static FLOAT: (ew: EventWindow, chance?: number) => boolean; 10 | static PATROL: (ew: EventWindow, chance?: number) => boolean; 11 | static PATROL_8: (ew: EventWindow, chance?: number) => boolean; 12 | static CREATE(direction: EWIndex[], type?: string | IElementType): (ew: EventWindow, chance?: number) => boolean; 13 | } 14 | -------------------------------------------------------------------------------- /mfmrocks/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | -------------------------------------------------------------------------------- /mfmrocks/src/styles/main.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | body { 6 | margin: 0; 7 | padding: 10px; 8 | background: #222; 9 | display: grid; 10 | justify-items: center; 11 | color: white; 12 | min-height: 100vh; 13 | font-family: sans-serif; 14 | } 15 | 16 | header { 17 | padding: 50px; 18 | } 19 | 20 | h3 { 21 | margin-block: 0px 5px; 22 | } 23 | 24 | ul { 25 | padding: 0; 26 | } 27 | 28 | 29 | 30 | nav ul { 31 | display: flex; 32 | flex-wrap: wrap; 33 | justify-content: center; 34 | list-style-type: none; 35 | margin: 0; 36 | padding: 0; 37 | gap: 1rem; 38 | } 39 | 40 | nav ul li { 41 | margin-inline: 20px; 42 | text-align: center; 43 | } 44 | 45 | nav ul li a, footer a { 46 | color: #e1325e; 47 | 48 | &:visited { 49 | color: lightsalmon; 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MFM-JS (v2) 2 | 3 | ## Movable Feast Machine simulator built in JavaScript 4 | 5 | --- 6 | 7 | ### The idea here is not to port MFM to JavaScript, but instead simulate it enough and provide an environment that is easy to quickly get hands-on with robust-first programming. 8 | 9 | --- 10 | 11 | Prerequisites 12 | 13 | - nodejs 18+ (_I use nvm to easily manage and upgrade node versions_) (https://nodejs.org) 14 | - an IDE like VS Code (https://code.visualstudio.com/) 15 | 16 | Install 17 | 18 | - git clone https://github.com/walpolea/MFM-JS.git 19 | - `cd MFM-JS/mfm-js` 20 | - `npm install` 21 | - `npm run build` 22 | - the library builds to `/mfm-js/dist` 23 | - also serve up `index.html` with `npm run dev` to watch chaanges as you build things. 24 | - note that the renderers are no longer bundled with the main library 25 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Decay.ts: -------------------------------------------------------------------------------- 1 | import { Element } from "../mfm/Element"; 2 | import { EventWindow } from "../mfm/EventWindow"; 3 | 4 | export class Decay { 5 | static MAKE_DECAY(lifeSpan: number, deathChance: number = 1) { 6 | return (ew: EventWindow, self: Element) => { 7 | const { age } = self.state; 8 | 9 | if (age > lifeSpan) { 10 | if (EventWindow.oneIn(deathChance)) { 11 | ew.destroy(); 12 | } 13 | } 14 | }; 15 | } 16 | 17 | static DECAY(ew: EventWindow, self: Element, lifeSpan: number, deathChance: number = 1): boolean { 18 | const { age } = self.state; 19 | 20 | 21 | if (age > lifeSpan) { 22 | if (EventWindow.oneIn(deathChance)) { 23 | ew.destroy(); 24 | return true; 25 | } 26 | } 27 | return false; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Perception.d.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from '../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../mfm/EventWindow'; 3 | import { Direction } from '../mfm/Wayfinder'; 4 | 5 | export type SignalType = "WARN" | "INFORM" | "BECKON"; 6 | export type Message = { 7 | senderId: string; 8 | senderType: IElementType | string; 9 | signalType: SignalType; 10 | message: string; 11 | signalDirection?: Direction; 12 | }; 13 | export declare class Perception { 14 | static SENSE(ew: EventWindow, type: IElementType | string, withinSet?: EWIndex[], minQuantity?: number, maxQuantity?: number): boolean; 15 | static SIGNAL(ew: EventWindow, signalType: SignalType, message: Message): void; 16 | static RECEIVE_SIGNAL(ew: EventWindow, self: Element, indexes?: EWIndex[]): Message | null; 17 | } 18 | -------------------------------------------------------------------------------- /mfm-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mfm-js", 3 | "version": "2.0.0", 4 | "description": "A movable feast machine simulator in JavaScript", 5 | "files": [ 6 | "dist" 7 | ], 8 | "main": "./dist/mfm.umd.js", 9 | "module": "./dist/mfm.es.js", 10 | "exports": { 11 | ".": { 12 | "import": "./dist/mfm.es.js", 13 | "require": "./dist/mfm.umd.js" 14 | } 15 | }, 16 | "scripts": { 17 | "dev": "vite", 18 | "build": "vite build", 19 | "preview": "vite preview" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "dependencies": { 25 | "pixi.js": "^8.0.0", 26 | "typescript": "^5.4.2" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^20.11.25", 30 | "pixi.js": "^8.0.0", 31 | "vite": "^5.2.7", 32 | "vite-plugin-dts": "^3.7.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/Tile.d.ts: -------------------------------------------------------------------------------- 1 | import { Site } from './Site'; 2 | import { ICoordinate } from './TileCoordinate'; 3 | 4 | export declare class Tile { 5 | width: number; 6 | height: number; 7 | sites: Map; 8 | sitesArray: Site[]; 9 | rands: Array; 10 | cur: number; 11 | isRunning: boolean; 12 | constructor(_width?: number, _height?: number); 13 | seedRandoms(): void; 14 | getSiteByCoordinate(c: ICoordinate): Site; 15 | getSiteById(id: string): Site; 16 | getRandomSite(): Site; 17 | getRandomSiteSeeded(): Site; 18 | getRandomSiteInRange(range: number[]): Site; 19 | getRandomSiteInRangeSeeded(range: number[]): Site; 20 | create(): void; 21 | add(atomizer: any, x?: number, y?: number): void; 22 | exportAtoms(): unknown[]; 23 | clear(t?: string): void; 24 | } 25 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Water.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Water extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static BUBBLY_WATER: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static WATER_GRID: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static WATER_LINE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static DO_BUBBLE: (ew: EventWindow) => boolean; 10 | constructor(type: IElementType, state?: any); 11 | init(): void; 12 | behave(ew: EventWindow): void; 13 | } 14 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/ElementRegistry.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from "./Element"; 2 | 3 | export class ElementRegistry { 4 | static TYPES: Map = new Map(); 5 | static GROUPS: Map = new Map(); 6 | 7 | static registerType(type: IElementType) { 8 | this.TYPES.set(type.name.toUpperCase(), type); 9 | 10 | const groups = type.groups ?? ["Misc"]; 11 | 12 | groups.forEach((g) => { 13 | if (!this.GROUPS.has(g)) { 14 | this.GROUPS.set(g, []); 15 | } 16 | 17 | const group = this.GROUPS.get(g); 18 | this.GROUPS.set(g, [...group, type]); 19 | }); 20 | } 21 | 22 | static getType(type: string | IElementType): IElementType { 23 | return typeof type === "string" ? this.TYPES.get(type.toUpperCase()) : this.TYPES.get(type.name.toUpperCase()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Repel.d.ts: -------------------------------------------------------------------------------- 1 | import { Element } from '../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../mfm/EventWindow'; 3 | 4 | export declare class Repel { 5 | static NAME: string; 6 | static CREATE(repelType: string, repelSites: EWIndex[], escapeType: string, escapeSites: EWIndex[]): (ew: EventWindow) => boolean; 7 | static FAR_NORTH(ew: EventWindow, repelType: string | string[], escapeType?: string | string[]): boolean; 8 | static MAKE_REPELLER(_repelTypes: string | string[], fromIndexes?: EWIndex[], toIndexes?: EWIndex[]): (ew: EventWindow, self: Element) => boolean; 9 | static MAKE_AVOIDER(_repelTypes: string | string[], fromIndexes?: EWIndex[], toIndexes?: EWIndex[]): (ew: EventWindow, self: Element) => boolean; 10 | static MAKE_ATTRACTOR(_attractTypes: string | string[], view?: EWIndex[]): (ew: EventWindow, self: Element) => boolean; 11 | } 12 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/TileCoordinate.ts: -------------------------------------------------------------------------------- 1 | export interface ICoordinate { 2 | x: number; 3 | y: number; 4 | } 5 | 6 | export interface ITileCoordinate { 7 | id: string; 8 | coordinate: ICoordinate; 9 | } 10 | 11 | export class TileCoordinate { 12 | static CoordinateToId(c: ICoordinate): string { 13 | return `${c.y}:${c.x}`; 14 | } 15 | 16 | static IdToCoordinate(id: string): ICoordinate { 17 | const [y, x] = id.split(":"); 18 | return { y: +y, x: +x }; 19 | } 20 | 21 | static fromCoordinate(c: ICoordinate): TileCoordinate { 22 | return new TileCoordinate(TileCoordinate.CoordinateToId(c)); 23 | } 24 | 25 | static fromId(id: string): TileCoordinate { 26 | return new TileCoordinate(id); 27 | } 28 | 29 | id: string; 30 | coordinate: ICoordinate; 31 | 32 | constructor(id: string) { 33 | this.id = id; 34 | this.coordinate = TileCoordinate.IdToCoordinate(this.id); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Looper.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Looper extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static WONKY: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static LOOP_WALL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static LOOP_DIRECTOR: (heading: any) => any; 9 | static REPEL_DIRECTIONAL: (ew: EventWindow, self: Element) => boolean; 10 | constructor(type: IElementType, state?: any); 11 | init(): void; 12 | makeBigTail(ew: EventWindow): void; 13 | behave(ew: EventWindow): void; 14 | behaveAsLooper(ew: EventWindow): void; 15 | behaveAsWonky(ew: EventWindow): void; 16 | } 17 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/elements/LemmingEmitter.ts: -------------------------------------------------------------------------------- 1 | import { Element } from "mfm-js"; 2 | import { EventWindow } from "mfm-js"; 3 | import { Lemming } from "./Lemming"; 4 | 5 | export class LemmingEmitter extends Element { 6 | static CREATE = LemmingEmitter.CREATOR({ name: "LEMIT", symbol: "LME", class: LemmingEmitter, color: 0x89f24e, groups: ["LEMMINGS"] }, { emitCount: 10}); 7 | 8 | constructor(type, state = {}) { 9 | super(type, state); 10 | 11 | this.init(); 12 | } 13 | 14 | init() { 15 | 16 | if(!this.state.emitCount) { 17 | this.state.emitCount = 100; 18 | } 19 | } 20 | 21 | behave(ew: EventWindow) { 22 | super.behave(ew); 23 | 24 | if(this.state.emitCount <= 0) { 25 | ew.destroy(); 26 | return; 27 | } 28 | 29 | if(this.state.emitCount > 0 && ew.is(3, "EMPTY")) { 30 | ew.mutate(3, Lemming.CREATE); 31 | this.state.emitCount--; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | module.exports = { 3 | mode: "development", 4 | entry: "./src/index.ts", 5 | devtool: "inline-source-map", 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.m?js$/, 10 | exclude: /(node_modules|bower_components)/, 11 | use: { 12 | loader: "babel-loader", 13 | options: { 14 | presets: ["@babel/preset-env"] 15 | } 16 | } 17 | }, 18 | { 19 | test: /\.tsx?$/, 20 | use: "ts-loader", 21 | exclude: /node_modules/ 22 | } 23 | ] 24 | }, 25 | resolve: { 26 | extensions: [".tsx", ".ts", ".js"] 27 | }, 28 | output: { 29 | filename: "mfm.js", 30 | path: path.resolve(__dirname, "dist") 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Swap.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from "../mfm/Element"; 2 | import { EventWindow, EWIndex } from "../mfm/EventWindow"; 3 | 4 | export class Swap { 5 | static DOWN = Swap.CREATE(EventWindow.S); 6 | static SIDE = Swap.CREATE([6, 8]); 7 | static SLIP = Swap.CREATE(EventWindow.EQUATOR); 8 | 9 | static SINK = Swap.CREATE([...EventWindow.S, 6, 8], "WATER"); 10 | static FLOAT = Swap.CREATE([...EventWindow.N, 5, 7], "WATER"); 11 | 12 | static PATROL = Swap.CREATE(EventWindow.ADJACENT4WAY); 13 | static PATROL_8 = Swap.CREATE(EventWindow.ADJACENT8WAY); 14 | 15 | static CREATE(direction: EWIndex[], type: string | IElementType = "EMPTY") { 16 | return (ew: EventWindow, chance: number = 1): boolean => { 17 | if (EventWindow.oneIn(chance) && ew.any(direction, type)) { 18 | const to: EWIndex = ew.filter(direction, type, true)?.[0]; 19 | return ew.swap(to); 20 | } 21 | 22 | return false; 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/Site.ts: -------------------------------------------------------------------------------- 1 | import { Empty } from "../elements/core/Empty"; 2 | import { Element } from "./Element"; 3 | import { EventWindow } from "./EventWindow"; 4 | import { TileCoordinate } from "./TileCoordinate"; 5 | 6 | export class Site { 7 | location: TileCoordinate; 8 | id: string; 9 | 10 | atom: Element; 11 | baseAtom: Element; 12 | //the tile needs to seed the EventWindow 13 | ew: EventWindow; 14 | 15 | constructor(_loc: TileCoordinate) { 16 | this.location = _loc; 17 | this.id = this.location.id; 18 | this.create(); 19 | } 20 | 21 | create() { 22 | this.atom = Empty.CREATE(); 23 | this.baseAtom = Empty.CREATE(); 24 | } 25 | 26 | swapAtoms(targetSite: Site): boolean { 27 | [this.atom, targetSite.atom] = [targetSite.atom, this.atom]; 28 | return true; 29 | } 30 | 31 | mutate(atom: Element) { 32 | this.atom = atom; 33 | } 34 | 35 | mutateBase(atom: Element) { 36 | this.baseAtom = atom; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Goop.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Goop extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static WALL_GOOP: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static LIFE_GOOP: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static HC3_GOOP: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static GOOP_GRID: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static ATTRACT: (ew: EventWindow, self: Element) => boolean; 11 | static AVOID: (ew: EventWindow, self: Element) => boolean; 12 | constructor(type: IElementType, state?: any); 13 | init(): void; 14 | behave(ew: EventWindow): void; 15 | } 16 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Build.ts: -------------------------------------------------------------------------------- 1 | import { EventWindow, EWIndex } from "../mfm/EventWindow"; 2 | 3 | export class Build { 4 | static GRID = Build.MAKE_REPEATER(EventWindow.DIAGONAL4WAY); 5 | static SMALL_GRID = Build.MAKE_REPEATER(EventWindow.DIAGONAL4WAY, 4); 6 | static H_LINE = Build.MAKE_REPEATER([1, 4]); 7 | static V_LINE = Build.MAKE_REPEATER([2, 3]); 8 | 9 | static MAKE_REPEATER(destinations: EWIndex[], steps?: number) { 10 | return (ew: EventWindow, multiplier: Function, creator: Function): boolean => { 11 | const empties = ew.filterByType(destinations, "EMPTY"); 12 | //PROPAGATE 13 | if (steps) { 14 | let step = ew.origin.atom.rd("buildStep") ?? 1; 15 | ew.origin.atom.wr("buildStep", step); 16 | 17 | if (step < steps) { 18 | ew.mutateMany(empties, multiplier, [{}, { buildStep: step + 1 }]); 19 | } 20 | } else { 21 | ew.mutateMany(empties, multiplier); 22 | } 23 | 24 | ew.mutate(0, creator); 25 | 26 | return true; 27 | }; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/AntiForkBomb.ts: -------------------------------------------------------------------------------- 1 | import { Decay } from "../../capabilities/Decay"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow } from "../../mfm/EventWindow"; 4 | 5 | export class AntiForkBomb extends Element { 6 | static CREATE = AntiForkBomb.CREATOR({ name: "ANTIFORKBOMB", symbol: "AFK", class: AntiForkBomb, color: 0x00aadd, groups: ["MFM"] }); 7 | 8 | constructor(type: IElementType, state: any = {}) { 9 | super(type, state); 10 | } 11 | 12 | behave(ew: EventWindow) { 13 | super.behave(ew); 14 | 15 | const forkbombs = ew.filterByType(EventWindow.ADJACENT8WAY, "FORKBOMB"); 16 | 17 | if (forkbombs.length) { 18 | ew.mutateMany(forkbombs, AntiForkBomb.CREATE, [{ color: (this.rd("color") + 2048) % 0xffffff }, {}]); 19 | } 20 | 21 | Decay.DECAY(ew, this, 40); 22 | 23 | const afks = ew.filterByType(EventWindow.ADJACENT8WAY, "ANTIFORKBOMB"); 24 | 25 | if (afks.length && EventWindow.oneIn(2)) { 26 | ew.mutateMany(afks, AntiForkBomb.CREATE, [{ color: (this.rd("color") + 1024) % 0xaaffff }, { age: this.state.age - 1 }]); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Wall.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Wall extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static MOVABLE_WALL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static DECAY_WALL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static DECAY_WALL_10: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static DECAY_WALL_25: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static DECAY_WALL_50: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static MOVABLE_WALL_GRID: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | constructor(type: IElementType, state?: any); 13 | init(): void; 14 | behave(ew: EventWindow): void; 15 | } 16 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/Sentry.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow } from "../../mfm/EventWindow"; 4 | import { Wayfinder } from "../../mfm/Wayfinder"; 5 | import { AntiForkBomb } from "../core/AntiForkBomb"; 6 | 7 | export class Sentry extends Element { 8 | //VARIANTS 9 | static CREATE = Sentry.CREATOR({ name: "SENTRY", class: Sentry, color: 0xa414ff, classifications: ["DIRECTIONAL", "DIRECTABLE"], groups: ["MFM"] }); 10 | 11 | constructor(type: IElementType, state: any = {}) { 12 | super(type, state); 13 | this.init(); 14 | } 15 | 16 | init() {} 17 | 18 | behave(ew: EventWindow) { 19 | super.behave(ew); 20 | 21 | if (!this.state.heading) { 22 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM()); 23 | } else { 24 | Wayfinding.MOVE_DIRECTIONALLY(ew, this); 25 | Wayfinding.SLIGHT_RANDOMLY(this); 26 | } 27 | 28 | const forkbombs = ew.filter(EventWindow.ALLADJACENT, "FORKBOMB", true); 29 | 30 | if (forkbombs.length) { 31 | ew.mutate(forkbombs[0], AntiForkBomb.CREATE); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/Sand.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Sand extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static RED_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static PINK_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static FLOATY_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static GREEN_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static BLUE_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static PURPLE_SAND: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | static SAND_GRID: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 13 | static BUBBLE: (ew: EventWindow) => boolean; 14 | constructor(type: IElementType, state?: any); 15 | init(): void; 16 | behave(ew: EventWindow): void; 17 | } 18 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Signal.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Signal extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static N: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static NE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static E: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static SE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static S: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static SW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | static W: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 13 | static NW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 14 | constructor(type: IElementType, state?: any); 15 | init(): void; 16 | behave(ew: EventWindow): void; 17 | } 18 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import '../../styles/main.scss'; 3 | 4 | import LemmingsGame from './_game/LemmingsGame.vue'; 5 | --- 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | MFM Lemmings 14 | 15 | 16 |
17 | 18 |
19 | MFM Lemmings 20 |
21 | 22 | 23 | 24 | 27 | 28 |
29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/SwapWorm.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../../mfm/EventWindow'; 3 | 4 | export declare class SwapWorm extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static SMOLSW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static BIGSW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | constructor(type: IElementType, state?: any); 9 | init(): void; 10 | behave(ew: EventWindow): boolean; 11 | establishType(ew: EventWindow): string; 12 | isHead(): boolean; 13 | isTail(): boolean; 14 | isMiddle(): boolean; 15 | isTemp(): boolean; 16 | confirmBehind(ew: EventWindow): boolean; 17 | confirmAhead(ew: EventWindow): boolean; 18 | isBehindTemp(ew: EventWindow): boolean; 19 | isAheadTemp(ew: EventWindow): boolean; 20 | makeGrowSegment(a: EWIndex): Element; 21 | makeGrowTemp(a: EWIndex): Element; 22 | getSegment(ew: EventWindow, segIndex: EWIndex): Element; 23 | swapSegments(ew: EventWindow, segIndex: EWIndex): boolean; 24 | grow(ew: EventWindow): boolean; 25 | move(ew: EventWindow): boolean; 26 | } 27 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/core/SwapLine.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class SwapLine extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static NORTH: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static SOUTH: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static EAST: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static WEST: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static NW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static NE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | static SW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 13 | static SE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 14 | constructor(type: IElementType, state?: any); 15 | init(): void; 16 | behave(ew: EventWindow): void; 17 | } 18 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/DReg.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow, EWIndex } from "../../mfm/EventWindow"; 3 | import { Res } from "./Res"; 4 | 5 | export class DReg extends Element { 6 | static CREATE = DReg.CREATOR( 7 | { name: "DREG", class: DReg, color: 0xff2020, groups: ["MFM"] }, 8 | { 9 | pDREG_CREATE: 1000, 10 | pRES_CREATE: 200, 11 | pDREG_DESTROY: 10, 12 | pANY_DESTROY: 100, 13 | } 14 | ); 15 | 16 | constructor(type: IElementType, state: any = {}) { 17 | super(type, state); 18 | } 19 | 20 | behave(ew: EventWindow) { 21 | super.behave(ew); 22 | 23 | const neighbor: EWIndex = ew.filter(EventWindow.ADJACENT4WAY, null, true)[0]; 24 | 25 | if (ew.is(neighbor, "EMPTY")) { 26 | const createDReg: boolean = EventWindow.oneIn(this.state.pDREG_CREATE); 27 | const createRes: boolean = EventWindow.oneIn(this.state.pRES_CREATE); 28 | 29 | if (createDReg) { 30 | ew.move(neighbor, DReg.CREATE()); 31 | } else if (createRes) { 32 | ew.move(neighbor, Res.CREATE()); 33 | } else { 34 | ew.swap(neighbor); 35 | } 36 | } else if ((ew.is(neighbor, "DREG") && EventWindow.oneIn(this.state.pDREG_DESTROY)) || EventWindow.oneIn(this.state.pANY_DESTROY)) { 37 | ew.move(neighbor); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/elements/Dirt.ts: -------------------------------------------------------------------------------- 1 | import { Element, EventWindow, Sand } from "mfm-js"; 2 | 3 | export class Dirt extends Element { 4 | static CREATE = Dirt.CREATOR({ name: "DIRT", class: Dirt, color: 0xcf932b, classifications: ["DIGGABLE"], groups: ["LEMMINGS"] }); 5 | static ROCK = Dirt.CREATOR({ name: "ROCK", class: Dirt, color: 0xa14e06, classifications: ["SOLID"], groups: ["LEMMINGS"] }); 6 | static MOSS = Dirt.CREATOR({ name: "MOSS", class: Dirt, color: 0x0ac765, classifications: ["SOLID"], groups: ["LEMMINGS"] }); 7 | 8 | static TODE_RED = Dirt.CREATOR({ name: "TODE_RED", class: Dirt, color: 0xfe4646, classifications: ["SOLID"], groups: ["LEMMINGS"] }); 9 | static TODE_GREEN = Dirt.CREATOR({ name: "TODE_GREEN", class: Dirt, color: 0x00fe81, classifications: ["SOLID"], groups: ["LEMMINGS"] }); 10 | static TODE_BLACK = Dirt.CREATOR({ name: "TODE_BLACK", class: Dirt, color: 0x161c28, classifications: ["SOLID"], groups: ["LEMMINGS"] }); 11 | 12 | static SAND = Sand.CREATOR({ name: "SAND", symbol: "SND", class: Sand, color: 0xffdd00, classifications: ["SAND", "MOVABLE"], groups: ["LEMMINGS"] }); 13 | 14 | 15 | 16 | constructor(type: IElementType, state: any = {}) { 17 | super(type, state); 18 | 19 | this.init(); 20 | } 21 | 22 | init() {} 23 | 24 | behave(ew: EventWindow) { 25 | super.behave(ew); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mfm-js/dist/src/capabilities/Wayfinding.d.ts: -------------------------------------------------------------------------------- 1 | import { Element } from '../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../mfm/EventWindow'; 3 | import { Direction } from '../mfm/Wayfinder'; 4 | 5 | export declare class Wayfinding { 6 | static NAME: string; 7 | static REVERSE(self: Element): void; 8 | static SLIGHT_LEFT(self: Element): void; 9 | static VEER_LEFT(self: Element): void; 10 | static TURN_LEFT(self: Element): void; 11 | static SLIGHT_RIGHT(self: Element): void; 12 | static VEER_RIGHT(self: Element): void; 13 | static TURN_RIGHT(self: Element): void; 14 | static SLIGHT_RANDOMLY(self: Element): void; 15 | static VEER_RANDOMLY(self: Element): void; 16 | static TURN_RANDOMLY(self: Element): void; 17 | static SET_DIRECTION(self: Element, d: Direction): void; 18 | static MOVE_IN_DIRECTION(ew: EventWindow, self: Element, direction: Direction | Direction[], types?: string | string[], leavingAtom?: Element): boolean; 19 | static MOVE_DIRECTIONALLY(ew: EventWindow, self: Element, types?: string | string[], leavingAtom?: Element): boolean; 20 | static SWAP_IN_DIRECTION(ew: EventWindow, self: Element, direction: Direction | Direction[], types?: string | string[]): boolean; 21 | static SWAP_DIRECTIONALLY(ew: EventWindow, self: Element, types?: string | string[]): boolean; 22 | static DIRECT(ew: EventWindow, s: EWIndex, heading: Direction): boolean; 23 | } 24 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/Element.d.ts: -------------------------------------------------------------------------------- 1 | import { EventWindow } from './EventWindow'; 2 | 3 | export interface IElementType { 4 | name: string; 5 | class: any; 6 | symbol?: string; 7 | color?: number; 8 | CREATE?: Function; 9 | classifications?: string[]; 10 | groups?: string[]; 11 | } 12 | export interface IElementTypePartial { 13 | symbol?: string; 14 | name?: string; 15 | class?: any; 16 | color?: number; 17 | CREATE?: Function; 18 | classifications?: string[]; 19 | groups?: string[]; 20 | } 21 | export interface IElement { 22 | TYPE: IElementType; 23 | behave: Function; 24 | state?: any; 25 | } 26 | export declare abstract class Element implements IElement { 27 | static CREATOR(typeDefinition: IElementType, state?: any): (_typeDefinition?: IElementTypePartial, _state?: any) => Element; 28 | static UID: (length?: number) => string; 29 | TYPE: IElementType; 30 | state: any; 31 | classes: Set; 32 | constructor(_type: IElementType, initialState?: any); 33 | initializeState(state?: any): void; 34 | behave(ew: EventWindow): void; 35 | rd(key: string): any; 36 | wr(key: string, value: any): any; 37 | classifyAs(c: string | IElementType): void; 38 | declassify(c: string | IElementType): void; 39 | is(type: string | IElementType | string[] | IElementType[]): boolean; 40 | isCore(type: string | IElementType): boolean; 41 | } 42 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Builder.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | import { Sand } from "./Sand"; 4 | 5 | export class Builder extends Element { 6 | // static CREATE = Builder.CREATOR({ name: "BUILDER", symbol: "BLD", class: Builder, color: 0x121112 }); 7 | 8 | constructor(type: IElementType, state: any = {}) { 9 | super(type, state); 10 | 11 | this.init(); 12 | } 13 | 14 | init() { 15 | this.classifyAs("BUILDER"); 16 | 17 | if( !this.state.transitionAge) { 18 | this.state.transitionAge = 10; 19 | } 20 | } 21 | 22 | behave(ew: EventWindow) { 23 | super.behave(ew); 24 | 25 | const { buildPath, atomizer, totalSteps, transitionAge, age } = this.state; 26 | 27 | 28 | 29 | const currentStep = this.state.currentStep ?? 0; 30 | const didSpread = this.state.didSpread ?? false; 31 | 32 | const empties = ew.filterByType(buildPath, "EMPTY"); 33 | 34 | if (!didSpread && empties.length) { 35 | if (totalSteps) { 36 | if (currentStep < totalSteps) { 37 | ew.mutateMany(empties, this.TYPE.CREATE, [{}, { currentStep: currentStep + 1 }]); 38 | this.state.totalSteps--; 39 | } 40 | } else { 41 | ew.mutateMany(empties, this.TYPE.CREATE); 42 | } 43 | } 44 | 45 | this.state.didSpread = true; 46 | if (age > transitionAge) ew.mutate(0, atomizer); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/ofswamp/Swampling.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../../mfm/Element"; 3 | import { EventWindow } from "../../../mfm/EventWindow"; 4 | import { Wayfinder } from "../../../mfm/Wayfinder"; 5 | import { Swamp } from "./Swamp"; 6 | 7 | export class Swampling extends Element { 8 | //VARIANTS 9 | static CREATE = Swampling.CREATOR({ 10 | name: "SWAMPLING", 11 | class: Swampling, 12 | color: 0x22cc88, 13 | // color: 0x3d5b31, 14 | classifications: ["OFSWAMP", "DIRECTIONAL", "DIRECTABLE"], 15 | groups: ["Swamp"], 16 | }); 17 | 18 | //SYSTEM ELEMENTS 19 | 20 | constructor(type: IElementType, state: any = {}) { 21 | super(type, state); 22 | } 23 | 24 | behave(ew: EventWindow) { 25 | super.behave(ew); 26 | 27 | if (!this.state.heading) { 28 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM()); 29 | const empties = ew.filterByType(EventWindow.ADJACENT8WAY, "EMPTY"); 30 | if (empties.length) { 31 | ew.mutateMany(empties, Swamp.CREATE); 32 | } 33 | } else { 34 | const empties = ew.filterByType(EventWindow.ADJACENT8WAY, "EMPTY"); 35 | if (empties.length > 1) { 36 | Wayfinding.SLIGHT_RIGHT(this); 37 | // Wayfinding.REVERSE(this); 38 | } 39 | Wayfinding.SWAP_DIRECTIONALLY(ew, this, "SWAMP"); 40 | if (EventWindow.oneIn(1.5)) { 41 | Wayfinding.SLIGHT_RIGHT(this); 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/HardCell3.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow, EWIndex } from '../../mfm/EventWindow'; 3 | import { Direction } from '../../mfm/Wayfinder'; 4 | 5 | export declare class HardCell3 extends Element { 6 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static HC3x8: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static HC3x4: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static CELL_SITES: EWIndex[]; 10 | static CELL_WANDER_MAP: Map; 11 | static CELL_DIRECTION_MAP: Map; 12 | static COLORS: number[]; 13 | constructor(type: IElementType, state?: any); 14 | init(): void; 15 | behave(ew: EventWindow): void; 16 | hasStasis(ew: EventWindow): Boolean; 17 | hasBadStructure(ew: EventWindow): Boolean; 18 | figureHops(ew: EventWindow): void; 19 | setColor(): void; 20 | canMove(ew: EventWindow): Boolean; 21 | shouldRegrow(ew: EventWindow): Boolean; 22 | shouldMove(ew: EventWindow): Direction | false; 23 | localType(): string; 24 | isRoot(): Boolean; 25 | isEnd(): Boolean; 26 | upstreams(ew: EventWindow): EWIndex[]; 27 | downstreams(ew: EventWindow): EWIndex[]; 28 | neighbors(ew: EventWindow): EWIndex[]; 29 | getDirectionFromWanderMap(ew: EventWindow, site: EWIndex): Direction; 30 | } 31 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/LivingWall.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | 4 | export class LivingWall extends Element { 5 | static CREATE = LivingWall.CREATOR({ name: "LIVING WALL", class: LivingWall, color: 0x2255aa, groups: ["Structural", "MFM"] }); 6 | static MOVABLE_LIVING_WALL = LivingWall.CREATOR({ 7 | name: "MOVABLE LIVING WALL", 8 | class: LivingWall, 9 | color: 0x2255aa, 10 | classifications: ["MOVABLE", "LIVING WALL"], 11 | groups: ["Structural", "MFM"], 12 | }); 13 | 14 | constructor(type: IElementType, state: any = {}) { 15 | super(type, state); 16 | 17 | this.init(); 18 | } 19 | 20 | init() {} 21 | 22 | behave(ew: EventWindow) { 23 | super.behave(ew); 24 | 25 | this.regen(ew); 26 | this.populateRegenMap(ew); 27 | } 28 | 29 | populateRegenMap(ew: EventWindow) { 30 | const otherLivingWalls = ew.filterByType(EventWindow.ALLADJACENT, "LIVING WALL"); 31 | if (otherLivingWalls.length > (this.state.regenMap?.length ?? 0)) { 32 | this.state.regenMap = otherLivingWalls.map((i) => { 33 | return [i, ew.getSite(i).atom.TYPE.CREATE]; 34 | }); 35 | 36 | this.state.color = this.TYPE.color - this.state.regenMap.length * 6; 37 | } 38 | } 39 | 40 | regen(ew: EventWindow) { 41 | if (this.state.regenMap?.length) { 42 | this.state.regenMap.forEach((w) => { 43 | if (ew.is(w[0], "EMPTY")) { 44 | ew.mutate(w[0], w[1]); 45 | } 46 | }); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Water.ts: -------------------------------------------------------------------------------- 1 | import { Build } from "../../capabilities/Build"; 2 | import { Swap } from "../../capabilities/Swap"; 3 | import { Repel } from "../../capabilities/Repel"; 4 | import { Element, IElementType } from "../../mfm/Element"; 5 | import { EventWindow } from "../../mfm/EventWindow"; 6 | import { Builder } from "./Builder"; 7 | 8 | export class Water extends Element { 9 | static CREATE = Water.CREATOR({ name: "WATER", symbol: "SND", class: Water, color: 0x0011ee, groups: ["Environment"] }); 10 | static BUBBLY_WATER = Water.CREATOR({ name: "BUBBLY WATER", class: Water, color: 0x0011ee, classifications: ["WATER", "BUBBLY"], groups: ["Environment"] }); 11 | 12 | static WATER_GRID = Builder.CREATOR( 13 | { name: "WATER GRID", class: Builder, color: 0x121112, groups: ["Environment"] }, 14 | { buildPath: EventWindow.DIAGONAL4WAY, atomizer: Water.CREATE, totalSteps: 40 } 15 | ); 16 | 17 | static WATER_LINE = Builder.CREATOR( 18 | { name: "WATER LINE", class: Builder, color: 0x121112, groups: ["Environment"] }, 19 | { buildPath: [1, 4], atomizer: Water.CREATE } 20 | ); 21 | 22 | static DO_BUBBLE = Repel.CREATE("WATER", EventWindow.S_QUADRANT, "EMPTY", EventWindow.N_QUADRANT); 23 | 24 | constructor(type: IElementType, state: any = {}) { 25 | super(type, state); 26 | 27 | this.init(); 28 | } 29 | 30 | init() { 31 | this.classifyAs("WATER"); 32 | } 33 | 34 | behave(ew: EventWindow) { 35 | super.behave(ew); 36 | 37 | Swap.DOWN(ew) || Swap.SLIP(ew); 38 | if (ew.selfIs("BUBBLY")) Water.DO_BUBBLE(ew); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/ofswamp/SwampWorker.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../../mfm/Element"; 3 | import { EventWindow } from "../../../mfm/EventWindow"; 4 | import { Wayfinder } from "../../../mfm/Wayfinder"; 5 | import { Swamp } from "./Swamp"; 6 | 7 | export class SwampWorker extends Element { 8 | static CREATE = SwampWorker.CREATOR({ 9 | name: "SWAMP WORKER", 10 | symbol: "SWK", 11 | class: SwampWorker, 12 | color: 0x03460f, 13 | // color: 0x3d5b31, 14 | // color: 0x3d6242, 15 | classifications: ["OFSWAMP", "DIRECTIONAL", "SWAMPLING"], 16 | groups: ["Swamp"], 17 | }); 18 | 19 | constructor(type: IElementType, state: any = {}) { 20 | super(type, state); 21 | } 22 | 23 | behave(ew: EventWindow) { 24 | super.behave(ew); 25 | 26 | const swampworkers = ew.filterByType(EventWindow.ADJACENT8WAY, "SWAMP WORKER"); 27 | const ofswamps = ew.filterByType(EventWindow.ALLADJACENT, "OFSWAMP"); 28 | 29 | if (ofswamps.length < EventWindow.ALLADJACENT.length * 0.4) { 30 | ew.mutate(0, Swamp.CREATE); 31 | return; 32 | } 33 | 34 | if (swampworkers.length) { 35 | ew.mutate(0, Swamp.CREATE); 36 | return; 37 | } 38 | 39 | if (!this.state.heading) { 40 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM(Wayfinder.DIRECTIONS_PRIMARY)); 41 | } else { 42 | if (Wayfinding.SWAP_DIRECTIONALLY(ew, this, "SWAMP")) { 43 | Wayfinding.VEER_RIGHT(this); 44 | } else { 45 | Wayfinding.SLIGHT_RIGHT(this); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/DirectorWall.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from "../../mfm/Element"; 2 | import { EventWindow } from "../../mfm/EventWindow"; 3 | import { Wayfinder } from "../../mfm/Wayfinder"; 4 | import { Director } from "./Director"; 5 | import { Wall } from "../core/Wall"; 6 | 7 | 8 | export class DirectorWall extends Element { 9 | static CREATE = DirectorWall.CREATOR({ name: "DIRECTOR WALL", symbol: "DRWL", class: DirectorWall, color: 0xbe146f, groups: ["Structural"] }); 10 | 11 | constructor(type: IElementType, state: any = {}) { 12 | super(type, state); 13 | 14 | this.init(); 15 | } 16 | 17 | init() {} 18 | 19 | behave(ew: EventWindow) { 20 | super.behave(ew); 21 | 22 | const emptyWallNeighbors = ew.filterByType([...EventWindow.LAYER1, ...EventWindow.LAYER2, ...EventWindow.LAYER3], ["EMPTY", "DIRECTOR"]); 23 | 24 | if (emptyWallNeighbors.length > 0) { 25 | ew.mutateMany( emptyWallNeighbors, Wall.CREATE ); 26 | } 27 | 28 | const emptyDirectorNeighbors = ew.filterByType(EventWindow.LAYER4, "EMPTY"); 29 | 30 | if( emptyDirectorNeighbors.length > 0 ) { 31 | emptyDirectorNeighbors.forEach( (i) => { 32 | let pointingDirection = Wayfinder.indexToDirection(i); 33 | const directorDirection = Wayfinder.turnLeft(pointingDirection); 34 | ew.mutate( i, Director.DIRECTORS_MAP.get(directorDirection) ); 35 | }); 36 | } 37 | 38 | if( EventWindow.oneIn(1000) ) { 39 | const directorNeighbors = ew.filterByType(EventWindow.LAYER4, "DIRECTOR"); 40 | ew.destroy(ew.random(directorNeighbors)); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Wanderer.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | 4 | export declare class Wanderer extends Element { 5 | static CREATE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 6 | static FLY: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static MOSQUITO: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static BIRD: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static FLOCKER: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static SWAMPDATA: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static BIRD_WING: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | static FLY_TAIL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 13 | static MOSQUITO_TAIL: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 14 | constructor(type: IElementType, state?: any); 15 | init(): void; 16 | behave(ew: EventWindow): void; 17 | behaveAsFly(ew: EventWindow): void; 18 | behaveAsMosquito(ew: EventWindow): void; 19 | behaveAsBird(ew: EventWindow): void; 20 | behaveAsFlocker(ew: EventWindow): void; 21 | behaveAsSwampData(ew: EventWindow): void; 22 | blazeTrail(ew: EventWindow): void; 23 | } 24 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/elements/Power.ts: -------------------------------------------------------------------------------- 1 | import { Element, EventWindow, Empty } from "mfm-js"; 2 | 3 | export class Power extends Element { 4 | static CREATE = Power.CREATOR({ name: "POWER", symbol: "POW", class: Power, color: 0x2390aa, groups: ["LEMMINGS"] }); 5 | static DIGGER = Power.CREATOR({ name: "DIGGER", symbol: "DIG", class: Power, color: 0xffffff, groups: ["LEMMINGS"] }, { power: "DIGGER", charges: 1}); 6 | static BLOCKER = Power.CREATOR({ name: "BLOCKER", symbol: "BLK", class: Power, color: 0x2390aa, groups: ["LEMMINGS"] }, { power: "BLOCKER", charges: 1}); 7 | static MINER = Power.CREATOR({ name: "MINER", symbol: "MNR", class: Power, color: 0x2390aa, groups: ["LEMMINGS"] }, { power: "MINER", charges: 1}); 8 | static BUILDER = Power.CREATOR({ name: "BUILDER", symbol: "MNR", class: Power, color: 0x2390aa, groups: ["LEMMINGS"] }, { power: "BUILDER", charges: 1}); 9 | 10 | 11 | constructor(type, state = {}) { 12 | super(type, state); 13 | 14 | this.init(); 15 | } 16 | 17 | init() { 18 | this.state.power = this.state.power ?? "WALKER"; 19 | this.state.charges = this.state.charges ?? 1; 20 | } 21 | 22 | behave(ew: EventWindow) { 23 | super.behave(ew); 24 | 25 | if( !ew.exists( 3 ) ) { 26 | ew.destroy(); 27 | } 28 | 29 | 30 | if(this.state.charges <= 0) { 31 | ew.destroy(); 32 | return; 33 | } 34 | 35 | const nearbyLemming = ew.randomOfType( EventWindow.ADJACENT8WAY, "WALKER" ); 36 | 37 | if( nearbyLemming ) { 38 | (ew.getSite(nearbyLemming).atom as Lemming)?.setRole(this.state.power); 39 | this.state.charges--; 40 | } 41 | } 42 | 43 | 44 | 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Perception.ts: -------------------------------------------------------------------------------- 1 | import { IElementType } from "../mfm/Element"; 2 | import { EventWindow, EWIndex } from "../mfm/EventWindow"; 3 | import { Signal } from "../elements/agents/Signal"; 4 | import { Wayfinder } from "../main"; 5 | import { Direction } from "../mfm/Wayfinder"; 6 | 7 | export type SignalType = "WARN" | "INFORM" | "BECKON"; 8 | export type Message = { 9 | senderId: string; 10 | senderType: IElementType | string; 11 | signalType: SignalType; 12 | message: string; 13 | signalDirection?: Direction; 14 | }; 15 | 16 | export class Perception { 17 | 18 | static SENSE(ew: EventWindow, type: IElementType | string, withinSet: EWIndex[] = EventWindow.ALLADJACENT, minQuantity: number = 1, maxQuantity: number = 1): boolean { 19 | const count = ew.howMany(withinSet, type); 20 | if (count < minQuantity || count > maxQuantity) { 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | static SIGNAL(ew: EventWindow, signalType: SignalType, message: Message): void { 27 | const d = EventWindow.RANDOM(EventWindow.LAYER3); 28 | if( ew.is(d, "EMPTY") ) { 29 | ew.mutate(d, Signal.CREATE, [{}, { heading: Wayfinder.indexToDirection(d), signalType, message }]); 30 | } 31 | } 32 | 33 | static RECEIVE_SIGNAL(ew:EventWindow, self: Element, indexes: EWIndex[] = EventWindow.ALLADJACENT): Message | null { 34 | const signalIndex = ew.filter(indexes, "SIGNAL", true)?.[0]; 35 | if (!signalIndex) return null; 36 | 37 | const signal = ew.getSite(signalIndex)?.atom; 38 | const message = signal.state.message; 39 | if( message.senderId === self.state.uid ) return null; 40 | const signalDirection = Wayfinder.reverse( signal.state.heading ); 41 | 42 | ew.destroy(signalIndex); 43 | return { ...message, signalDirection }; 44 | } 45 | } -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites/lemmings.json: -------------------------------------------------------------------------------- 1 | { 2 | "frames": { 3 | "CIRCLE.png": { 4 | "frame": { "x": 0, "y": 0, "w": 32, "h": 32 }, 5 | "rotated": false, 6 | "trimmed": false, 7 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 8 | "sourceSize": { "w": 32, "h": 32 } 9 | }, 10 | "DEFAULT.png": { 11 | "frame": { "x": 32, "y": 0, "w": 32, "h": 32 }, 12 | "rotated": false, 13 | "trimmed": false, 14 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 15 | "sourceSize": { "w": 32, "h": 32 } 16 | }, 17 | "DIRT.png": { 18 | "frame": { "x": 0, "y": 32, "w": 32, "h": 32 }, 19 | "rotated": false, 20 | "trimmed": false, 21 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 22 | "sourceSize": { "w": 32, "h": 32 } 23 | }, 24 | "ROCK.png": { 25 | "frame": { "x": 32, "y": 32, "w": 32, "h": 32 }, 26 | "rotated": false, 27 | "trimmed": false, 28 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 29 | "sourceSize": { "w": 32, "h": 32 } 30 | }, 31 | "LEMM_HEAD.png": { 32 | "frame": { "x": 0, "y": 64, "w": 32, "h": 32 }, 33 | "rotated": false, 34 | "trimmed": false, 35 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 36 | "sourceSize": { "w": 32, "h": 32 } 37 | }, 38 | "LEMM.png": { 39 | "frame": { "x": 32, "y": 64, "w": 32, "h": 32 }, 40 | "rotated": false, 41 | "trimmed": false, 42 | "spriteSourceSize": { "x": 0, "y": 0, "w": 32, "h": 32 }, 43 | "sourceSize": { "w": 32, "h": 32 } 44 | } 45 | }, 46 | "meta": { "app": "https://github.com/piskelapp/piskel/", "version": "1.0", "image": "lemmings.png", "format": "RGBA8888", "size": { "w": 64, "h": 96 } } 47 | } 48 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import '../styles/main.scss' 3 | import MFMS from '../components/mfms.vue' 4 | --- 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | MFM.ROCKS 13 | 14 | 15 |
16 | 17 |

A Movable Feast Machine Simulator written in JavaScript

18 | 40 |
41 | 42 | 45 | 46 |
47 | 48 |
49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/renderer/sprites.ts: -------------------------------------------------------------------------------- 1 | import { Sprite, Assets, Texture, Spritesheet } from "pixi.js"; 2 | 3 | import DEFAULT from "./sprites/CIRCLE.png"; 4 | // import SOLID from "./sprites/SOLID.png"; 5 | // import LEMM from "./sprites/LEMM.png"; 6 | // import LEMM_HEAD from "./sprites/LEMM_HEAD.png"; 7 | // import DIRT from "./sprites/DIRT.png"; 8 | // import ROCK from "./sprites/ROCK.png"; 9 | // import POWER from "./sprites/POWER.png"; 10 | 11 | import SPRITESHEET_DATA from './sprites/lemmings.json'; 12 | import SPRITESHEET from './sprites/lemmings.png'; 13 | 14 | export const DEFAULT_TEXTURE = await Assets.load( DEFAULT ); 15 | 16 | const SPRITE_MAP = { 17 | // "EMPTY": "DEFAULT", 18 | "LEMM": "LEMM", 19 | "LEMM_HEAD": "LEMM_HEAD", 20 | // "DIRT": "DIRT", 21 | // "ROCK": "ROCK", 22 | // "TODE_RED": "CIRCLE", 23 | // "TODE_GREEN": "CIRCLE", 24 | // "TODE_BLACK": "CIRCLE", 25 | // "SAND": "DIRT", 26 | // "MOSS": "DIRT", 27 | // "SOLID": "CIRCLE", 28 | // "EXIT_FRAME": "ROCK", 29 | // "EMPTY_EXIT": "ROCK", 30 | // "EXIT": "ROCK", 31 | } 32 | 33 | const SPRITE_SHEET_TEXTURE = await Assets.load( SPRITESHEET ); 34 | export const SPRITES = new Spritesheet( 35 | SPRITE_SHEET_TEXTURE, 36 | SPRITESHEET_DATA 37 | ); 38 | 39 | await SPRITES.parse(); 40 | 41 | console.log( SPRITES ); 42 | 43 | export const LEMMING_TEXTURES = {} 44 | 45 | 46 | export function getTexture( name, rotate ) { 47 | 48 | // rotate = [0,2,4,6,8][~~(Math.random() * 4)]; 49 | 50 | const t = SPRITES.textures[`${SPRITE_MAP[name]}.png`] || LEMMING_TEXTURES[name] || DEFAULT_TEXTURE; 51 | 52 | if( rotate ) { 53 | return rotateTexture( t, rotate ); 54 | } 55 | return t; 56 | } 57 | 58 | function rotateTexture( texture, rotation ) { 59 | return new Texture( { 60 | source: texture.baseTexture, 61 | frame: texture.frame, 62 | orig: texture.orig, 63 | trim:texture.trim, 64 | rotate: rotation ?? 0 65 | }); 66 | } -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Wall.ts: -------------------------------------------------------------------------------- 1 | import { Decay } from "../../capabilities/Decay"; 2 | import { Repel } from "../../capabilities/Repel"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Builder } from "./Builder"; 6 | 7 | export class Wall extends Element { 8 | static CREATE = Wall.CREATOR({ name: "WALL", class: Wall, color: 0x3311cc, groups: ["Structural", "MFM"] }); 9 | static MOVABLE_WALL = Wall.CREATOR({ name: "MOVABLE WALL", class: Wall, color: 0x8811cc, classifications: ["MOVABLE"], groups: ["Structural", "MFM"] }); 10 | static DECAY_WALL = Wall.CREATOR({ name: "DECAY WALL", class: Wall, color: 0x0000ee, classifications: ["DECAYABLE", "WALL"], groups: ["Structural"] }); 11 | static DECAY_WALL_10 = Wall.CREATOR( 12 | { name: "DECAY WALL 10", class: Wall, color: 0x0000ee, classifications: ["DECAYABLE", "WALL", "DECAY WALL"], groups: ["Structural"] }, 13 | { lifeSpan: 10 } 14 | ); 15 | static DECAY_WALL_25 = Wall.CREATOR( 16 | { name: "DECAY WALL 25", class: Wall, color: 0x0000ee, classifications: ["DECAYABLE", "WALL", "DECAY WALL"], groups: ["Structural"] }, 17 | { lifeSpan: 25 } 18 | ); 19 | static DECAY_WALL_50 = Wall.CREATOR( 20 | { name: "DECAY WALL 50", class: Wall, color: 0x0000ee, classifications: ["DECAYABLE", "WALL", "DECAY WALL"], groups: ["Structural"] }, 21 | { lifeSpan: 50 } 22 | ); 23 | 24 | static MOVABLE_WALL_GRID = Builder.CREATOR( 25 | { name: "MOVABLE WALL GRID", class: Builder, color: 0x121112, groups: ["Structural"] }, 26 | { buildPath: EventWindow.DIAGONAL4WAY, atomizer: Wall.MOVABLE_WALL } 27 | ); 28 | 29 | constructor(type: IElementType, state: any = {}) { 30 | super(type, state); 31 | 32 | this.init(); 33 | } 34 | 35 | init() {} 36 | 37 | behave(ew: EventWindow) { 38 | super.behave(ew); 39 | 40 | if (ew.selfIs("DECAYABLE")) { 41 | Decay.DECAY(ew, this, this.state.lifeSpan ?? 100, 2); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mfm-js/dist/renderers/pixi/renderer.d.ts: -------------------------------------------------------------------------------- 1 | import { Sprite, Container, Application, Texture, FederatedPointerEvent } from 'pixi.js'; 2 | import { Site } from '../../src/mfm/Site.js'; 3 | import { Tile } from '../../src/mfm/Tile.js'; 4 | 5 | export interface IRenderer { 6 | tile: Tile; 7 | render: Function; 8 | view: any; 9 | } 10 | export interface ISiteRenderer { 11 | } 12 | export declare class PixiRenderer implements IRenderer { 13 | rendererWidth: number; 14 | rendererHeight: number; 15 | tile: Tile; 16 | tileWidth: number; 17 | tileHeight: number; 18 | totalSites: number; 19 | siteSize: number; 20 | siteTexture: Texture; 21 | particleContainer: Container; 22 | pixiApplication: Application; 23 | siteVisuals: Map; 24 | subdivisions: number; 25 | gridDivisions: number[][]; 26 | gridDivisionTotal: number; 27 | siteArray: Site[]; 28 | renderSpeed: number; 29 | fixedRenderSpeed: number; 30 | renderMultiplier: number; 31 | view: any; 32 | clickArea: Container; 33 | pointerDown: boolean; 34 | curSelectedElementFunction: Function; 35 | selectedSite: Site; 36 | mouseEnabled: boolean; 37 | brushSize: number; 38 | constructor(t: Tile, rendererW: number, rendererH: number); 39 | init(): Promise; 40 | calculateSubdivisions(): number; 41 | setSubdivisions(subs: number): void; 42 | setRenderMultiplier(mult: number): void; 43 | initializePIXI(): Promise; 44 | initializeVisuals(): Promise; 45 | createSubdivisions(subdivisions: any): void; 46 | startRendering(): void; 47 | deconstruct(): void; 48 | render(): void; 49 | initializeClickArea(): void; 50 | getSitesFromCanvasXY(x: number, y: number, size?: number): Site[]; 51 | getSiteFromCanvasXY(x: number, y: number): Site; 52 | handleClick(e: FederatedPointerEvent): void; 53 | addAtom(site: Site): void; 54 | minValue(v1: any, v2: any): any; 55 | maxValue(v1: any, v2: any): any; 56 | clear(): void; 57 | } 58 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/VirtualEventWindow.d.ts: -------------------------------------------------------------------------------- 1 | import { EWIndex, EventWindow } from './EventWindow'; 2 | import { Site } from './Site'; 3 | import { ICoordinate } from './TileCoordinate'; 4 | 5 | export type VirtualEWIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144; 6 | export declare class VirtualEventWindow { 7 | static VIRTUAL_WINDOW_OFFSETS: Array; 8 | static VIRTUAL_WINDOW_OFFSETS_MAP: Map; 9 | static ORIENTATIONS: Array>; 10 | static REVERSE_ORIENTATIONS: Array>; 11 | static CREATE_EW_TO_VIRTUAL_MAP(centerIndex: EWIndex): Map; 12 | static CREATE_VIRTUAL_TO_EW_MAP(centerIndex: EWIndex): Map; 13 | static getOrientedSite(fromOrientation: EWIndex, virtualIndex: VirtualEWIndex, ew: EventWindow): Site; 14 | static getOrientedSites(fromOrientation: EWIndex, virtualIndexes: VirtualEWIndex[], ew: EventWindow): Site[]; 15 | static getOrientedSiteIndex(fromOrientation: EWIndex, virtualIndex: VirtualEWIndex): EWIndex; 16 | static getOrientedSiteIndexes(fromOrientation: EWIndex, virtualIndexes: VirtualEWIndex[]): EWIndex[]; 17 | static getVirtualIndex(fromOrientation: EWIndex, siteIndex: EWIndex): VirtualEWIndex; 18 | static getVirtualIndexes(fromOrientation: EWIndex, siteIndexes: EWIndex[]): VirtualEWIndex[]; 19 | } 20 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/ofswamp/Swamp.ts: -------------------------------------------------------------------------------- 1 | import { Decay } from "../../../capabilities/Decay"; 2 | import { Element, IElementType } from "../../../mfm/Element"; 3 | import { EventWindow } from "../../../mfm/EventWindow"; 4 | import { Wall } from "../../core/Wall"; 5 | import { Swampling } from "./Swampling"; 6 | import { SwampWorker } from "./SwampWorker"; 7 | 8 | export class Swamp extends Element { 9 | static CREATE = Swamp.CREATOR({ 10 | name: "SWAMP", 11 | class: Swamp, 12 | color: 0x3d5b31, 13 | classifications: ["ENV", "OFSWAMP", "DECAYABLE"], 14 | groups: ["Swamp"], 15 | }); 16 | 17 | static BOG = Wall.CREATOR({ name: "BOG", class: Wall, classifications: ["DECAYABLE"], color: 0x142606 }, { lifeSpan: 45 }); 18 | 19 | constructor(type: IElementType, state: any = {}) { 20 | super(type, state); 21 | 22 | this.init(); 23 | } 24 | 25 | init() { 26 | this.state.lifeSpan = this.state.lifeSpan ?? 90; 27 | } 28 | 29 | behave(ew: EventWindow) { 30 | super.behave(ew); 31 | 32 | const swamplings = ew.filterByType(EventWindow.ADJACENT8WAY, "SWAMPLING"); 33 | const ofswamps = ew.filterByType(EventWindow.ADJACENT8WAY, "OFSWAMP"); 34 | const swamps = ew.filterByType(EventWindow.ALLADJACENT, "SWAMP"); 35 | 36 | if (!this.state.made && swamps.length === EventWindow.ALLADJACENT.length) { 37 | this.state.made = true; 38 | ew.mutate(0, SwampWorker.CREATE); 39 | return; 40 | } 41 | 42 | if (swamplings.length > 0) { 43 | this.state.age = 0; 44 | if (EventWindow.oneIn(6)) this.grow(ew); 45 | } else { 46 | if (ew.selfIs("DECAYABLE") && ofswamps.length < 7) { 47 | if (Decay.DECAY(ew, this, this.state.lifeSpan)) { 48 | ew.mutate(0, Swamp.BOG); 49 | } 50 | } else { 51 | this.state.age = 0; 52 | } 53 | } 54 | } 55 | 56 | grow(ew: EventWindow) { 57 | const empties = ew.filterByType(EventWindow.ADJACENT4WAY, "EMPTY"); 58 | 59 | if (empties.length) { 60 | // ew.mutateMany(empties, this.TYPE.CREATE); 61 | ew.mutate(ew.random(empties), this.TYPE.CREATE); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/DirectionalDirector.ts: -------------------------------------------------------------------------------- 1 | import { setFlagsFromString } from "v8"; 2 | import { Wayfinding } from "../../capabilities/Wayfinding"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Wayfinder } from "../../mfm/Wayfinder"; 6 | 7 | export class DirectionalDirector extends Element { 8 | static CREATE = DirectionalDirector.CREATOR({ name: "DIRDIR", symbol: "DDIR", class: DirectionalDirector, color: 0xaaaaaa, groups: ["Agents"], classifications: ["DIRECTOR"], }); 9 | 10 | 11 | static DDIR_ONCE = DirectionalDirector.CREATOR( 12 | { name: "DIRDIR ONCE", symbol: "DDIRO", class: DirectionalDirector, color: 0x999999, classifications: ["DIRECTOR"], groups: ["Agents"] }, 13 | { directableTypes: ["DIRECTABLE"], once: true } 14 | ); 15 | 16 | constructor(type: IElementType, state: any = {}) { 17 | super(type, state); 18 | 19 | this.init(); 20 | } 21 | 22 | init() {} 23 | 24 | behave(ew: EventWindow) { 25 | super.behave(ew); 26 | 27 | if (!this.state.heading) { 28 | this.state.heading = Wayfinder.RANDOM( Wayfinder.DIRECTIONS ); 29 | return; 30 | } 31 | 32 | // Look for directables and direct one 33 | const directables = ew.filterByType(EventWindow.ALLADJACENT, this.state?.directableTypes ?? ["DIRECTABLE"]); 34 | if( directables.length > 0) { 35 | const directable = directables[0]; 36 | const directed = Wayfinding.DIRECT(ew, directable, this.state?.pointing ?? Wayfinder.reverse(this.state.heading) ); 37 | 38 | //Die if once and directed 39 | if( this.state.once && directed ) { 40 | ew.destroy(0); 41 | return; 42 | } 43 | 44 | // this.state.heading = Wayfinder.reverse(this.state.heading); 45 | } 46 | 47 | // Move in the direction of the heading 48 | if( this.state.heading ) { 49 | const moved = Wayfinding.MOVE_DIRECTIONALLY( ew, this, this.state.direction ); 50 | 51 | if(!moved) { 52 | this.state.heading = Wayfinder.slightRandom( this.state.heading ); 53 | } 54 | } 55 | 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/elements/Exit.ts: -------------------------------------------------------------------------------- 1 | import { Element, Empty, EventWindow, Decay } from "mfm-js"; 2 | import { useGameState } from "../useGameState"; 3 | 4 | export class Exit extends Element { 5 | static CREATE = Exit.CREATOR({ name: "EXIT", class: Exit, color: 0x444444, classifications: ["GOAL"], groups: ["LEMMINGS"] }); 6 | static EMPTY_GOAL = Exit.CREATOR({name: "EMPTY_EXIT", class: Exit, color: 0x444444, classifications: ["GOAL", "EMPTY", "DECORATION"]}); 7 | static EXIT_FRAME = Exit.CREATOR({name: "EXIT_FRAME", class: Exit, color: 0xffc72e, classifications: ["GOAL", "EMPTY", "DECORATION"]}); 8 | 9 | static BUILD_MAP = { 10 | '2': Exit.EMPTY_GOAL, 11 | '1': Exit.EMPTY_GOAL, 12 | '4': Exit.EMPTY_GOAL, 13 | '6': Exit.EMPTY_GOAL, 14 | '3': Exit.EMPTY_GOAL, 15 | '8': Exit.EMPTY_GOAL, 16 | '5': Exit.EXIT_FRAME, 17 | '7': Exit.EXIT_FRAME, 18 | '9': Exit.EXIT_FRAME, 19 | '10': Exit.EXIT_FRAME, 20 | '12': Exit.EXIT_FRAME, 21 | '14': Exit.EXIT_FRAME, 22 | '20': Exit.EXIT_FRAME, 23 | } 24 | constructor(type: IElementType, state: any = {}) { 25 | super(type, state); 26 | 27 | this.init(); 28 | } 29 | 30 | init() { 31 | this.state.exitCount = 0; 32 | } 33 | 34 | behave(ew: EventWindow) { 35 | super.behave(ew); 36 | 37 | if (ew.selfIs("DECORATION")) { 38 | Decay.DECAY(ew, this, 50, 1); 39 | return; 40 | } 41 | 42 | this.build(ew); 43 | 44 | const lemmings = ew.filter( EventWindow.ALLADJACENT, 'LEMM'); 45 | 46 | if( lemmings.length ) { 47 | lemmings.forEach( l => ew.destroy(l) ); 48 | this.state.exitCount += lemmings.length; 49 | useGameState().currentSavedLemmings.value += lemmings.length; 50 | } 51 | 52 | } 53 | 54 | build( ew ) { 55 | const buildSites = Object.keys(Exit.BUILD_MAP).map( key => parseInt(key) ); 56 | 57 | buildSites.forEach( (siteIndex) => { 58 | if( ew.is( siteIndex, "GOAL") ) { 59 | return; 60 | } 61 | 62 | if( ew.is( siteIndex, "EMPTY") ) { 63 | ew.mutate( siteIndex, Exit.BUILD_MAP[siteIndex] ); 64 | } 65 | 66 | }); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /mfm-js/dist/src/elements/agents/Director.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from '../../mfm/Element'; 2 | import { EventWindow } from '../../mfm/EventWindow'; 3 | import { Direction } from '../../mfm/Wayfinder'; 4 | 5 | export declare class Director extends Element { 6 | static N: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 7 | static S: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 8 | static E: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 9 | static W: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 10 | static NE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 11 | static NW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 12 | static SW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 13 | static SE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 14 | static NNE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 15 | static NNW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 16 | static ENE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 17 | static WNW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 18 | static SSE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 19 | static SSW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 20 | static ESE: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 21 | static WSW: (_typeDefinition?: import('../../mfm/Element').IElementTypePartial, _state?: any) => Element; 22 | static DIRECTORS_MAP: Map; 23 | constructor(type: IElementType, state?: any); 24 | init(): void; 25 | behave(ew: EventWindow): void; 26 | } 27 | -------------------------------------------------------------------------------- /mfm-js/dist/src/main.d.ts: -------------------------------------------------------------------------------- 1 | export { Tile } from './mfm/Tile'; 2 | export { Site } from './mfm/Site'; 3 | export { Element } from './mfm/Element'; 4 | export { ElementRegistry } from './mfm/ElementRegistry'; 5 | export { EventWindow } from './mfm/EventWindow'; 6 | export { VirtualEventWindow } from './mfm/VirtualEventWindow'; 7 | export { CapabilityRegistry } from './mfm/Capability'; 8 | export { Wayfinder } from './mfm/Wayfinder'; 9 | export { Wayfinding } from './capabilities/Wayfinding'; 10 | export { Build } from './capabilities/Build'; 11 | export { Decay } from './capabilities/Decay'; 12 | export { Repel } from './capabilities/Repel'; 13 | export { Swap } from './capabilities/Swap'; 14 | export { Empty } from './elements/core/Empty'; 15 | export { DReg } from './elements/core/DReg'; 16 | export { Res } from './elements/core/Res'; 17 | export { ForkBomb } from './elements/core/ForkBomb'; 18 | export { AntiForkBomb } from './elements/core/AntiForkBomb'; 19 | export { SwapLine } from './elements/core/SwapLine'; 20 | export { Wall } from './elements/core/Wall'; 21 | export { LivingWall } from './elements/agents/LivingWall'; 22 | export { Sand } from './elements/core/Sand'; 23 | export { Water } from './elements/core/Water'; 24 | export { Builder } from './elements/core/Builder'; 25 | export { Goop } from './elements/core/Goop'; 26 | export { Looper } from './elements/agents/Looper'; 27 | export { Bein } from './elements/agents/Bein'; 28 | export { Wanderer } from './elements/agents/Wanderer'; 29 | export { Director } from './elements/agents/Director'; 30 | export { DirectorWall } from './elements/agents/DirectorWall'; 31 | export { DirectionalDirector } from './elements/agents/DirectionalDirector'; 32 | export { Swamp } from './elements/agents/ofswamp/Swamp'; 33 | export { Swampling } from './elements/agents/ofswamp/Swampling'; 34 | export { SwampWorker } from './elements/agents/ofswamp/SwampWorker'; 35 | export { Sentry } from './elements/agents/Sentry'; 36 | export { HardCell3 } from './elements/agents/HardCell3'; 37 | export { SwapWorm } from './elements/agents/SwapWorm'; 38 | export { WormTrap } from './elements/components/WormTrap'; 39 | export { MeshNet } from './elements/agents/MeshNet'; 40 | export { Signal } from './elements/agents/Signal'; 41 | -------------------------------------------------------------------------------- /mfmrocks/README.md: -------------------------------------------------------------------------------- 1 | # Astro Starter Kit: Minimal 2 | 3 | ```sh 4 | npm create astro@latest -- --template minimal 5 | ``` 6 | 7 | [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/minimal) 8 | [![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/minimal) 9 | [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/minimal/devcontainer.json) 10 | 11 | > 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun! 12 | 13 | ## 🚀 Project Structure 14 | 15 | Inside of your Astro project, you'll see the following folders and files: 16 | 17 | ```text 18 | / 19 | ├── public/ 20 | ├── src/ 21 | │ └── pages/ 22 | │ └── index.astro 23 | └── package.json 24 | ``` 25 | 26 | Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name. 27 | 28 | There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components. 29 | 30 | Any static assets, like images, can be placed in the `public/` directory. 31 | 32 | ## 🧞 Commands 33 | 34 | All commands are run from the root of the project, from a terminal: 35 | 36 | | Command | Action | 37 | | :------------------------ | :----------------------------------------------- | 38 | | `npm install` | Installs dependencies | 39 | | `npm run dev` | Starts local dev server at `localhost:4321` | 40 | | `npm run build` | Build your production site to `./dist/` | 41 | | `npm run preview` | Preview your build locally, before deploying | 42 | | `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | 43 | | `npm run astro -- --help` | Get help using the Astro CLI | 44 | 45 | ## 👀 Want to learn more? 46 | 47 | Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). 48 | -------------------------------------------------------------------------------- /mfm-js/src/main.ts: -------------------------------------------------------------------------------- 1 | //CORE MFM 2 | export { Tile } from "./mfm/Tile"; 3 | export { Site } from "./mfm/Site"; 4 | export { Element } from "./mfm/Element"; 5 | export { ElementRegistry } from "./mfm/ElementRegistry"; 6 | export { EventWindow } from "./mfm/EventWindow"; 7 | export { VirtualEventWindow } from "./mfm/VirtualEventWindow"; 8 | export { CapabilityRegistry } from "./mfm/Capability"; 9 | export { Wayfinder } from "./mfm/Wayfinder"; 10 | 11 | // CAPABILITIES 12 | export { Wayfinding } from "./capabilities/Wayfinding"; 13 | export { Build } from "./capabilities/Build"; 14 | export { Decay } from "./capabilities/Decay"; 15 | export { Repel } from "./capabilities/Repel"; 16 | export { Swap } from "./capabilities/Swap"; 17 | 18 | ///ELEMENTS 19 | export { Empty } from "./elements/core/Empty"; 20 | export { DReg } from "./elements/core/DReg"; 21 | export { Res } from "./elements/core/Res"; 22 | export { ForkBomb } from "./elements/core/ForkBomb"; 23 | export { AntiForkBomb } from "./elements/core/AntiForkBomb"; 24 | export { SwapLine } from "./elements/core/SwapLine"; 25 | export { Wall } from "./elements/core/Wall"; 26 | export { LivingWall } from "./elements/agents/LivingWall"; 27 | export { Sand } from "./elements/core/Sand"; 28 | export { Water } from "./elements/core/Water"; 29 | export { Builder } from "./elements/core/Builder"; 30 | export { Goop } from "./elements/core/Goop"; 31 | export { Looper } from "./elements/agents/Looper"; 32 | export { Bein } from "./elements/agents/Bein"; 33 | export { Wanderer } from "./elements/agents/Wanderer"; 34 | export { Director } from "./elements/agents/Director"; 35 | export { DirectorWall } from "./elements/agents/DirectorWall"; 36 | export { DirectionalDirector } from "./elements/agents/DirectionalDirector"; 37 | export { Swamp } from "./elements/agents/ofswamp/Swamp"; 38 | export { Swampling } from "./elements/agents/ofswamp/Swampling"; 39 | export { SwampWorker } from "./elements/agents/ofswamp/SwampWorker"; 40 | export { Sentry } from "./elements/agents/Sentry"; 41 | export { HardCell3 } from "./elements/agents/HardCell3"; 42 | export { SwapWorm } from "./elements/agents/SwapWorm"; 43 | export { WormTrap } from "./elements/components/WormTrap"; 44 | export { MeshNet } from "./elements/agents/MeshNet"; 45 | export { Signal } from "./elements/agents/Signal"; -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Sand.ts: -------------------------------------------------------------------------------- 1 | import { Swap } from "../../capabilities/Swap"; 2 | import { Repel } from "../../capabilities/Repel"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Builder } from "./Builder"; 6 | 7 | export class Sand extends Element { 8 | static CREATE = Sand.CREATOR({ name: "SAND", symbol: "SND", class: Sand, color: 0xffdd00, classifications: ["SAND", "MOVABLE"], groups: ["Environment"] }); 9 | static RED_SAND = Sand.CREATOR({ name: "RED SAND", class: Sand, color: 0xff0000, classifications: ["SAND", "BUBBLY", "MOVABLE"], groups: ["Environment"] }); 10 | static PINK_SAND = Sand.CREATOR({ 11 | name: "PINK SAND", 12 | class: Sand, 13 | color: 0xfa86c4, 14 | classifications: ["BUBBLY", "SAND", "FLOATS", "MOVABLE"], 15 | groups: ["Environment"], 16 | }); 17 | static FLOATY_SAND = Sand.CREATOR({ 18 | name: "FLOATY SAND", 19 | class: Sand, 20 | color: 0x000000, 21 | classifications: ["FLOATS", "SAND", "MOVABLE"], 22 | groups: ["Environment"], 23 | }); 24 | 25 | static GREEN_SAND = Sand.CREATOR({ name: "GREEN SAND", class: Sand, color: 0x8ac926, classifications: ["SAND", "MOVABLE"], groups: ["Environment"] }); 26 | static BLUE_SAND = Sand.CREATOR({ name: "BLUE SAND", class: Sand, color: 0x1982c4, classifications: ["SAND", "MOVABLE"], groups: ["Environment"] }); 27 | static PURPLE_SAND = Sand.CREATOR({ name: "PURPLE SAND", class: Sand, color: 0x6a4c93, classifications: ["SAND", "MOVABLE"], groups: ["Environment"] }); 28 | 29 | static SAND_GRID = Builder.CREATOR( 30 | { name: "SAND GRID", class: Builder, color: 0x121112, groups: ["Environment"] }, 31 | { buildPath: EventWindow.DIAGONAL4WAY, atomizer: Sand.CREATE, totalSteps: 40 } 32 | ); 33 | 34 | //CAPABILITIES 35 | static BUBBLE = Repel.CREATE("SAND", EventWindow.S_QUADRANT, "EMPTY", EventWindow.N_QUADRANT); 36 | 37 | constructor(type: IElementType, state: any = {}) { 38 | super(type, state); 39 | this.init(); 40 | } 41 | 42 | init() {} 43 | 44 | behave(ew: EventWindow) { 45 | super.behave(ew); 46 | 47 | Swap.DOWN(ew) || Swap.SIDE(ew); 48 | if (ew.selfIs("BUBBLY")) Sand.BUBBLE(ew); 49 | 50 | if (ew.selfIs("FLOATS")) { 51 | Swap.FLOAT(ew, 1.25); 52 | } else { 53 | Swap.SINK(ew); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/useGameState.js: -------------------------------------------------------------------------------- 1 | import { computed, ref, watch } from 'vue'; 2 | import {LEVEL1} from './levels/level1'; 3 | import {LEVEL2} from './levels/level2'; 4 | import {LEVEL3} from './levels/level3'; 5 | import {LEVEL4} from './levels/level4'; 6 | import {LEVEL5} from './levels/level5'; 7 | 8 | const levels = [LEVEL1, LEVEL2, LEVEL3, LEVEL4, LEVEL5]; 9 | let currentLevelIndex = 0; 10 | 11 | const currentLevel = ref(levels[currentLevelIndex]); 12 | const currentSavedLemmings = ref(0); 13 | const currentSaveGoal = ref(50); 14 | const currentResources = ref(currentLevel.value.resources); 15 | const csi = ref(null); //CurrentSelectedResourceIndex 16 | 17 | const levelPassed = computed(() => { 18 | return currentSavedLemmings.value >= currentSaveGoal.value; 19 | }); 20 | 21 | watch( () => levelPassed.value, () => { 22 | if( levelPassed.value ) { 23 | nextLevel(); 24 | } 25 | }); 26 | 27 | watch( () => levelPassed.value, () => { 28 | if( levelPassed.value ) { 29 | nextLevel(); 30 | } 31 | }); 32 | 33 | loadLevel(LEVEL1); 34 | 35 | export function useGameState() { 36 | 37 | return { 38 | currentLevel, 39 | currentSavedLemmings, 40 | currentSaveGoal, 41 | currentResources, 42 | csi, 43 | levelPassed, 44 | loadLevel, 45 | nextLevel, 46 | prevLevel, 47 | selectResource, 48 | deselectResource, 49 | } 50 | } 51 | 52 | export function resetLevel() { 53 | currentSavedLemmings.value = 0; 54 | currentSaveGoal.value = currentLevel.value.saveGoal; 55 | currentResources.value = [...currentLevel.value.resources]; 56 | } 57 | 58 | export function loadLevel(level) { 59 | currentLevel.value = level; 60 | resetLevel(); 61 | } 62 | 63 | export function nextLevel() { 64 | currentLevelIndex = currentLevelIndex >= levels.length - 1 ? 0 : currentLevelIndex+1; 65 | loadLevel(levels[currentLevelIndex]); 66 | } 67 | 68 | export function prevLevel() { 69 | currentLevelIndex = currentLevelIndex <= 0 ? levels.length - 1 : currentLevelIndex-1; 70 | loadLevel(levels[currentLevelIndex]); 71 | } 72 | 73 | export function selectResource(type) { 74 | const resIndex = currentResources.value.findIndex(r => r.type === type); 75 | 76 | if( resIndex !== -1 ) { 77 | csi.value = resIndex; 78 | } else { 79 | csi.value = null; 80 | } 81 | 82 | } 83 | 84 | export function deselectResource() { 85 | csi.value = null; 86 | } -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/Signal.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../capabilities/Wayfinding"; 2 | import { Decay } from "../../capabilities/Decay"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Wayfinder } from "../../mfm/Wayfinder"; 6 | import { AntiForkBomb } from "../core/AntiForkBomb"; 7 | 8 | export class Signal extends Element { 9 | //VARIANTS 10 | static CREATE = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0x46215c, classifications: ["DIRECTIONAL", "DECAYABLE", "MOVABLE"], groups: ["MFM"] }); 11 | 12 | static N = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "N" }); 13 | static NE = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "NE" }); 14 | static E = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "E" }); 15 | static SE = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "SE" }); 16 | static S = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "S" }); 17 | static SW = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "SW" }); 18 | static W = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "W" }); 19 | static NW = Signal.CREATOR({ name: "SIGNAL", class: Signal, color: 0xa414ff, classifications: ["DIRECTIONAL", "DECAYABLE"], groups: [] }, { heading: "NW" }); 20 | 21 | constructor(type: IElementType, state: any = {}) { 22 | super(type, state); 23 | this.init(); 24 | } 25 | 26 | init() { 27 | } 28 | 29 | behave(ew: EventWindow) { 30 | super.behave(ew); 31 | 32 | if(ew.selfIs("DECAYABLE")) { 33 | Decay.DECAY(ew, this, this.state.lifeSpan ?? 50, 2); 34 | } 35 | 36 | if (!this.state.heading) { 37 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM()); 38 | } 39 | 40 | const moved = Wayfinding.MOVE_DIRECTIONALLY(ew, this); 41 | 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mfm-js/src/elements/components/WormTrap.ts: -------------------------------------------------------------------------------- 1 | import { Decay } from "../../capabilities/Decay"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow } from "../../mfm/EventWindow"; 4 | import { Builder } from "../core/Builder"; 5 | import { Wall } from "../core/Wall"; 6 | 7 | export class WormTrap extends Element { 8 | static CREATE = WormTrap.CREATOR({ name: "WORMTRAP", class: WormTrap, color: 0x8844ee, groups: ["Structural"] }); 9 | 10 | static BUILDER = Builder.CREATOR( 11 | { name: "WORM TRAP WALL", class: Builder, color: 0x121112, groups: ["Misc"] }, 12 | { buildPath: [4], totalSteps: 5, atomizer: Wall.CREATE, transitionAge: 1 } 13 | ); 14 | 15 | constructor(type: IElementType, state: any = {}) { 16 | super(type, state); 17 | 18 | this.init(); 19 | } 20 | 21 | init() { 22 | this.state.status = "LOOKING"; 23 | } 24 | 25 | behave(ew: EventWindow) { 26 | super.behave(ew); 27 | 28 | switch( this.state.status ) { 29 | case "LOOKING": 30 | this.captureWorm(ew); 31 | break; 32 | case "HOLDING": 33 | ew.mutate(2, WormTrap.BUILDER ); 34 | ew.mutate(3, WormTrap.BUILDER ); 35 | this.state.status = "BUILT"; 36 | break; 37 | case "BUILT": 38 | 39 | if( ew.is( 1, "SWAPWORM")) { 40 | const worm = ew.getSite(1).atom; 41 | worm.state.heading = "E"; 42 | worm.state.status = "DIRECTED"; 43 | } 44 | 45 | if( ew.is( 2, "WORM TRAP WALL") ) { 46 | return; 47 | } else if( ew.is( 2, "WALL") ) { 48 | ew.swap(4); 49 | } else { 50 | ew.mutateMany([2,3], Wall.CREATE); 51 | this.state.status = "DONE"; 52 | return; 53 | } 54 | break; 55 | } 56 | 57 | if (ew.selfIs("DECAYABLE")) { 58 | Decay.DECAY(ew, this, this.state.lifeSpan ?? 100, 2); 59 | } 60 | } 61 | 62 | captureWorm( ew:EventWindow ):Boolean { 63 | 64 | const worms = ew.filter([1,5,6,9], "SWAPWORM" ); 65 | 66 | if( worms.length > 0 ) { 67 | const worm = ew.getSite( EventWindow.RANDOM(worms) ).atom; 68 | 69 | const empties = ew.filter([,2,3,10,11,15,16,13,14,21,25,26,29,30,37], "EMPTY"); 70 | ew.mutateMany( empties, Wall.CREATE ); 71 | 72 | worm.state.status = "DIRECTED"; 73 | this.state.status = "HOLDING"; 74 | 75 | return true; 76 | } 77 | 78 | return false; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/Goop.ts: -------------------------------------------------------------------------------- 1 | import { Repel } from "../../capabilities/Repel"; 2 | import { Wayfinding } from "../../capabilities/Wayfinding"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Wayfinder } from "../../mfm/Wayfinder"; 6 | import { Builder } from "./Builder"; 7 | 8 | export class Goop extends Element { 9 | static CREATE = Goop.CREATOR({ name: "GOOP", symbol: "GOP", class: Goop, color: 0x5492a2, groups: ["Goopy Stuff"] }); 10 | 11 | static WALL_GOOP = Goop.CREATOR( 12 | { name: "WALL GOOP", class: Goop, color: 0xe692ff, classifications: ["GOOP"], groups: ["Goopy Stuff"] }, 13 | { stickyType: ["WALL"] } 14 | ); 15 | static LIFE_GOOP = Goop.CREATOR( 16 | { name: "LIFE GOOP", class: Goop, color: 0x54a997, classifications: ["GOOP", "MOVABLE"], groups: ["Goopy Stuff"] }, 17 | { stickyType: ["DIRECTIONAL", "TAIL"] } 18 | ); 19 | 20 | static HC3_GOOP = Goop.CREATOR( 21 | { name: "HC3 GOOP", class: Goop, color: 0x54a997, classifications: ["GOOP", "MOVABLE"], groups: ["Goopy Stuff"] }, 22 | { stickyType: ["HC3-END"] } 23 | ); 24 | 25 | static GOOP_GRID = Builder.CREATOR( 26 | { name: "GOOP GRID", class: Builder, color: 0x121112, groups: ["Goopy Stuff"] }, 27 | { buildPath: EventWindow.DIAGONAL4WAY, atomizer: Goop.LIFE_GOOP, totalSteps: 40 } 28 | ); 29 | 30 | static ATTRACT = Repel.MAKE_ATTRACTOR(["GOOP"], [...EventWindow.LAYER2, ...EventWindow.LAYER3, ...EventWindow.LAYER4]); 31 | static AVOID = Repel.MAKE_AVOIDER(["GOOP"], [...EventWindow.LAYER1], [...EventWindow.LAYER2, ...EventWindow.LAYER3, ...EventWindow.LAYER4]); 32 | 33 | constructor(type: IElementType, state: any = {}) { 34 | super(type, state); 35 | 36 | this.init(); 37 | } 38 | 39 | init() { 40 | if (!this.state.stickyType) { 41 | this.state.stickyType = ["GOOP"]; 42 | } 43 | 44 | this.state.attractor = Repel.MAKE_ATTRACTOR( 45 | this.state.stickyType, 46 | [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40] 47 | ); 48 | 49 | this.state.avoider = Repel.MAKE_AVOIDER(this.state.stickyType, [1, 2, 3, 4, 5, 6, 7, 8], [9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]); 50 | 51 | this.state.aloneCount = 0; 52 | } 53 | 54 | behave(ew: EventWindow) { 55 | super.behave(ew); 56 | 57 | //stranded, alone, time to die. 58 | if (!ew.filterByType(EventWindow.ALLADJACENT, [...this.state.stickyType, "GOOP"]).length) { 59 | // this.state.aloneCount++; 60 | // if (this.state.aloneCount > 20) { 61 | // ew.destroy(); 62 | // return; 63 | // } 64 | } 65 | 66 | //attract, else avoid, else attract to self 67 | !this.state.attractor(ew, this) && !this.state.avoider(ew, this) && !Goop.ATTRACT(ew, this); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/MeshNet.ts: -------------------------------------------------------------------------------- 1 | import { Repel } from "../../capabilities/Repel"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EWIndex, EventWindow } from "../../mfm/EventWindow"; 4 | 5 | export class MeshNet extends Element { 6 | static CREATE = MeshNet.CREATOR({ name: "MESHNET", symbol: "MNT", class: MeshNet, color: 0xee2211, groups: ["MFM"] }); 7 | 8 | static ATTRACT = Repel.MAKE_ATTRACTOR(["MESHNET"], EventWindow.ALLADJACENT); 9 | // static AVOID = Repel.MAKE_AVOIDER(["MESHNET"], [21, 22, 23, 24, 1, 2, 3, 4], [9, 10, 11, 12, 37, 38, 39, 40]); 10 | static AVOID = Repel.MAKE_AVOIDER(["MESHNET"], [...EventWindow.LAYER1, ...EventWindow.LAYER2, ...EventWindow.LAYER3], [ ...EventWindow.LAYER4, ...EventWindow.LAYER4, ...EventWindow.LAYER4]); 11 | 12 | constructor(type: IElementType, state: any = {}) { 13 | super(type, state); 14 | 15 | this.init(); 16 | } 17 | 18 | init() { 19 | this.state.homeostasis = 5; 20 | this.state.inactivityCounter = 0; 21 | } 22 | 23 | behave(ew: EventWindow) { 24 | super.behave(ew); 25 | 26 | if( this.state.hasData || this.state.hadData ) { 27 | this.state.inactivityCounter = 0; 28 | } else { 29 | this.state.inactivityCounter++; 30 | } 31 | 32 | // const DIRECT_NEIGHBOR_SITES:EWIndex[] = [9, 10, 11, 12]; 33 | // const INDIRECT_NEIGHBOR_SITES:EWIndex[] = [21, 22, 23, 24, 37, 38, 39, 40]; 34 | // const OTHER_SITES:EWIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36]; 35 | 36 | const nextToMeshNets = ew.filterByType( [...EventWindow.LAYER1, ...EventWindow.ADJACENT8WAY], "MESHNET"); 37 | const neighborMeshNets = ew.filterByType( EventWindow.ALLADJACENT, "MESHNET"); 38 | 39 | if( neighborMeshNets.length > 1 ) { 40 | this.state.color = 0x00ff00; 41 | } else { 42 | this.state.color = 0xee2211; 43 | if( !MeshNet.ATTRACT(ew, this) ) { 44 | ew.swap(ew.random(EventWindow.ALLADJACENT) ); 45 | } 46 | return; 47 | } 48 | 49 | 50 | if( neighborMeshNets.length === 0 ) { 51 | if( !MeshNet.ATTRACT(ew, this) ) { 52 | ew.swap(ew.random(EventWindow.ALLADJACENT) ); 53 | } 54 | return; 55 | } 56 | 57 | if( nextToMeshNets.length > 0 ) { 58 | MeshNet.AVOID(ew, this); 59 | return; 60 | } 61 | 62 | const neighborData = ew.filterByType( EventWindow.ADJACENT8WAY, "RES"); 63 | 64 | if( neighborData.length > 0 ) { 65 | ew.destroy(neighborData[0]); 66 | this.state.hasData = true; 67 | this.state.hadData = true; 68 | return; 69 | } 70 | 71 | if( this.state.hasData) { 72 | this.state.color = 0xFFBB33; 73 | 74 | if( neighborMeshNets.length > 0 ) { 75 | 76 | const randomMesh = ew.getSite(EventWindow.RANDOM(neighborMeshNets)).atom; 77 | if( !randomMesh.state.hasData ) { 78 | this.state.hasData = false; 79 | this.state.hadData = true; 80 | randomMesh.state.hasData = true; 81 | } 82 | } 83 | 84 | return; 85 | } 86 | 87 | if( this.state.hadData && !this.state.hasData ) { 88 | this.state.color = 0x000; 89 | } 90 | 91 | if( this.state.inactivityCounter > 1000 ) { 92 | this.state.inactivityCounter = 0; 93 | ew.swap(ew.random(EventWindow.ALLADJACENT) ); 94 | } 95 | 96 | } 97 | 98 | 99 | } 100 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/Looper.ts: -------------------------------------------------------------------------------- 1 | import { Repel } from "../../capabilities/Repel"; 2 | import { Wayfinding } from "../../capabilities/Wayfinding"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow } from "../../mfm/EventWindow"; 5 | import { Wayfinder } from "../../mfm/Wayfinder"; 6 | import { Wall } from "../core/Wall"; 7 | import { Director } from "./Director"; 8 | 9 | export class Looper extends Element { 10 | static CREATE = Looper.CREATOR({ name: "LOOPER", symbol: "LPR", class: Looper, color: 0xff146f, groups: ["Life"] }); 11 | static WONKY = Looper.CREATOR({ 12 | name: "WONKY LOOPER", 13 | symbol: "LPR", 14 | class: Looper, 15 | color: 0xff146f, 16 | classifications: ["WONKY", "LOOPER"], 17 | groups: ["Life"], 18 | }); 19 | 20 | //SYSTEM ELEMENTS 21 | static LOOP_WALL = Wall.CREATOR({ name: "LOOP WALL", class: Wall, color: 0x661199, classifications: ["DECAYABLE", "MOVABLE"] }, { lifeSpan: 150 }); 22 | static LOOP_DIRECTOR = (heading) => { 23 | return Director.DIRECTORS_MAP.get(Wayfinder.turnRight(heading))({ classifications: ["LOOP DIRECTOR", "DECAYABLE"] }, { lifeSpan: 100 }); 24 | }; 25 | 26 | //CAPABILITIES 27 | static REPEL_DIRECTIONAL = Repel.MAKE_REPELLER(["DIRECTIONAL"], [1, 2, 3, 4, 5, 6, 7, 8], [9, 10, 11, 12, 25, 26, 27, 28]); 28 | 29 | constructor(type: IElementType, state: any = {}) { 30 | super(type, state); 31 | 32 | this.init(); 33 | } 34 | 35 | init() { 36 | this.classifyAs("DIRECTIONAL"); 37 | } 38 | 39 | makeBigTail(ew: EventWindow) { 40 | [ 41 | ...Wayfinder.getLeft(this.state.heading, true), 42 | Wayfinder.getDirectionalMove(Wayfinder.veerLeft(this.state.heading), true), 43 | Wayfinder.getDirectionalMove(Wayfinder.veerRight(this.state.heading), true), 44 | Wayfinder.getDirectionalMove(Wayfinder.turnRight(this.state.heading), true), 45 | Wayfinder.getDirectionalMove(Wayfinder.turnLeft(this.state.heading), true), 46 | ].forEach((s) => { 47 | if (ew.is(s, "EMPTY")) ew.replace(s, Looper.LOOP_WALL()); 48 | }); 49 | } 50 | 51 | behave(ew: EventWindow) { 52 | super.behave(ew); 53 | 54 | Looper.REPEL_DIRECTIONAL(ew, this); 55 | 56 | if (ew.selfIs("WONKY")) { 57 | this.behaveAsWonky(ew); 58 | } else { 59 | this.behaveAsLooper(ew); 60 | } 61 | } 62 | 63 | behaveAsLooper(ew: EventWindow) { 64 | if (!this.state.heading) { 65 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM(Wayfinder.DIRECTIONS_PRIMARY)); 66 | this.state.moves = 0; 67 | } else { 68 | const tails = this.state.moves % 2 !== 0; 69 | 70 | let leavingAtom; 71 | 72 | this.makeBigTail(ew); 73 | 74 | if (tails) { 75 | leavingAtom = Looper.LOOP_DIRECTOR(this.state.heading); 76 | } 77 | Wayfinding.MOVE_DIRECTIONALLY(ew, this, ["LOOP WALL", "LOOP DIRECTOR", "EMPTY"], leavingAtom); 78 | this.state.moves++; 79 | 80 | if (this.state.moves % 8 === 0) { 81 | Wayfinding.VEER_RIGHT(this); 82 | } 83 | } 84 | } 85 | 86 | behaveAsWonky(ew: EventWindow) { 87 | if (!this.state.heading) { 88 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM(Wayfinder.DIRECTIONS)); 89 | this.state.moves = 0; 90 | } else { 91 | Wayfinding.MOVE_DIRECTIONALLY( 92 | ew, 93 | this, 94 | ["DECAY WALL", "EMPTY"], 95 | Director.DIRECTORS_MAP.get(Wayfinder.turnRight(this.state.heading))({ classifications: ["DECAY WALL", "DECAYABLE"] }, { lifeSpan: 50 }) 96 | ); 97 | 98 | if (this.state.age % 5 === 0) { 99 | Wayfinding.SLIGHT_RIGHT(this); 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/Element.ts: -------------------------------------------------------------------------------- 1 | import { ICapability, CapabilityRegistry } from "./Capability"; 2 | import { ElementRegistry } from "./ElementRegistry"; 3 | import { EventWindow } from "./EventWindow"; 4 | 5 | export interface IElementType { 6 | name: string; 7 | class: any; 8 | symbol?: string; 9 | color?: number; 10 | CREATE?: Function; 11 | classifications?: string[]; 12 | groups?: string[]; 13 | } 14 | 15 | export interface IElementTypePartial { 16 | symbol?: string; 17 | name?: string; 18 | class?: any; 19 | color?: number; 20 | CREATE?: Function; 21 | classifications?: string[]; 22 | groups?: string[]; 23 | } 24 | 25 | export interface IElement { 26 | TYPE: IElementType; 27 | behave: Function; 28 | state?: any; 29 | } 30 | 31 | export abstract class Element implements IElement { 32 | //creator creates an atom of element creation function with the ability to override BASE_TYPE element properties 33 | static CREATOR(typeDefinition: IElementType, state: any = {}) { 34 | let type: IElementType = { ...typeDefinition }; 35 | 36 | //the create function can also take in IElementType properties to override on the fly. 37 | const CREATE = (_typeDefinition: IElementTypePartial = {}, _state: any = {}): Element => { 38 | const newtype = { ...type, ..._typeDefinition }; 39 | const newstate = { ...state, ..._state }; 40 | const atom = newstate ? new newtype.class(newtype, newstate) : new newtype.class(newtype); 41 | return atom; 42 | }; 43 | 44 | type.CREATE = CREATE; 45 | 46 | ElementRegistry.registerType({ ...type, CREATE }); 47 | 48 | return CREATE; 49 | } 50 | 51 | static UID = (length: number = 8): string => { 52 | return Math.random().toString(36).slice(-length); 53 | } 54 | 55 | TYPE: IElementType; 56 | state: any; 57 | classes: Set; 58 | 59 | constructor(_type: IElementType, initialState?: any) { 60 | this.TYPE = _type; 61 | this.classes = new Set(); 62 | this.initializeState(initialState ?? undefined); 63 | this.classifyAs(this.TYPE); 64 | 65 | if (this.TYPE.classifications) { 66 | this.TYPE.classifications.forEach((c) => { 67 | this.classifyAs(c); 68 | }); 69 | } 70 | } 71 | 72 | initializeState(state?: any) { 73 | this.state = {}; 74 | this.wr("age", state.age ?? 0); 75 | this.wr("color", this.TYPE.color ?? 0xffffff); 76 | this.wr("uid", Element.UID()); 77 | this.state = state ? { ...this.state, ...state } : { ...this.state }; 78 | } 79 | 80 | behave(ew: EventWindow): void { 81 | this.state.age++; 82 | } 83 | 84 | rd(key: string): any { 85 | return this.state[key]; 86 | } 87 | 88 | wr(key: string, value: any): any { 89 | this.state[key] = value; 90 | } 91 | 92 | classifyAs(c: string | IElementType): void { 93 | const t: string = typeof c === "string" ? c : c.name; 94 | this.classes.add(t.toUpperCase()); 95 | } 96 | 97 | declassify(c: string | IElementType): void { 98 | if(!c) return; 99 | const t: string = typeof c === "string" ? c : c?.name; 100 | this.classes.delete(t.toUpperCase()); 101 | } 102 | 103 | is(type: string | IElementType | string[] | IElementType[]): boolean { 104 | if (Array.isArray(type)) { 105 | return type.some((ty) => { 106 | const t: string = typeof ty === "string" ? ty : ty.name; 107 | return this.classes.has(t.toUpperCase()); 108 | }); 109 | } 110 | const t: string = typeof type === "string" ? type : type.name; 111 | return this.classes.has(t.toUpperCase()); 112 | } 113 | 114 | isCore(type: string | IElementType): boolean { 115 | const t: string = typeof type === "string" ? type : type.name; 116 | return t.toUpperCase() === this.TYPE.name.toUpperCase(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/EventWindow.d.ts: -------------------------------------------------------------------------------- 1 | import { Element, IElementType } from './Element'; 2 | import { Site } from './Site'; 3 | import { Tile } from './Tile'; 4 | import { ICoordinate } from './TileCoordinate'; 5 | 6 | export type EWIndexes = EWIndex[]; 7 | export type EWIndex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40; 8 | export declare class EventWindow { 9 | static WINDOW_OFFSETS: Array; 10 | static RANDOM: (subset?: EWIndex[]) => EWIndex; 11 | static oneIn(n: number): boolean; 12 | static ORIGIN: EWIndex[]; 13 | static W: EWIndex[]; 14 | static N: EWIndex[]; 15 | static S: EWIndex[]; 16 | static E: EWIndex[]; 17 | static NW: EWIndex[]; 18 | static SW: EWIndex[]; 19 | static NE: EWIndex[]; 20 | static SE: EWIndex[]; 21 | static ADJACENT4WAY: EWIndex[]; 22 | static DIAGONAL4WAY: EWIndex[]; 23 | static ADJACENT8WAY: EWIndex[]; 24 | static LAYER1: EWIndex[]; 25 | static LAYER2: EWIndex[]; 26 | static LAYER3: EWIndex[]; 27 | static LAYER4: EWIndex[]; 28 | static W_LINE: EWIndex[]; 29 | static N_LINE: EWIndex[]; 30 | static S_LINE: EWIndex[]; 31 | static E_LINE: EWIndex[]; 32 | static NW_LINE: EWIndex[]; 33 | static SW_LINE: EWIndex[]; 34 | static NE_LINE: EWIndex[]; 35 | static SE_LINE: EWIndex[]; 36 | static EQUATOR: EWIndex[]; 37 | static PRIME_MERIDIAN: EWIndex[]; 38 | static W_QUADRANT: EWIndex[]; 39 | static N_QUADRANT: EWIndex[]; 40 | static S_QUADRANT: EWIndex[]; 41 | static E_QUADRANT: EWIndex[]; 42 | static NW_QUADRANT: EWIndex[]; 43 | static SW_QUADRANT: EWIndex[]; 44 | static NE_QUADRANT: EWIndex[]; 45 | static SE_QUADRANT: EWIndex[]; 46 | static W_HEMISPHERE: EWIndex[]; 47 | static N_HEMISPHERE: EWIndex[]; 48 | static S_HEMISPHERE: EWIndex[]; 49 | static E_HEMISPHERE: EWIndex[]; 50 | static ALL: EWIndex[]; 51 | static ALLADJACENT: EWIndex[]; 52 | static OPPOSITES: EWIndex[]; 53 | static X_REFLECTION: EWIndex[]; 54 | static Y_REFLECTION: EWIndex[]; 55 | static XY_REFLECTION: EWIndex[]; 56 | static SUBSETS: Map; 57 | origin: Site; 58 | window: Site[]; 59 | offsetOrigin: EWIndex; 60 | constructor(_tile: Tile, _origin: ICoordinate); 61 | private makeWindow; 62 | private offsetFromOrigin; 63 | getSite(index: EWIndex): Site; 64 | getSites(indexes: EWIndex[]): Site[]; 65 | filter(subset: EWIndex[], type?: string | string[] | IElementType | IElementType[], oneRandom?: boolean): EWIndex[]; 66 | randomOfType(siteSet: EWIndex[], type: string | string[] | IElementType | IElementType[]): EWIndex; 67 | random(siteSet: EWIndex[]): EWIndex; 68 | filterByType(indexes: EWIndex[], type: string | string[] | IElementType | IElementType[]): EWIndex[]; 69 | getSubsetIndexes(subset: EWIndex[]): EWIndex[]; 70 | exists(site: EWIndex): boolean; 71 | is(site: EWIndex | Site, type: string | string[] | IElementType | IElementType[]): boolean; 72 | any(siteSet: EWIndex | EWIndex[], type: IElementType | string): boolean; 73 | all(siteSet: EWIndex | EWIndex[], type: IElementType | string): boolean; 74 | howMany(siteSet: EWIndex[], type: IElementType | string): number; 75 | selfIs(type: string | string[] | IElementType | IElementType[]): boolean; 76 | move(toIndex: EWIndex, leavingElement?: Element, fromIndex?: EWIndex): boolean; 77 | swap(toIndex: EWIndex, fromIndex?: EWIndex): boolean; 78 | replace(targetIndex: EWIndex, atom: Element): boolean; 79 | mutate(targetIndex: EWIndex, atomCreator: Function, creatorParams?: any[]): boolean; 80 | mutateMany(siteSet: EWIndex[], atomCreator: Function, creatorParams?: any[]): void; 81 | destroy(targetIndex?: EWIndex): boolean; 82 | shuffleSites(siteSet: EWIndex[]): EWIndex[]; 83 | } 84 | -------------------------------------------------------------------------------- /mfm-js/src/mfm/Tile.ts: -------------------------------------------------------------------------------- 1 | import { Empty } from "../elements/core/Empty"; 2 | import { IElementType } from "./Element"; 3 | import { EventWindow } from "./EventWindow"; 4 | import { Site } from "./Site"; 5 | import { ICoordinate, TileCoordinate } from "./TileCoordinate"; 6 | 7 | export class Tile { 8 | width: number; 9 | height: number; 10 | sites: Map; 11 | sitesArray: Site[]; 12 | 13 | rands: Array; 14 | cur: number = 0; 15 | 16 | isRunning: boolean = false; 17 | 18 | constructor(_width: number = 1, _height: number = 1) { 19 | this.width = _width; 20 | this.height = _height; 21 | this.create(); 22 | 23 | this.seedRandoms(); 24 | } 25 | 26 | seedRandoms() { 27 | this.rands = new Array(); 28 | var array = new Uint32Array(16000); 29 | /* @ts-ignore */ 30 | crypto.getRandomValues(array); 31 | 32 | for (var i = 0; i < array.length; i++) { 33 | this.rands.push(array[i] % (this.width * this.height)); 34 | } 35 | } 36 | 37 | getSiteByCoordinate(c: ICoordinate): Site { 38 | return this.sites.get(TileCoordinate.CoordinateToId(c)); 39 | } 40 | 41 | getSiteById(id: string): Site { 42 | return this.sites.get(id); 43 | } 44 | 45 | getRandomSite(): Site { 46 | return this.sites.get(`${~~(Math.random() * this.height)}:${~~(Math.random() * this.width)}`); 47 | } 48 | 49 | getRandomSiteSeeded(): Site { 50 | if (this.cur > 15998) { 51 | this.seedRandoms(); 52 | this.cur = 0; 53 | } 54 | this.cur++; 55 | return this.sitesArray[this.rands[this.cur]]; 56 | } 57 | 58 | getRandomSiteInRange(range: number[]) { 59 | return this.sites.get(`${~~(Math.random() * (range[1] - range[0])) + range[0]}:${~~(Math.random() * (range[3] - range[2])) + range[2]}`); 60 | } 61 | 62 | getRandomSiteInRangeSeeded(range: number[]) { 63 | if (this.cur > 15997) { 64 | this.seedRandoms(); 65 | this.cur = 0; 66 | } 67 | this.cur++; 68 | 69 | return this.sites.get(`${(this.rands[this.cur] % (range[1] - range[0])) + range[0]}:${(this.rands[this.cur + 1] % (range[3] - range[2])) + range[2]}`); 70 | } 71 | 72 | create() { 73 | this.sites = new Map(); 74 | 75 | for (let i: number = 0; i < this.width; i++) { 76 | //across columns (x) 77 | for (let j: number = 0; j < this.height; j++) { 78 | //down rows (y) 79 | const tc: TileCoordinate = TileCoordinate.fromId(`${j}:${i}`); 80 | this.sites.set(tc.id, new Site(tc)); 81 | } 82 | } 83 | 84 | //pregenerate each site's EventWindow and assign it into the site 85 | this.sites.forEach((s) => { 86 | s.ew = new EventWindow(this, s.location.coordinate); 87 | }); 88 | 89 | this.sitesArray = Array.from(this.sites.values()); 90 | } 91 | 92 | add(atomizer, x = 0, y = 0) { 93 | if (x >= 0 && y >= 0 && x < this.width && y < this.height) { 94 | const atom = atomizer(); 95 | let site: Site = this.getSiteByCoordinate({ x, y }); 96 | if (site) { 97 | site.atom = atom; 98 | } 99 | } 100 | } 101 | 102 | exportAtoms() { 103 | const atomMap = {}; 104 | Array.from(this.sites.values()) 105 | .filter((s) => !s.atom.is("EMPTY")) 106 | .forEach((s) => { 107 | if (atomMap[s.atom.TYPE.name]) { 108 | atomMap[s.atom.TYPE.name] += `-${s.location.coordinate.x}x${s.location.coordinate.y}`; 109 | } else { 110 | atomMap[s.atom.TYPE.name] = `${s.atom.TYPE.name}`; 111 | atomMap[s.atom.TYPE.name] += `-${s.location.coordinate.x}x${s.location.coordinate.y}`; 112 | } 113 | }); 114 | 115 | console.log(atomMap); 116 | return Object.values(atomMap); 117 | } 118 | 119 | clear(t: string = undefined) { 120 | if (t) { 121 | this.sites.forEach((s) => { 122 | if (s.atom.is(t)) s.mutate(Empty.CREATE()); 123 | }); 124 | } else { 125 | this.sites.forEach((s) => { 126 | s.mutate(Empty.CREATE()); 127 | }); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/TileConnector.js: -------------------------------------------------------------------------------- 1 | export const TileConnections = Vue.component("tile-connections", { 2 | data: function () { 3 | return { 4 | peer: undefined, 5 | tileId: undefined, 6 | availableConnections: ["nw", "ne", "e", "se", "sw", "w"], 7 | connections: {}, 8 | connectionMessages: {} 9 | } 10 | }, 11 | computed: { 12 | west: () => { return this.connections["w"] ? this.peer.connections[this.connections["w"]] : undefined }, 13 | east: () => { return this.connections["w"] ? this.peer.connections[this.connections["e"]] : undefined }, 14 | northwest: () => { return this.connections["w"] ? this.peer.connections[this.connections["nw"]] : undefined }, 15 | northeast: () => { return this.connections["w"] ? this.peer.connections[this.connections["ne"]] : undefined }, 16 | southwest: () => { return this.connections["w"] ? this.peer.connections[this.connections["sw"]] : undefined }, 17 | southeast: () => { return this.connections["w"] ? this.peer.connections[this.connections["se"]] : undefined }, 18 | }, 19 | created() { 20 | this.peer = new Peer({ key: 'lwjd5qra8257b9' }); 21 | 22 | this.peer.on('open', (id) => { 23 | 24 | //connected to p2p server, given an ID to share 25 | console.log('My peer ID is: ' + id); 26 | this.$emit("ontileid", id); 27 | this.tileId = id; 28 | 29 | 30 | //set up peer connection callback 31 | this.peer.on('connection', (dataConnection) => { 32 | 33 | console.log("someone is trying to connect to me!", dataConnection.label, dataConnection); 34 | 35 | //do I already have a direction that way? 36 | if (!this.connections[dataConnection.label]) { 37 | 38 | const dir = dataConnection.label; 39 | console.log("setting connection to ", dir); 40 | this.connections[dir] = dataConnection.peer; 41 | this.$emit("connection", dir); 42 | 43 | dataConnection.on('data', (data) => { 44 | this.handleMessage(data); 45 | }); 46 | 47 | dataConnection.on('close', () => { 48 | console.log("disconnected", this.peer.connections) 49 | }); 50 | 51 | this.peer.connect(dataConnection.peer, { label: this.oppositeDirection(dir) }); 52 | 53 | } else { 54 | console.log("Already have a connection in that direction") 55 | } 56 | }); 57 | }); 58 | }, 59 | mounted() { 60 | 61 | }, 62 | methods: { 63 | directionConnection(dir) { 64 | return this.connections[dir] ? this.peer.connections[this.connections[dir]] : undefined 65 | }, 66 | oppositeDirection(dir) { 67 | return { 68 | "w": "e", 69 | "e": "w", 70 | "se": "nw", 71 | "nw": "se", 72 | "ne": "sw", 73 | "sw": "ne" 74 | }[dir]; 75 | }, 76 | connect(id, dir) { 77 | 78 | 79 | if (!this.connections.hasOwnProperty(dir)) { 80 | dir = this.oppositeDirection(dir); 81 | 82 | const curConn = this.peer.connect(id, { label: dir }); 83 | this.connections[this.oppositeDirection(dir)] = id; 84 | this.$emit("connection", this.oppositeDirection(dir)); 85 | 86 | curConn.on('data', (data) => { 87 | this.handleMessage(data); 88 | }); 89 | 90 | curConn.on('close', () => { 91 | console.log("disconnected...", this.peer.connections) 92 | }); 93 | 94 | } else { 95 | console.log("no open spots on the grid"); 96 | } 97 | 98 | }, 99 | send(to, data) { 100 | const conn = this.directionConnection(to)[0]; 101 | console.log(conn); 102 | if (conn) { 103 | console.log("sending message to", to, data); 104 | conn.send({ from: this.oppositeDirection(to), message: data }); 105 | } 106 | }, 107 | handleMessage(data) { 108 | 109 | if (!this.connectionMessages[data.from]) { 110 | this.connectionMessages[data.from] = []; 111 | } 112 | 113 | //store the message 114 | this.connectionMessages[data.from].push(data.message); 115 | 116 | //do something with the message 117 | console.log("handling data", data); 118 | this.$emit("onmessage", data); 119 | 120 | 121 | //don't accumulate too many messages 122 | if (this.connectionMessages[data.from].length > 10) { 123 | this.connectionMessages[data.from].unshift(); 124 | } 125 | } 126 | } 127 | }); 128 | -------------------------------------------------------------------------------- /mfm-js/src/elements/core/SwapLine.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow } from "../../mfm/EventWindow"; 4 | import { Wayfinder } from "../../mfm/Wayfinder"; 5 | 6 | export class SwapLine extends Element { 7 | static CREATE = SwapLine.CREATOR({ name: "SWAPLINE", symbol: "SWL", class: SwapLine, color: 0x99aa22, groups: ["Swaplines"] }); 8 | 9 | static NORTH = SwapLine.CREATOR({ name: "SWAPLINE NORTH", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "N"}); 10 | static SOUTH = SwapLine.CREATOR({ name: "SWAPLINE SOUTH", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "S"}); 11 | static EAST = SwapLine.CREATOR({ name: "SWAPLINE EAST", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "E"}); 12 | static WEST = SwapLine.CREATOR({ name: "SWAPLINE WEST", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "W"}); 13 | static NW = SwapLine.CREATOR({ name: "SWAPLINE NW", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "NW"}); 14 | static NE = SwapLine.CREATOR({ name: "SWAPLINE NE", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "NE"}); 15 | static SW = SwapLine.CREATOR({ name: "SWAPLINE SW", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "SW"}); 16 | static SE = SwapLine.CREATOR({ name: "SWAPLINE SE", symbol: "SWL", class: SwapLine, color: 0x99aa22, classifications: ["SWAPLINE"], groups: ["Swaplines"] }, { heading: "SE"}); 17 | 18 | 19 | constructor(type: IElementType, state: any = {}) { 20 | super(type, state); 21 | 22 | this.init(); 23 | } 24 | 25 | init() { 26 | 27 | if( !this.state.heading ) { 28 | this.state.heading = "E"; 29 | } 30 | this.state.phase = "BUILD"; 31 | 32 | } 33 | 34 | behave(ew: EventWindow) { 35 | super.behave(ew); 36 | 37 | 38 | const left = Wayfinder.directionToIndex( Wayfinder.turnLeft(this.state.heading) ); 39 | const right = Wayfinder.directionToIndex( Wayfinder.turnRight( this.state.heading) ); 40 | 41 | const fronts = Wayfinder.getInFront(this.state.heading); 42 | if( ew.is( fronts[0], "SWAPLINE") ) { 43 | ew.destroy(); 44 | } 45 | 46 | 47 | switch( this.state.phase ) { 48 | 49 | case "BUILD": 50 | 51 | const leftFront = Wayfinder.getInFront( Wayfinder.veerLeft(this.state.heading)); 52 | const rightFront = Wayfinder.getInFront( Wayfinder.veerRight( this.state.heading)); 53 | 54 | if( ew.any( [...leftFront, ...rightFront], "SWAPLINE") ) { 55 | return; 56 | } 57 | 58 | if( ew.is(left, "EMPTY")) { 59 | ew.mutate( left, this.TYPE.CREATE ); 60 | } else { 61 | this.state.color = 0xcccc00; 62 | } 63 | 64 | if( ew.is(right, "EMPTY")) { 65 | ew.mutate( right, this.TYPE.CREATE ); 66 | this.state.color = 0xffff00; 67 | } 68 | 69 | if( !ew.any([left, right], "EMPTY") ) { 70 | this.state.phase = "WAIT_TO_SWAP"; 71 | return; 72 | } 73 | 74 | ew.destroy(); 75 | 76 | break; 77 | case "WAIT_TO_SWAP": 78 | this.state.color = 0x666666; 79 | if( ew.getSite(left)?.atom.state.phase !== "BUILD" && ew.getSite(right)?.atom.state.phase !== "BUILD") { 80 | this.state.phase = "SWAP"; 81 | } 82 | 83 | break; 84 | case "SWAP": 85 | this.state.color = 0xff6600; 86 | 87 | 88 | const leftBack = Wayfinder.getInFront( Wayfinder.turnLeft( Wayfinder.veerLeft(this.state.heading) ) ).slice(0,2); 89 | const rightBack = Wayfinder.getInFront( Wayfinder.turnRight( Wayfinder.veerRight(this.state.heading) ) ).slice(0,2); 90 | 91 | const shouldSwap:Boolean = !ew.any([...fronts, ...leftBack, ...rightBack], "SWAPLINE"); 92 | 93 | 94 | if( shouldSwap ) { 95 | const swapped = Wayfinding.SWAP_DIRECTIONALLY(ew, this, "ANY"); 96 | } 97 | 98 | if( !ew.getSite(fronts[0]) ) { 99 | this.state.phase = "DIE"; 100 | } 101 | 102 | break; 103 | case "DIE": 104 | ew.destroy(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /mfm-js/dist/src/mfm/Wayfinder.d.ts: -------------------------------------------------------------------------------- 1 | import { EWIndex } from './EventWindow'; 2 | 3 | export type DirectionMap = Map; 4 | export type Direction = "N" | "E" | "S" | "W" | "NW" | "NE" | "SW" | "SE" | "NNW" | "WNW" | "SSW" | "WSW" | "NNE" | "ENE" | "SSE" | "ESE"; 5 | export declare class Wayfinder { 6 | static RANDOM: (subset?: Direction[]) => Direction; 7 | static NORTH: Direction; 8 | static SOUTH: Direction; 9 | static EAST: Direction; 10 | static WEST: Direction; 11 | static NORTHWEST: Direction; 12 | static NORTHEAST: Direction; 13 | static SOUTHWEST: Direction; 14 | static SOUTHEAST: Direction; 15 | static NORTHNORTHWEST: Direction; 16 | static WESTNORTHWEST: Direction; 17 | static NORTHNORTHEAST: Direction; 18 | static EASTNORTHEAST: Direction; 19 | static SOUTHSOUTHWEST: Direction; 20 | static WESTWOUTHWEST: Direction; 21 | static SOUTHSOUTHEAST: Direction; 22 | static EASTSOUTHEAST: Direction; 23 | static DIRECTIONS_PRIMARY: Direction[]; 24 | static DIRECTIONS_SECONDARY: Direction[]; 25 | static DIRECTIONS_TERTIARY: Direction[]; 26 | static DIRECTIONS: Direction[]; 27 | static W_LINE: EWIndex[]; 28 | static N_LINE: EWIndex[]; 29 | static S_LINE: EWIndex[]; 30 | static E_LINE: EWIndex[]; 31 | static NW_LINE: EWIndex[]; 32 | static SW_LINE: EWIndex[]; 33 | static NE_LINE: EWIndex[]; 34 | static SE_LINE: EWIndex[]; 35 | static WNW_LINE: EWIndex[]; 36 | static NNW_LINE: EWIndex[]; 37 | static NNE_LINE: EWIndex[]; 38 | static ENE_LINE: EWIndex[]; 39 | static WSW_LINE: EWIndex[]; 40 | static SSW_LINE: EWIndex[]; 41 | static SSE_LINE: EWIndex[]; 42 | static ESE_LINE: EWIndex[]; 43 | static W_QUADRANT: EWIndex[]; 44 | static N_QUADRANT: EWIndex[]; 45 | static S_QUADRANT: EWIndex[]; 46 | static E_QUADRANT: EWIndex[]; 47 | static NW_QUADRANT: EWIndex[]; 48 | static SW_QUADRANT: EWIndex[]; 49 | static NE_QUADRANT: EWIndex[]; 50 | static SE_QUADRANT: EWIndex[]; 51 | static NNW_QUADRANT: EWIndex[]; 52 | static WNW_QUADRANT: EWIndex[]; 53 | static SSW_QUADRANT: EWIndex[]; 54 | static WSW_QUADRANT: EWIndex[]; 55 | static NNE_QUADRANT: EWIndex[]; 56 | static ENE_QUADRANT: EWIndex[]; 57 | static SSE_QUADRANT: EWIndex[]; 58 | static ESE_QUADRANT: EWIndex[]; 59 | static DIRMAP_CLOCKWISE_PRIMARY: DirectionMap; 60 | static DIRMAP_COUNTERCLOCKWISE_PRIMARY: DirectionMap; 61 | static DIRMAP_CLOCKWISE_SECONDARY: DirectionMap; 62 | static DIRMAP_COUNTERCLOCKWISE_SECONDARY: DirectionMap; 63 | static DIRMAP_CLOCKWISE_ALL: DirectionMap; 64 | static DIRMAP_COUNTERCLOCKWISE_ALL: DirectionMap; 65 | static DIRMAP_REVERSE: DirectionMap; 66 | static DIRECTIONS_INDEX_MAP: Map; 67 | static DIRECTIONS_FRONT_MAP: Map; 68 | static DIRECTIONS_FRONT_QUADRANT_MAP: Map; 69 | static DIRECTIONS_BEHIND_MAP: Map; 70 | static DIRECTIONS_BEHIND_QUADRANT_MAP: Map; 71 | static DIRECTIONS_LEFT_MAP: Map; 72 | static DIRECTIONS_LEFT_QUADRANT_MAP: Map; 73 | static DIRECTIONS_RIGHT_MAP: Map; 74 | static DIRECTIONS_RIGHT_QUADRANT_MAP: Map; 75 | static INDEX_DIRECTION_MAP: Map; 76 | static directionToIndex(dir: Direction, useSecondaryDirections?: boolean): EWIndex; 77 | static indexToDirection(index: EWIndex, useSecondaryDirections?: boolean): Direction; 78 | static getDirectionalMove(dir: Direction, useSecondaryDirections?: boolean): EWIndex; 79 | static nextDirection(dir: Direction, directionMap: DirectionMap): Direction; 80 | static reverse(dir: Direction): Direction; 81 | static turnLeft(dir: Direction): Direction; 82 | static veerLeft(dir: Direction): Direction; 83 | static slightLeft(dir: Direction): Direction; 84 | static turnRight(dir: Direction): Direction; 85 | static veerRight(dir: Direction): Direction; 86 | static slightRight(dir: Direction): Direction; 87 | static getInFront(dir: Direction, getQuadrant?: boolean): EWIndex[]; 88 | static getBehind(dir: Direction, getQuadrant?: boolean): EWIndex[]; 89 | static getLeft(dir: Direction, getQuadrant?: boolean): EWIndex[]; 90 | static getRight(dir: Direction, getQuadrant?: boolean): EWIndex[]; 91 | static slightRandom(dir: Direction): Direction; 92 | static veerRandom(dir: Direction): Direction; 93 | static turnRandom(dir: Direction): Direction; 94 | static mapPath(path: Direction[], startingLocation?: EWIndex): EWIndex[]; 95 | static getDestinationFromPath(path: Direction[], startingLocation?: EWIndex): EWIndex; 96 | } 97 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Wayfinding.ts: -------------------------------------------------------------------------------- 1 | import { Empty } from "../elements/core/Empty"; 2 | import { Element } from "../mfm/Element"; 3 | import { EventWindow, EWIndex } from "../mfm/EventWindow"; 4 | import { Direction, Wayfinder } from "../mfm/Wayfinder"; 5 | 6 | export class Wayfinding { 7 | static NAME: string = "WAYFINDING"; 8 | 9 | static REVERSE(self: Element) { 10 | const { heading } = self.state; 11 | if (heading) { 12 | self.wr("heading", Wayfinder.reverse(heading)); 13 | } 14 | } 15 | static SLIGHT_LEFT(self: Element) { 16 | const { heading } = self.state; 17 | if (heading) { 18 | self.wr("heading", Wayfinder.slightLeft(heading)); 19 | } 20 | } 21 | static VEER_LEFT(self: Element) { 22 | const { heading } = self.state; 23 | if (heading) { 24 | self.wr("heading", Wayfinder.veerLeft(heading)); 25 | } 26 | } 27 | static TURN_LEFT(self: Element) { 28 | const { heading } = self.state; 29 | if (heading) { 30 | self.wr("heading", Wayfinder.turnLeft(heading)); 31 | } 32 | } 33 | static SLIGHT_RIGHT(self: Element) { 34 | const { heading } = self.state; 35 | if (heading) { 36 | self.wr("heading", Wayfinder.slightRight(heading)); 37 | } 38 | } 39 | static VEER_RIGHT(self: Element) { 40 | const { heading } = self.state; 41 | if (heading) { 42 | self.wr("heading", Wayfinder.veerRight(heading)); 43 | } 44 | } 45 | static TURN_RIGHT(self: Element) { 46 | const { heading } = self.state; 47 | if (heading) { 48 | self.wr("heading", Wayfinder.turnRight(heading)); 49 | } 50 | } 51 | static SLIGHT_RANDOMLY(self: Element) { 52 | EventWindow.oneIn(2) ? this.SLIGHT_LEFT(self) : this.SLIGHT_RIGHT(self); 53 | } 54 | static VEER_RANDOMLY(self: Element) { 55 | EventWindow.oneIn(2) ? this.VEER_LEFT(self) : this.VEER_RIGHT(self); 56 | } 57 | static TURN_RANDOMLY(self: Element) { 58 | EventWindow.oneIn(2) ? this.TURN_RIGHT(self) : this.TURN_RIGHT(self); 59 | } 60 | 61 | static SET_DIRECTION(self: Element, d: Direction) { 62 | self.wr("heading", d); 63 | } 64 | 65 | static MOVE_IN_DIRECTION( 66 | ew: EventWindow, 67 | self: Element, 68 | direction: Direction | Direction[], 69 | types: string | string[] = "EMPTY", 70 | leavingAtom: Element = Empty.CREATE(), 71 | ): boolean { 72 | 73 | if (typeof direction === "string") { 74 | direction = [direction]; 75 | } 76 | 77 | const possibleMoves = direction.map((d) => Wayfinder.getDirectionalMove(d, true)); 78 | 79 | if(!possibleMoves.length) { 80 | return false; 81 | } 82 | 83 | for (const travelTo of possibleMoves) { 84 | if (types === "ANY" || ew.is(travelTo, types)) { 85 | const moved = ew.move(travelTo, leavingAtom); 86 | if( moved ) self.wr('location', travelTo); 87 | return moved; 88 | } 89 | } 90 | } 91 | 92 | static MOVE_DIRECTIONALLY(ew: EventWindow, self: Element, types: string | string[] = "EMPTY", leavingAtom: Element = Empty.CREATE()): boolean { 93 | const { heading } = self.state; 94 | if (heading) { 95 | const travelTo: EWIndex = Wayfinder.getDirectionalMove(heading, true); 96 | 97 | if (types === "ANY" || ew.is(travelTo, types)) { 98 | const moved = ew.move(travelTo, leavingAtom); 99 | if( moved ) self.wr('location', travelTo); 100 | return moved; 101 | } 102 | } 103 | 104 | return false; 105 | } 106 | 107 | static SWAP_IN_DIRECTION( 108 | ew: EventWindow, 109 | self: Element, 110 | direction: Direction | Direction[], 111 | types: string | string[] = "EMPTY", 112 | ): boolean { 113 | const { heading } = self.state; 114 | 115 | if (typeof direction === "string") { 116 | direction = [direction]; 117 | } 118 | 119 | const possibleMoves = direction.map((d) => Wayfinder.getDirectionalMove(d, true)); 120 | 121 | for (const travelTo of possibleMoves) { 122 | if (types === "ANY" || ew.is(travelTo, types)) { 123 | const swapped = ew.swap(travelTo); 124 | if( swapped ) self.wr('location', travelTo); 125 | return swapped; 126 | } 127 | } 128 | } 129 | 130 | static SWAP_DIRECTIONALLY(ew: EventWindow, self: Element, types: string | string[] = "EMPTY"): boolean { 131 | const { heading } = self.state; 132 | if (heading) { 133 | const travelTo: EWIndex = Wayfinder.getDirectionalMove(heading, true); 134 | 135 | if (types === "ANY" || ew.is(travelTo, types)) { 136 | const swapped = ew.swap(travelTo); 137 | if( swapped ) self.wr('location', travelTo); 138 | return swapped; 139 | } 140 | } 141 | 142 | return false; 143 | } 144 | static DIRECT(ew: EventWindow, s: EWIndex, heading: Direction): boolean { 145 | const { atom } = ew.getSite(s); 146 | if (atom && atom.state.heading) { 147 | atom.state.heading = heading; 148 | return true; 149 | } 150 | return false; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /mfm-js/src/capabilities/Repel.ts: -------------------------------------------------------------------------------- 1 | import { Element } from "../mfm/Element"; 2 | import { EventWindow, EWIndex } from "../mfm/EventWindow"; 3 | import { Wayfinder } from "../mfm/Wayfinder"; 4 | 5 | export class Repel { 6 | static NAME: string = "REPEL"; 7 | 8 | static CREATE(repelType: string, repelSites: EWIndex[], escapeType: string = "EMPTY", escapeSites: EWIndex[]) { 9 | return (ew: EventWindow): boolean => { 10 | let swapped = false; 11 | if (ew.any(repelSites, repelType)) { 12 | const escapes = ew.filterByType(escapeSites, escapeType); 13 | if (escapes.length) swapped = ew.swap(ew.random(escapes)); 14 | } 15 | return swapped; 16 | }; 17 | } 18 | 19 | static FAR_NORTH(ew: EventWindow, repelType: string | string[], escapeType: string | string[] = "EMPTY"): boolean { 20 | if (EventWindow.oneIn(5) && ew.is(3, repelType) && ew.is(10, escapeType)) { 21 | ew.swap(10); 22 | } 23 | return true; 24 | } 25 | 26 | static MAKE_REPELLER( 27 | _repelTypes: string | string[], 28 | fromIndexes: EWIndex[] = [1, 2, 3, 4, 5, 6, 7, 8], 29 | toIndexes: EWIndex[] = [37, 38, 39, 40, 25, 26, 27, 28] 30 | ) { 31 | return (ew: EventWindow, self: Element): boolean => { 32 | const repelTypes = self.state.repelTypes ?? _repelTypes; 33 | 34 | if (fromIndexes.length > toIndexes.length) { 35 | throw new Error("fromSet must be less than or equal to length of toSet"); 36 | } 37 | 38 | const repellers: EWIndex[] = ew.filter(fromIndexes, repelTypes); 39 | const emptyDests: EWIndex[] = ew.filter(toIndexes, "EMPTY"); 40 | 41 | if (repellers.length && emptyDests.length) { 42 | const repelMap = Object.fromEntries( 43 | fromIndexes.map((fi, i) => { 44 | return [fi, toIndexes[i]]; 45 | }) 46 | ); 47 | 48 | let moved = false; 49 | 50 | repellers.forEach((target) => { 51 | if (!moved) { 52 | const toSite: EWIndex = repelMap[target]; 53 | //try to repel in the opposing direction 54 | if (emptyDests.includes(toSite)) { 55 | moved = ew.move(toSite, undefined, target); 56 | } else { 57 | //otherwise just repel it anywhere available in the toSet! 58 | const empty = EventWindow.RANDOM(emptyDests); 59 | moved = ew.move(empty, undefined, target); 60 | } 61 | } 62 | }); 63 | 64 | return moved; 65 | } 66 | 67 | return false; 68 | }; 69 | } 70 | 71 | static MAKE_AVOIDER( 72 | _repelTypes: string | string[], 73 | fromIndexes: EWIndex[] = [1, 2, 3, 4, 5, 6, 7, 8], 74 | toIndexes: EWIndex[] = [40, 39, 38, 37, 28, 27, 26, 25] 75 | ) { 76 | return (ew: EventWindow, self: Element): boolean => { 77 | const repelTypes = self.state.repelTypes ?? _repelTypes; 78 | 79 | if (fromIndexes.length > toIndexes.length) { 80 | throw new Error("fromSet must be less than or equal to length of toSet"); 81 | } 82 | 83 | const repellers: EWIndex[] = ew.filter(fromIndexes, repelTypes); 84 | const emptyDests: EWIndex[] = ew.filter(toIndexes, "EMPTY"); 85 | 86 | if (repellers.length && emptyDests.length) { 87 | const repelMap = Object.fromEntries( 88 | fromIndexes.map((fi, i) => { 89 | return [fi, toIndexes[i]]; 90 | }) 91 | ); 92 | 93 | let moved = false; 94 | repellers.forEach((target) => { 95 | if (!moved) { 96 | // const toSite: EWIndex = repelMap[target]; 97 | //try to avoid in the opposing direction 98 | // if (emptyDests.includes(toSite)) { 99 | // moved = ew.move(toSite); 100 | // } else { 101 | //otherwise just avoid it anywhere available in the toSet! 102 | moved = ew.move(EventWindow.RANDOM(emptyDests)); 103 | // } 104 | } 105 | }); 106 | 107 | return moved; 108 | } 109 | 110 | return false; 111 | }; 112 | } 113 | 114 | static MAKE_ATTRACTOR(_attractTypes: string | string[], view: EWIndex[] = EventWindow.ALLADJACENT) { 115 | return (ew: EventWindow, self: Element): boolean => { 116 | const attractTypes = self.state.attractTypes ?? _attractTypes; 117 | const swapTypes = self.state.swapTypes ?? ["EMPTY"]; 118 | 119 | const attractors: EWIndex[] = ew.filter(view, attractTypes); 120 | 121 | if (attractors.length) { 122 | const attractor = EventWindow.RANDOM(attractors); 123 | const towardIndexes: EWIndex[] = Wayfinder.getInFront(Wayfinder.indexToDirection(attractor), true); 124 | const swapDests: EWIndex[] = ew.filter(towardIndexes, swapTypes); 125 | 126 | if (swapDests.length) { 127 | //sort by nearest index to the target attractor 128 | swapDests.sort((a, b) => Math.abs(attractor - a) - Math.abs(attractor - b)).slice(~~(swapDests.length / 2)); 129 | // return ew.swap(swapDests[0]); 130 | return ew.swap(EventWindow.RANDOM(swapDests)); 131 | } 132 | } 133 | 134 | return false; 135 | }; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/LemmingsGame.vue: -------------------------------------------------------------------------------- 1 | 40 | 158 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/Director.ts: -------------------------------------------------------------------------------- 1 | import { Decay } from "../../capabilities/Decay"; 2 | import { Wayfinding } from "../../capabilities/Wayfinding"; 3 | import { Element, IElementType } from "../../mfm/Element"; 4 | import { EventWindow, EWIndex } from "../../mfm/EventWindow"; 5 | import { Direction, Wayfinder } from "../../mfm/Wayfinder"; 6 | 7 | export class Director extends Element { 8 | static N = Director.CREATOR( 9 | { name: "DIRECTOR N", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 10 | { pointing: "N", directableTypes: ["DIRECTABLE"] } 11 | ); 12 | 13 | static S = Director.CREATOR( 14 | { name: "DIRECTOR S", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 15 | { pointing: "S", directableTypes: ["DIRECTABLE"] } 16 | ); 17 | 18 | static E = Director.CREATOR( 19 | { name: "DIRECTOR E", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 20 | { pointing: "E", directableTypes: ["DIRECTABLE"] } 21 | ); 22 | 23 | static W = Director.CREATOR( 24 | { name: "DIRECTOR W", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 25 | { pointing: "W", directableTypes: ["DIRECTABLE"] } 26 | ); 27 | 28 | static NE = Director.CREATOR( 29 | { name: "DIRECTOR NE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 30 | { pointing: "NE", directableTypes: ["DIRECTABLE"] } 31 | ); 32 | 33 | static NW = Director.CREATOR( 34 | { name: "DIRECTOR NW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 35 | { pointing: "NW", directableTypes: ["DIRECTABLE"] } 36 | ); 37 | 38 | static SW = Director.CREATOR( 39 | { name: "DIRECTOR SW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 40 | { pointing: "SW", directableTypes: ["DIRECTABLE"] } 41 | ); 42 | 43 | static SE = Director.CREATOR( 44 | { name: "DIRECTOR SE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 45 | { pointing: "SE", directableTypes: ["DIRECTABLE"] } 46 | ); 47 | 48 | static NNE = Director.CREATOR( 49 | { name: "DIRECTOR NNE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 50 | { pointing: "NNE", directableTypes: ["DIRECTABLE"] } 51 | ); 52 | 53 | static NNW = Director.CREATOR( 54 | { name: "DIRECTOR NNW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 55 | { pointing: "NNW", directableTypes: ["DIRECTABLE"] } 56 | ); 57 | 58 | static ENE = Director.CREATOR( 59 | { name: "DIRECTOR ENE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 60 | { pointing: "ENE", directableTypes: ["DIRECTABLE"] } 61 | ); 62 | 63 | static WNW = Director.CREATOR( 64 | { name: "DIRECTOR WNW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 65 | { pointing: "WNW", directableTypes: ["DIRECTABLE"] } 66 | ); 67 | 68 | static SSE = Director.CREATOR( 69 | { name: "DIRECTOR SSE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 70 | { pointing: "SSE", directableTypes: ["DIRECTABLE"] } 71 | ); 72 | static SSW = Director.CREATOR( 73 | { name: "DIRECTOR SSW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 74 | { pointing: "SSW", directableTypes: ["DIRECTABLE"] } 75 | ); 76 | 77 | static ESE = Director.CREATOR( 78 | { name: "DIRECTOR ESE", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 79 | { pointing: "ESE", directableTypes: ["DIRECTABLE"] } 80 | ); 81 | static WSW = Director.CREATOR( 82 | { name: "DIRECTOR WSW", symbol: "DTR", class: Director, color: 0x670239, classifications: ["DIRECTOR"], groups: ["Agents"] }, 83 | { pointing: "WSW", directableTypes: ["DIRECTABLE"] } 84 | ); 85 | 86 | static DIRECTORS_MAP: Map = new Map([ 87 | ["N", Director.N], 88 | ["E", Director.E], 89 | ["S", Director.S], 90 | ["W", Director.W], 91 | ["NE", Director.NE], 92 | ["NW", Director.NW], 93 | ["SE", Director.SE], 94 | ["SW", Director.SW], 95 | ["NNE", Director.NNE], 96 | ["NNW", Director.NNW], 97 | ["ENE", Director.ENE], 98 | ["WNW", Director.WNW], 99 | ["SSE", Director.SSE], 100 | ["SSW", Director.SSW], 101 | ["ESE", Director.ESE], 102 | ["WSW", Director.WSW], 103 | ]); 104 | 105 | constructor(type: IElementType, state: any = {}) { 106 | super(type, state); 107 | 108 | this.init(); 109 | } 110 | 111 | init() {} 112 | 113 | behave(ew: EventWindow) { 114 | super.behave(ew); 115 | if (this.state.directableTypes) { 116 | this.state.directableTypes.forEach((type) => { 117 | const directables: EWIndex[] = ew.filter(EventWindow.ALLADJACENT, type); 118 | directables.forEach((s) => { 119 | Wayfinding.DIRECT(ew, s, this.state.pointing); 120 | }); 121 | }); 122 | } 123 | 124 | if (ew.selfIs("DECAYABLE")) { 125 | Decay.DECAY(ew, this, this.state.lifeSpan ?? 100, 2); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /mfmrocks/src/pages/lemmings/_game/elements/Lemming.ts: -------------------------------------------------------------------------------- 1 | import { Element, EventWindow, Empty, Wall, Wayfinding, VirtualEventWindow, Wayfinder } from "mfm-js"; 2 | import { Dirt } from "./Dirt"; 3 | 4 | export class Lemming extends Element { 5 | static CREATE = Lemming.CREATOR({ name: "LEMM", symbol: "LMG", class: Lemming, color: 0xffffff, groups: ["LEMMINGS"], classifications: ["WALKABLE"] }); 6 | static HEAD = Wall.CREATOR( 7 | { name: "LEMM_HEAD", class: Wall, color: 0xffffff, classifications: ["DECAYABLE", "EMPTY"] }, 8 | { lifeSpan: 10 } 9 | ); 10 | 11 | constructor(type, state = {}) { 12 | super(type, state); 13 | this.init(); 14 | } 15 | 16 | static DIRS = { 17 | 'N': 2, 18 | 'E': 4, 19 | 'S': 3, 20 | 'W': 1 21 | } 22 | 23 | static ORIENTED_DIRS = { 24 | 'N': (location = 0) => VirtualEventWindow.getOrientedSiteIndex( location, 2 ), 25 | 'E': (location = 0) => VirtualEventWindow.getOrientedSiteIndex( location, 4 ), 26 | 'S': (location = 0) => VirtualEventWindow.getOrientedSiteIndex( location, 3 ), 27 | 'W': (location = 0) => VirtualEventWindow.getOrientedSiteIndex( location, 1 ), 28 | } 29 | 30 | static FIELD_OF_VIEW = { 31 | 'N': ["N", "NE", "NW"], 32 | 'S': ["S", "SE", "SW"], 33 | 'E': ["E", "NE", "SE"], 34 | 'W': ["W", "NW", "SW"], 35 | } 36 | 37 | static BUILD_WALK_TARGETS = { 38 | "E" : "NE", 39 | "W" : "NW", 40 | } 41 | 42 | init() { 43 | 44 | this.state.heading = 'E'; 45 | this.state.location = 0; 46 | 47 | this.setRole('WALKER'); 48 | } 49 | 50 | behave(ew: EventWindow) { 51 | super.behave(ew); 52 | this.state.location = 0; 53 | 54 | if( !ew.exists( 3 ) ) { 55 | ew.destroy(); 56 | } 57 | 58 | this.behead(ew); 59 | 60 | //Gravity? 61 | if( ew.is(3, "EMPTY") ) { 62 | const swapped = ew.swap(3); 63 | if( swapped ) this.state.location = 3; 64 | this.head(ew); 65 | return; 66 | } 67 | 68 | let moved = false; 69 | 70 | switch( this.state.role ) { 71 | case "WALKER": 72 | moved = this.walk(ew); 73 | break; 74 | case "DIGGER": 75 | if( !this.dig(ew) ) { 76 | moved = this.walk(ew); 77 | } 78 | break; 79 | case "BLOCKER": 80 | this.block(ew); 81 | break; 82 | case "MINER": 83 | if( !this.mine(ew) ) { 84 | moved = this.walk(ew); 85 | } 86 | break; 87 | case "BUILDER": 88 | if( !this.build(ew) ) { 89 | moved = this.walk(ew); 90 | } 91 | break; 92 | } 93 | this.head(ew); 94 | } 95 | 96 | walk(ew) { 97 | 98 | const WALK_CHANCE = 5; 99 | let walked = false; 100 | 101 | if( EventWindow.oneIn( WALK_CHANCE ) ) { 102 | const walkChecks = Lemming.FIELD_OF_VIEW[this.state.heading]; 103 | 104 | walked = Wayfinding.MOVE_IN_DIRECTION(ew, this, walkChecks, "EMPTY"); 105 | 106 | if( !walked && EventWindow.oneIn(2) ) { 107 | walked = Wayfinding.SWAP_IN_DIRECTION(ew, this, walkChecks, "WALKABLE"); 108 | } 109 | 110 | if(!walked && EventWindow.oneIn(2)) { 111 | Wayfinding.REVERSE(this); 112 | } 113 | 114 | } 115 | return false; 116 | } 117 | 118 | dig(ew) { 119 | let dug = false; 120 | const digChecks = Lemming.FIELD_OF_VIEW["S"]; 121 | 122 | dug = Wayfinding.MOVE_IN_DIRECTION(ew, this, digChecks, "DIGGABLE"); 123 | if( !dug ) { 124 | dug = Wayfinding.SWAP_IN_DIRECTION(ew, this, digChecks, "MOVABLE"); 125 | } 126 | return dug; 127 | } 128 | 129 | mine(ew) { 130 | let mined = false; 131 | const mineChecks = Lemming.FIELD_OF_VIEW[this.state.heading]; 132 | 133 | mined = Wayfinding.MOVE_IN_DIRECTION(ew, this, mineChecks, ["DIGGABLE", "MOVABLE"]); 134 | 135 | if( !mined ) { 136 | mined = Wayfinding.SWAP_IN_DIRECTION(ew, this, mineChecks, "MOVABLE"); 137 | } 138 | return mined; 139 | } 140 | 141 | block(ew) { 142 | const blocks = [2,3,10,22,38]; 143 | blocks.forEach( b => { 144 | ew.mutate(0, Dirt.MOSS); 145 | if( ew.is(b, "EMPTY" ) ) { 146 | ew.mutate(b, Dirt.MOSS); 147 | } 148 | }); 149 | } 150 | 151 | build(ew) { 152 | 153 | let built = false; 154 | this.state.buildCount = this.state.buildCount ?? 0; 155 | 156 | const buildChecks = Wayfinder.mapPath(this.buildPath() ); 157 | 158 | if( ew.all( buildChecks, "EMPTY") ) { 159 | 160 | ew.mutateMany( buildChecks, Wall.CREATE ); 161 | built = true; 162 | this.state.buildCount++; 163 | 164 | const buildWalkTarget = Wayfinder.getDestinationFromPath( [...this.buildPath(), "N"] ); 165 | if( ew.is( buildWalkTarget, "EMPTY" ) ) { 166 | if( ew.move( buildWalkTarget ) ) { 167 | this.state.location = buildWalkTarget; 168 | } 169 | } 170 | } 171 | 172 | if( this.state.buildCount > 5 ) { 173 | this.setRole('WALKER'); 174 | this.state.buildCount = 0; 175 | } 176 | 177 | return built; 178 | } 179 | 180 | buildPath() { 181 | const h = this.state.heading; 182 | return [ h, h, h]; 183 | } 184 | 185 | behead(ew) { 186 | if( ew.is( Lemming.ORIENTED_DIRS.N(this.state.location), "LEMM_HEAD" ) ) { 187 | ew.mutate( Lemming.ORIENTED_DIRS.N(this.state.location), Empty.CREATE ); 188 | } 189 | if( ew.is( Lemming.ORIENTED_DIRS[this.state.heading](this.state.location), "LEMM_HEAD" ) ) { 190 | ew.mutate( Lemming.ORIENTED_DIRS[this.state.heading](this.state.location), Empty.CREATE ); 191 | } 192 | } 193 | 194 | head(ew) { 195 | if( ew.is( Lemming.ORIENTED_DIRS.N(this.state.location), "EMPTY" ) ) { 196 | ew.mutate( Lemming.ORIENTED_DIRS.N(this.state.location), Lemming.HEAD ); 197 | } else if( ew.is( Lemming.ORIENTED_DIRS[this.state.heading](this.state.location), "EMPTY" ) ) { 198 | ew.mutate( Lemming.ORIENTED_DIRS[this.state.heading](this.state.location), Lemming.HEAD ); 199 | } 200 | } 201 | 202 | setRole(role) { 203 | this.declassify(this.state.role); 204 | this.state.role = role; 205 | this.classifyAs(this.state.role); 206 | } 207 | 208 | 209 | } 210 | 211 | -------------------------------------------------------------------------------- /mfmrocks/public/v1/p2p.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 73 | 74 | 75 | 76 |
77 | 78 | 79 |
80 |
81 |
82 | 84 |
85 |
86 | 88 |
89 |
90 |
91 |
92 | 94 |
95 |
96 | 98 |
99 |
100 |
101 |
102 | 104 |
105 |
106 | 108 |
109 |
110 |
111 |

My ID: {{tileId}}

112 |

{{lastMessage}}

113 |
114 |
115 |
116 | 118 |
119 |
120 | 122 |
123 |
124 |
125 |
126 | 127 | 128 |
129 |
130 | 133 |
134 |
135 |
136 |
137 | 139 |
140 |
141 | 144 |
145 |
146 |
147 | 148 |
149 | 150 | 151 | 152 | 153 | 154 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/SwapWorm.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow, EWIndex } from "../../mfm/EventWindow"; 4 | import { Wayfinder } from "../../mfm/Wayfinder"; 5 | 6 | export class SwapWorm extends Element { 7 | static CREATE = SwapWorm.CREATOR({ name: "SWAPWORM", symbol: "SWP", class: SwapWorm, color: 0xbe146f, classifications:["DIRECTIONAL", "DIRECTABLE", "WORM"], groups: ["Life"] }); 8 | 9 | 10 | static SMOLSW = SwapWorm.CREATOR({ name: "SMOLSW", symbol: "SWP", class: SwapWorm, color: 0xbe146f, classifications:["SWAPWORM", "DIRECTIONAL", "DIRECTABLE", "WORM"], groups: ["Life"] }, {growCount: 2}); 11 | static BIGSW = SwapWorm.CREATOR({ name: "BIGSW", symbol: "SWP", class: SwapWorm, color: 0xbe146f, classifications:["SWAPWORM", "DIRECTIONAL", "DIRECTABLE", "WORM"], groups: ["Life"] }, {growCount: 24}); 12 | 13 | // static COLORS = [ 0xff0000, 0xff7f00, 0xffff00, 0x00ff00, 0x0000ff, 0x4b0082, 0x940d3 ]; 14 | 15 | constructor(type: IElementType, state: any = {}) { 16 | super(type, state); 17 | 18 | this.init(); 19 | } 20 | 21 | init() { 22 | this.state.growCount = this.state.growCount ?? 8; 23 | // this.state.color = SwapWorm.COLORS[~~(Math.random()*SwapWorm.COLORS.length)]; 24 | } 25 | 26 | behave(ew: EventWindow) { 27 | super.behave(ew); 28 | 29 | let type = this.establishType(ew); 30 | let moved:boolean = false; 31 | 32 | if(!type && this.state.age > 1) { 33 | ew.destroy(); 34 | } 35 | 36 | //GROW 37 | if( this.state.growCount ) { 38 | moved = this.grow(ew); 39 | if( moved ) { 40 | return; 41 | } 42 | } 43 | 44 | switch(type) { 45 | 46 | 47 | case "HEAD": 48 | return this.move(ew); 49 | break; 50 | case "MIDDLE": 51 | if( this.isAheadTemp(ew) ) { 52 | moved = this.swapSegments(ew, this.state.ahead ); 53 | 54 | if( moved ) { 55 | return; 56 | } 57 | } 58 | break; 59 | case "TAIL": 60 | 61 | if( this.isTemp() ) { 62 | const ahead = this.getSegment( ew, this.state.ahead ); 63 | if( ahead ) { 64 | ahead.state.behind = null; 65 | } 66 | ew.destroy(); 67 | } else { 68 | if( this.isAheadTemp(ew) ) { 69 | moved = this.swapSegments(ew, this.state.ahead ); 70 | 71 | if( moved ) { 72 | return; 73 | } 74 | } 75 | } 76 | 77 | break; 78 | } 79 | 80 | } 81 | 82 | establishType(ew:EventWindow):string { 83 | 84 | this.confirmBehind(ew); 85 | this.confirmAhead(ew); 86 | 87 | let type = undefined; 88 | 89 | if( this.isHead() ) { 90 | this.state.color = 0xddeeff; 91 | type = "HEAD"; 92 | } else if( this.isTail() ) { 93 | // this.state.color = 0xff33ff; 94 | type = "TAIL"; 95 | } else if( this.isMiddle() ) { 96 | // this.state.color = 0xcc0066; 97 | type = "MIDDLE"; 98 | } 99 | 100 | if( this.isTemp() ) { 101 | // this.state.color = SwapWorm.COLORS[~~(Math.random()*SwapWorm.COLORS.length)]; 102 | } 103 | 104 | return type; 105 | } 106 | 107 | isHead():boolean { 108 | return !!(!this.state.ahead && this.state.behind); 109 | } 110 | 111 | isTail():boolean { 112 | return !!( this.state.ahead && !this.state.behind) ; 113 | } 114 | 115 | isMiddle():boolean { 116 | return !!( this.state.ahead && this.state.behind ); 117 | } 118 | 119 | isTemp():boolean { 120 | return this.state.temp; 121 | } 122 | 123 | confirmBehind(ew:EventWindow):boolean { 124 | const behind = this.getSegment( ew, this.state.behind ); 125 | 126 | if( !behind || (behind && !behind.is('SWAPWORM'))) { 127 | this.state.behind = null; 128 | return false; 129 | } 130 | 131 | return true; 132 | } 133 | 134 | confirmAhead(ew:EventWindow):boolean { 135 | const ahead = this.getSegment( ew, this.state.ahead ); 136 | 137 | if( !ahead || (ahead && !ahead.is('SWAPWORM'))) { 138 | this.state.ahead = null; 139 | return false; 140 | } 141 | 142 | return true; 143 | } 144 | 145 | isBehindTemp(ew:EventWindow):boolean { 146 | if( this.state.behind ) { 147 | const behind = this.getSegment( ew, this.state.behind ); 148 | if( behind && behind.state.temp ) { 149 | return true; 150 | } 151 | } 152 | 153 | return false; 154 | } 155 | 156 | isAheadTemp(ew:EventWindow):boolean { 157 | if( this.state.ahead ) { 158 | const ahead = this.getSegment( ew, this.state.ahead ); 159 | if( ahead && ahead.state.temp ) { 160 | return true; 161 | } 162 | } 163 | 164 | return false; 165 | } 166 | 167 | makeGrowSegment( a:EWIndex ):Element { 168 | return SwapWorm.CREATE(this.TYPE, { growCount: 0, ahead: a, behind: this.state.behind }) 169 | } 170 | 171 | makeGrowTemp( a:EWIndex):Element { 172 | return SwapWorm.CREATE(this.TYPE, { growCount: 0, ahead: a, behind: this.state.behind, temp: true }) 173 | } 174 | 175 | getSegment( ew:EventWindow, segIndex:EWIndex ):Element { 176 | 177 | if( !segIndex && segIndex !== 0 ) { 178 | return undefined; 179 | } 180 | 181 | const seg = ew.getSite(segIndex)?.atom; 182 | 183 | if( !seg || !seg.is("SWAPWORM") ) { 184 | return undefined; 185 | } 186 | 187 | return seg; 188 | } 189 | 190 | swapSegments( ew:EventWindow, segIndex:EWIndex ) { 191 | const segElement:Element = this.getSegment( ew, segIndex ); 192 | [this.state.ahead, this.state.behind, segElement.state.ahead, segElement.state.behind] = [ segElement.state.ahead, segElement.state.behind, this.state.ahead, this.state.behind ]; 193 | return ew.swap(segIndex); 194 | } 195 | 196 | grow(ew:EventWindow):boolean { 197 | 198 | const moveSite:EWIndex = Math.min( ...ew.filter( EventWindow.ADJACENT8WAY, "EMPTY") ) as EWIndex; 199 | 200 | if( moveSite && moveSite !== Infinity ) { 201 | const moved = ew.move( moveSite, this.makeGrowSegment( moveSite ) ); 202 | 203 | if( moved ) { 204 | this.state.growCount--; 205 | this.state.behind = EventWindow.OPPOSITES[moveSite]; 206 | } 207 | 208 | return moved; 209 | } 210 | 211 | return false; 212 | } 213 | 214 | move( ew:EventWindow ):boolean { 215 | 216 | if( this.state?.status === "HELD" ) { 217 | return false; 218 | } 219 | 220 | if (!this.state.heading) { 221 | Wayfinding.SET_DIRECTION(this, Wayfinder.RANDOM()); 222 | } else if (EventWindow.oneIn(2)) { 223 | 224 | 225 | const travelTo: EWIndex = Wayfinder.getDirectionalMove(this.state.heading, true); 226 | let moved = false; 227 | 228 | if( ew.is(travelTo, "EMPTY") ) { 229 | moved = ew.move(travelTo, this.makeGrowTemp(travelTo) ); 230 | } 231 | 232 | if( !(this.state?.status === "DIRECTED") ) { 233 | EventWindow.oneIn(10) && Wayfinding.SLIGHT_RANDOMLY(this); 234 | } 235 | 236 | if( !moved ) { 237 | Wayfinding.SLIGHT_RANDOMLY(this); 238 | } else { 239 | this.state.behind = EventWindow.OPPOSITES[travelTo]; 240 | } 241 | 242 | return moved; 243 | } 244 | 245 | return false; 246 | 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /mfmrocks/src/components/mfms.vue: -------------------------------------------------------------------------------- 1 | 55 | 56 | 176 | 177 | 304 | 305 | 306 | -------------------------------------------------------------------------------- /mfm-js/src/elements/agents/HardCell3.ts: -------------------------------------------------------------------------------- 1 | import { Wayfinding } from "../../capabilities/Wayfinding"; 2 | import { Element, IElementType } from "../../mfm/Element"; 3 | import { EventWindow, EWIndex } from "../../mfm/EventWindow"; 4 | import { Direction } from "../../mfm/Wayfinder"; 5 | 6 | export class HardCell3 extends Element { 7 | static CREATE = HardCell3.CREATOR({ name: "HARDCELL3x16", symbol: "HC3", class: HardCell3, color: 0xbe146f, classifications: ["HARDCELL3"], groups: ["MFM"] }); 8 | static HC3x8 = HardCell3.CREATOR({ name: "HARDCELL3x8", symbol: "HC3", class: HardCell3, color: 0xbe146f, classifications: ["HARDCELL3"], groups: ["MFM"] }, { maxHops: 8}); 9 | static HC3x4 = HardCell3.CREATOR({ name: "HARDCELL3x4", symbol: "HC3", class: HardCell3, color: 0xbe146f, classifications: ["HARDCELL3"], groups: ["MFM"] }, { maxHops: 4}); 10 | 11 | // static CELL_SITES:EWIndex[] = [37, 38, 39, 40]; //HardCell4 12 | static CELL_SITES:EWIndex[] = [21,22,23,24]; 13 | // static CELL_SITES:EWIndex[] = [9,10,11,12]; //HardCell2 14 | // static CELL_SITES:EWIndex[] = [1,2,3,4]; //HardCell1 15 | 16 | static CELL_WANDER_MAP = new Map([ 17 | [ 0, [1, 2, 3, 4] ], 18 | [ 21, [9,29,30,37] ], 19 | [ 22, [10,31,33,38] ], 20 | [ 23, [11,32,34,39] ], 21 | [ 24, [12,35,36,40] ], 22 | ]); 23 | 24 | static CELL_DIRECTION_MAP = new Map([ 25 | [ 2, "N" ], 26 | [ 11, "N" ], 27 | [ 29, "N" ], 28 | [ 35, "N" ], 29 | [ 38, "N" ], 30 | [ 4, "E" ], 31 | [ 9, "E" ], 32 | [ 33, "E" ], 33 | [ 34, "E" ], 34 | [ 40, "E" ], 35 | [ 1, "W" ], 36 | [ 12, "W" ], 37 | [ 31, "W" ], 38 | [ 32, "W" ], 39 | [ 37, "W" ], 40 | [ 3, "S" ], 41 | [ 10, "S" ], 42 | [ 30, "S" ], 43 | [ 36, "S" ], 44 | [ 39, "S" ], 45 | ]); 46 | 47 | static COLORS = [ 0xff0000, 0xff7f00, 0xffff00, 0x00ff00, 0x0000ff, 0x4b0082, 0x940d3 ]; 48 | 49 | constructor(type: IElementType, state: any = {}) { 50 | super(type, state); 51 | 52 | this.init(); 53 | } 54 | 55 | init() { 56 | this.state.maxHops = this.state.maxHops ?? 16; 57 | this.state.stasus = false; 58 | 59 | if( !this.state.localId ) { 60 | this.state.localId = (~~(Math.random()*255)).toString(16); 61 | } 62 | 63 | this.classifyAs( `HC3-${this.state.localId}` ); 64 | } 65 | 66 | behave(ew: EventWindow) { 67 | super.behave(ew); 68 | 69 | 70 | this.state.stasus = this.hasStasis(ew); 71 | 72 | //figure out hopCount 73 | if( this.state.hops === undefined || this.hasBadStructure(ew) ) { 74 | this.figureHops(ew); 75 | 76 | return; 77 | } 78 | 79 | //check for movement 80 | if( !this.isRoot() ) { 81 | 82 | const dir = this.shouldMove(ew); 83 | if( dir ) { 84 | const moved = Wayfinding.MOVE_IN_DIRECTION( ew, this, dir ); 85 | return; 86 | } 87 | } 88 | 89 | //grow if empties 90 | //would like this to happen more than once (for regrowth) 91 | // if( !this.isEnd() && !this.shouldMove(ew) && !this.state.stasus ) { 92 | if( this.shouldRegrow(ew) ) { 93 | const empties = ew.filter( HardCell3.CELL_SITES, "EMPTY" ); 94 | if( empties.length ) { 95 | ew.mutateMany( empties, HardCell3.CREATOR( this.TYPE, { maxHops: this.state.maxHops, localId: this.state.localId } )); 96 | } 97 | } 98 | 99 | if( this.isRoot() && this.state.stasus ) { 100 | 101 | if( EventWindow.oneIn(4) && this.canMove(ew) ) { 102 | ew.swap( EventWindow.RANDOM( HardCell3.CELL_WANDER_MAP.get(0) ), 0 ); 103 | 104 | //experimental move root within grid 105 | // const neighbors = this.neighbors(ew); 106 | // const up = ew.getSite( EventWindow.RANDOM( neighbors ) ); 107 | // neighbors.forEach( n => ew.getSite(n).atom.state.hops = undefined ); 108 | // [this.state.hops, up.atom.state.hops] = [undefined, 0]; 109 | 110 | } 111 | 112 | } 113 | 114 | if( this.isEnd() ) { 115 | this.classifyAs( `HC3-END` ); 116 | } 117 | 118 | } 119 | 120 | //stasus means you have all 4 HardCell3's surrounding you if you're not the end 121 | //AND all upstream HardCell3's are also in stasus 122 | hasStasis( ew:EventWindow ):Boolean { 123 | 124 | if( this.isEnd() ) { 125 | return true; 126 | } 127 | 128 | const neighbors = this.neighbors(ew); 129 | 130 | if( neighbors.length !== ew.getSites(HardCell3.CELL_SITES).filter(s => s).length) { 131 | return false; 132 | } 133 | 134 | const neighborStasus = this.upstreams(ew).map( n => ew.getSite(n).atom.state.stasus ?? false ).some( v => !v ); 135 | return !neighborStasus; 136 | 137 | } 138 | 139 | //bad structure is if the neighbors have too high or low or no hop count 140 | hasBadStructure( ew :EventWindow ):Boolean { 141 | const neighborHops = this.neighbors(ew).map( n => ew.getSite(n).atom.state.hops ); 142 | const badStructure = neighborHops.some( h => h === undefined ) || neighborHops.some( h => h > this.state.hops+1 ) || neighborHops.some( h => h < this.state.hops-1 ) || neighborHops.some( h => h === undefined ); 143 | 144 | return badStructure; 145 | } 146 | 147 | 148 | figureHops(ew:EventWindow) { 149 | 150 | const neighbors = this.neighbors(ew); 151 | 152 | if(neighbors.length === 0 || this.state.hops === 0 ) { 153 | this.state.hops = 0; 154 | this.setColor(); 155 | return; 156 | 157 | } else { 158 | 159 | const neighborHops:EWIndex[] = neighbors.map( n => ew.getSite(n)?.atom.state.hops ).filter( h => !isNaN(h) && h !== undefined ); 160 | 161 | if( neighborHops.length ) { 162 | this.state.hops = Math.min(...neighborHops) + 1; 163 | } else { 164 | return; 165 | } 166 | 167 | } 168 | 169 | this.setColor(); 170 | 171 | if( isNaN(this.state.hops) ) { 172 | ew.destroy(); 173 | } 174 | 175 | } 176 | 177 | setColor() { 178 | this.state.color = HardCell3.COLORS[this.state.hops % HardCell3.COLORS.length] 179 | } 180 | 181 | canMove( ew:EventWindow ):Boolean { 182 | return this.neighbors(ew).length === HardCell3.CELL_SITES.length; 183 | } 184 | 185 | shouldRegrow(ew:EventWindow):Boolean { 186 | 187 | if( this.isEnd() ) { 188 | return false; 189 | } 190 | 191 | const allHC3 = ew.filter( EventWindow.ALLADJACENT, this.localType() ); 192 | 193 | if( allHC3.length === 4 ) { 194 | return false; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | 201 | shouldMove( ew:EventWindow ):Direction | false { 202 | 203 | // const downstreams = this.downstreams(ew); 204 | 205 | // if( this.state.hops < this.state.maxHops && downstreams.length < HardCell3.CELL_SITES.length ) { 206 | // // const wandered = ew.filter( HardCell3.CELL_SITES, 207 | // } 208 | 209 | const empties = ew.filter( HardCell3.CELL_SITES, "EMPTY" ); 210 | 211 | if( empties.length ) { 212 | 213 | const e = EventWindow.RANDOM(empties); 214 | const lookArounds = HardCell3.CELL_WANDER_MAP.get(e); 215 | 216 | const hc3s = ew.filter( lookArounds, this.localType() ).filter( h => ew.getSite(h).atom.state.hops < this.state.hops ); 217 | 218 | if( hc3s.length ) { 219 | const hc3 = EventWindow.RANDOM(hc3s); 220 | return HardCell3.CELL_DIRECTION_MAP.get( hc3 ); 221 | } 222 | } 223 | 224 | return false; 225 | } 226 | 227 | localType():string { 228 | return `HC3-${this.state.localId}`; 229 | } 230 | 231 | isRoot():Boolean { 232 | return this.state.hops === 0; 233 | } 234 | 235 | isEnd():Boolean { 236 | return this.state.hops === this.state.maxHops; 237 | } 238 | 239 | upstreams(ew:EventWindow):EWIndex[] { 240 | const neighbors = ew.filter( HardCell3.CELL_SITES, this.localType() ); 241 | const cellSites = neighbors.filter( n => ew.getSite(n)?.atom.state?.hops > this.state.hops ); 242 | 243 | return cellSites; 244 | } 245 | 246 | downstreams(ew:EventWindow):EWIndex[] { 247 | const neighbors = ew.filter( HardCell3.CELL_SITES, this.localType() ); 248 | const cellSites = neighbors.filter( n => ew.getSite(n)?.atom.state?.hops < this.state.hops ); 249 | 250 | return cellSites; 251 | } 252 | 253 | //returns actual HardCell3's at CELL_SITES 254 | neighbors(ew:EventWindow):EWIndex[] { 255 | return ew.filter( HardCell3.CELL_SITES, this.localType() ); 256 | } 257 | 258 | getDirectionFromWanderMap( ew:EventWindow, site:EWIndex ) { 259 | const sites:EWIndex[] = HardCell3.CELL_WANDER_MAP.get(site); 260 | 261 | if( !sites ) { 262 | return; 263 | } 264 | 265 | const wentSite:EWIndex = ew.filter( sites, this.localType(), true)?.[0]; 266 | 267 | if( wentSite ) { 268 | return HardCell3.CELL_DIRECTION_MAP.get(wentSite); 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /mfm-js/renderers/pixi/renderer.ts: -------------------------------------------------------------------------------- 1 | import { Sprite, Container, Application, Assets, Texture, Point, Rectangle, ObservablePoint, FederatedPointerEvent } from "pixi.js"; 2 | import { EventWindow } from "../../src/mfm/EventWindow.js"; 3 | import { Site } from "../../src/mfm/Site.js"; 4 | import { Tile } from "../../src/mfm/Tile.js"; 5 | //@ts-ignore 6 | import url from "./element.png"; 7 | 8 | export interface IRenderer { 9 | tile: Tile; 10 | render: Function; 11 | view; 12 | } 13 | 14 | export interface ISiteRenderer {} 15 | 16 | export class PixiRenderer implements IRenderer { 17 | rendererWidth: number; 18 | rendererHeight: number; 19 | 20 | tile: Tile; 21 | tileWidth: number; 22 | tileHeight: number; 23 | totalSites: number; 24 | 25 | siteSize: number; 26 | siteTexture: Texture;//Texture.from(url); 27 | 28 | particleContainer: Container; 29 | pixiApplication: Application; 30 | 31 | siteVisuals: Map; 32 | 33 | subdivisions: number = 1; 34 | gridDivisions: number[][]; 35 | gridDivisionTotal: number; 36 | 37 | siteArray: Site[]; 38 | 39 | renderSpeed: number = 1; 40 | fixedRenderSpeed: number = 1; 41 | renderMultiplier: number = 1; 42 | view; 43 | 44 | clickArea: Container; 45 | pointerDown: boolean = false; 46 | curSelectedElementFunction: Function; 47 | selectedSite: Site; 48 | mouseEnabled: boolean = true; 49 | brushSize: number = 1; 50 | 51 | constructor(t: Tile, rendererW: number, rendererH: number) { 52 | this.tile = t; 53 | 54 | this.rendererHeight = rendererH; 55 | this.rendererWidth = rendererW; 56 | 57 | this.tileWidth = this.tile.width; 58 | this.tileHeight = this.tile.height; 59 | this.totalSites = this.tileWidth * this.tileHeight; 60 | 61 | this.siteSize = ~~(this.maxValue(this.rendererHeight, this.rendererWidth) / this.maxValue(this.tileHeight, this.tileWidth)); 62 | this.siteArray = Array.from(t.sites.values()).sort((s) => Math.random() - 0.5); 63 | } 64 | 65 | async init() { 66 | await this.initializePIXI(); 67 | await this.initializeVisuals(); 68 | 69 | const subs = this.calculateSubdivisions(); 70 | this.setSubdivisions(subs); 71 | 72 | this.initializeClickArea(); 73 | 74 | this.startRendering(); 75 | } 76 | 77 | calculateSubdivisions(): number { 78 | let subs = Math.sqrt(this.tileWidth) * 0.25; 79 | 80 | if ((this.tileWidth / subs) % 1 !== 0) { 81 | subs = ~~subs; 82 | 83 | do { 84 | subs--; 85 | } while ((this.tileWidth / subs) % 1 !== 0); 86 | } 87 | 88 | if (subs < 1) { 89 | subs = 1; 90 | } 91 | 92 | return subs; 93 | } 94 | 95 | setSubdivisions(subs: number) { 96 | this.subdivisions = subs; 97 | this.createSubdivisions(this.subdivisions); 98 | this.setRenderMultiplier(this.renderMultiplier); 99 | } 100 | 101 | setRenderMultiplier(mult: number) { 102 | this.renderMultiplier = mult; 103 | this.renderSpeed = (this.totalSites / this.gridDivisions.length) * this.renderMultiplier; 104 | this.fixedRenderSpeed = this.totalSites * this.renderMultiplier; 105 | } 106 | 107 | async initializePIXI() { 108 | this.particleContainer = new Container({ 109 | isRenderGroup:true 110 | }); 111 | 112 | this.pixiApplication = new Application(); 113 | 114 | await this.pixiApplication.init({ 115 | width: this.rendererWidth, 116 | height: this.rendererHeight, 117 | antialias: false, 118 | backgroundAlpha: 0xffffff, 119 | backgroundColor: 0x222222, 120 | resolution: 1, 121 | }); 122 | 123 | this.pixiApplication.stage.addChild(this.particleContainer); 124 | this.view = this.pixiApplication.canvas; 125 | } 126 | 127 | async initializeVisuals() { 128 | this.siteTexture = await Assets.load(url); 129 | const textureSize = 14; //this.siteTexture._frame.width; 130 | this.siteVisuals = new Map(); 131 | 132 | // Create the sprite and add it to the stage 133 | for (let i = 0; i < this.tileHeight; i++) { 134 | for (let j = 0; j < this.tileWidth; j++) { 135 | let viz = Sprite.from(this.siteTexture); 136 | viz.interactive = false; 137 | 138 | // viz.scale = new ObservablePoint(undefined, undefined, this.siteSize / textureSize, this.siteSize / textureSize); 139 | viz.scale = new Point(this.siteSize / textureSize, this.siteSize / textureSize) as ObservablePoint; 140 | 141 | viz.x = j * this.siteSize; 142 | viz.y = i * this.siteSize; 143 | // viz.cacheAsBitmap = true; 144 | 145 | viz.tint = this.tile.sites.get(`${i}:${j}`).atom.rd("color"); 146 | this.particleContainer.addChild(viz); 147 | 148 | this.siteVisuals.set(this.tile.sites.get(`${i}:${j}`), viz); 149 | } 150 | } 151 | } 152 | 153 | createSubdivisions(subdivisions) { 154 | let ww = ~~(this.tileWidth / subdivisions); 155 | let wh = ~~(this.tileHeight / subdivisions); 156 | 157 | this.gridDivisions = new Array>(); 158 | 159 | for (let row = 0; row < this.tileHeight; row += wh) { 160 | for (let col = 0; col < this.tileWidth; col += ww) { 161 | const rowmax = row + wh; 162 | const colmax = col + ww; 163 | 164 | this.gridDivisions.push([row, rowmax, col, colmax]); 165 | } 166 | } 167 | 168 | this.gridDivisionTotal = this.gridDivisions.length; 169 | } 170 | 171 | startRendering() { 172 | this.pixiApplication.ticker.add((delta) => { 173 | this.render(); 174 | }); 175 | } 176 | 177 | deconstruct() { 178 | Assets.load(url); 179 | this.particleContainer.destroy(true); 180 | this.pixiApplication.stop(); 181 | this.pixiApplication.destroy(true); 182 | } 183 | 184 | render() { 185 | // console.time(); 186 | 187 | let i = 0, 188 | j = 0, 189 | rs = this.fixedRenderSpeed; 190 | for (i; i < rs; i++) { 191 | const { atom, ew } = this.tile.getRandomSiteSeeded(); 192 | atom.behave(ew); 193 | } 194 | 195 | for (j; j < this.totalSites; j++) { 196 | const s = this.siteArray[j]; 197 | const v = this.siteVisuals.get(s); 198 | const color = s.atom.state.color; 199 | if (v.tint !== color) { 200 | v.tint = color; 201 | } 202 | } 203 | 204 | // console.timeEnd(); 205 | } 206 | 207 | initializeClickArea() { 208 | this.clickArea = new Container(); 209 | this.clickArea.hitArea = new Rectangle(0, 0, this.rendererWidth, this.rendererHeight); 210 | this.clickArea.interactive = true; 211 | this.pixiApplication.stage.addChild(this.clickArea); 212 | 213 | this.clickArea.on("pointerdown", (e) => { 214 | this.pointerDown = true; 215 | this.handleClick(e); 216 | }); 217 | 218 | this.clickArea.on("pointerup", (e) => { 219 | this.pointerDown = false; 220 | }); 221 | 222 | this.clickArea.on("pointermove", this.handleClick, this); 223 | } 224 | 225 | getSitesFromCanvasXY(x: number, y: number, size: number = 1): Site[] { 226 | let sites = new Array(); 227 | const overSite = this.getSiteFromCanvasXY(x, y); 228 | if (!overSite) { 229 | return sites; 230 | } 231 | let sx = overSite.location.coordinate.x; 232 | let sy = overSite.location.coordinate.y; 233 | 234 | switch (size) { 235 | case 5: 236 | sites.push(...EventWindow.LAYER4.map((i) => overSite.ew.getSite(i))); 237 | case 4: 238 | sites.push(...EventWindow.LAYER3.map((i) => overSite.ew.getSite(i))); 239 | case 3: 240 | sites.push(...EventWindow.LAYER2.map((i) => overSite.ew.getSite(i))); 241 | case 2: 242 | sites.push(...EventWindow.LAYER1.map((i) => overSite.ew.getSite(i))); 243 | case 1: 244 | sites.push(overSite); 245 | } 246 | 247 | return sites.filter((s) => s); 248 | } 249 | 250 | getSiteFromCanvasXY(x: number, y: number): Site { 251 | x = (x / this.siteSize) >> 0; 252 | y = (y / this.siteSize) >> 0; 253 | 254 | return this.tile.getSiteByCoordinate({ x, y }); 255 | } 256 | 257 | handleClick(e:FederatedPointerEvent) { 258 | if (this.mouseEnabled && this.pointerDown && e.target) { 259 | let p: Point = (this.pixiApplication.stage).toLocal(e.global);//e.data.getLocalPosition(this.pixiApplication.stage); 260 | let sites: Site[] = this.getSitesFromCanvasXY(p.x, p.y, this.brushSize); 261 | sites.forEach((site) => { 262 | this.addAtom(site); 263 | }); 264 | } 265 | } 266 | 267 | addAtom(site: Site) { 268 | this.selectedSite = site; 269 | 270 | if (site && this.curSelectedElementFunction) { 271 | site.atom = this.curSelectedElementFunction(); 272 | } 273 | } 274 | 275 | minValue(v1, v2) { 276 | return v1 < v2 ? v1 : v2; 277 | } 278 | 279 | maxValue(v1, v2) { 280 | return v1 > v2 ? v1 : v2; 281 | } 282 | 283 | clear() { 284 | this.tile.clear(); 285 | } 286 | } 287 | --------------------------------------------------------------------------------