diff options
author | Elizabeth Hunt <elizabeth@simponic.xyz> | 2025-03-01 12:36:47 -0700 |
---|---|---|
committer | Elizabeth Hunt <elizabeth@simponic.xyz> | 2025-03-01 12:36:47 -0700 |
commit | 8dacee8f73633131fd68935c1e2493dc4beec837 (patch) | |
tree | fc9adf76fce4761b01208ba2f44e72a6838244aa /src/engine/entities | |
parent | d903bd9a13e790cf42c84c3dc59bf89ffeae1d80 (diff) | |
download | the-abstraction-engine-8dacee8f73633131fd68935c1e2493dc4beec837.tar.gz the-abstraction-engine-8dacee8f73633131fd68935c1e2493dc4beec837.zip |
updates
Diffstat (limited to 'src/engine/entities')
-rw-r--r-- | src/engine/entities/FunctionApplication.ts | 108 | ||||
-rw-r--r-- | src/engine/entities/FunctionBox.ts | 99 | ||||
-rw-r--r-- | src/engine/entities/LambdaFactory.ts | 239 | ||||
-rw-r--r-- | src/engine/entities/Sign.ts | 59 |
4 files changed, 201 insertions, 304 deletions
diff --git a/src/engine/entities/FunctionApplication.ts b/src/engine/entities/FunctionApplication.ts index a266941..f15fcb9 100644 --- a/src/engine/entities/FunctionApplication.ts +++ b/src/engine/entities/FunctionApplication.ts @@ -1,17 +1,13 @@ -import { - Entity, - EntityNames, - FunctionBox, - Key, - Particles, - makeLambdaTermHighlightComponent, -} from "."; +import { Entity, EntityNames, FunctionBox, Key, Particles } from "."; import { BoundingBox, Colliding, ComponentNames, Grid, + Highlight, + Interactable, LambdaTerm, + Modal, Sprite, } from "../components"; import { @@ -26,9 +22,9 @@ import { import { Coord2D, Direction } from "../interfaces"; import { Game } from ".."; import { Grid as GridSystem, SystemNames } from "../systems"; -import { colors } from "../utils"; +import { colors, tryWrap } from "../utils"; import { - DebrujinifiedLambdaTerm, + InvalidLambdaTermError, SymbolTable, emitNamed, interpret, @@ -62,8 +58,8 @@ export class FunctionApplication extends Entity { y: 0, }, dimension, - 0, - ), + 0 + ) ); this.addComponent(new Grid(gridPosition)); @@ -76,13 +72,48 @@ export class FunctionApplication extends Entity { { x: 0, y: 0 }, dimension, FunctionApplication.spriteSpec.msPerFrame, - FunctionApplication.spriteSpec.frames, - ), + FunctionApplication.spriteSpec.frames + ) ); this.addComponent(new Colliding(this.handleCollision.bind(this))); - this.addComponent(makeLambdaTermHighlightComponent(this)); + this.addComponent( + new Highlight(this.onHighlight.bind(this), this.onUnhighlight.bind(this)) + ); + } + + private onHighlight(_direction: Direction) { + this.addComponent(new Interactable(this.interaction.bind(this))); + } + + private interaction() { + const codeConsumer = (_code: string) => { + this.removeComponent(ComponentNames.Modal); + return { consumed: true }; + }; + const { last, code } = this.getComponent<LambdaTerm>( + ComponentNames.LambdaTerm + ); + this.addComponent( + new Modal({ + type: "CODE_EDITOR", + codeInit: { + code, + codeConsumer, + readonly: true, + result: { + error: last?.error && `Error: ${last.error.message}`, + data: last?.data && `Last Result: ${emitNamed(last.data)}`, + }, + }, + }) + ); + } + + private onUnhighlight() { + this.removeComponent(ComponentNames.Modal); + this.removeComponent(ComponentNames.Interactable); } public handleCollision(game: Game, entity: Entity) { @@ -100,7 +131,7 @@ export class FunctionApplication extends Entity { const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid); const fail = () => { entityGrid.movingDirection = gridSystem.oppositeDirection( - entityGrid.previousDirection, + entityGrid.previousDirection ); entity.addComponent(entityGrid); @@ -109,18 +140,17 @@ export class FunctionApplication extends Entity { }; const applicationTerm = this.getComponent<LambdaTerm>( - ComponentNames.LambdaTerm, + ComponentNames.LambdaTerm ); const functionTerm = entity.getComponent<LambdaTerm>( - ComponentNames.LambdaTerm, + ComponentNames.LambdaTerm ); const newCode = applicationTerm.code.replace("_INPUT", functionTerm.code); - let result: DebrujinifiedLambdaTerm | null = null; - try { - result = interpret(newCode, this.symbolTable, true); - } catch (e) { - console.error(e); + const result = tryWrap(() => interpret(newCode, this.symbolTable, true)); + applicationTerm.last = result; + if (result.error || !result.data) { + console.error(result.error); fail(); return; } @@ -128,29 +158,34 @@ export class FunctionApplication extends Entity { const { dimension } = gridSystem; const nextPosition = gridSystem.getNewGridPosition( grid.gridPosition, - entityGrid.previousDirection, + entityGrid.previousDirection ); let applicationResultingEntity: Entity | null = null; // this should be its own function - if ("abstraction" in result) { - const code = emitNamed(result); - + const { data } = result; + if ("application" in data) { + // if we get an application that means we didn't interpret correctly. + // this should "not" happen and should be fatal. + throw new InvalidLambdaTermError( + "produced term should not be an application" + ); + } + if ("abstraction" in data) { + const code = emitNamed(data); applicationResultingEntity = new FunctionBox(grid.gridPosition, code); - } else if ("name" in result) { - const { name } = result; + } + if ("name" in data) { + const { name } = data; const entityFactory = APPLICATION_RESULTS[name]; if (entityFactory) { game.addEntity(entityFactory(nextPosition)); } - } else { - fail(); - return; } game.removeEntity(entity.id); if (applicationResultingEntity) { const grid = applicationResultingEntity.getComponent<Grid>( - ComponentNames.Grid, + ComponentNames.Grid ); grid.movingDirection = entityGrid.previousDirection; applicationResultingEntity.addComponent(grid); @@ -158,7 +193,7 @@ export class FunctionApplication extends Entity { game.addEntity(applicationResultingEntity); } - this.playTransformSound(); + SOUNDS.get(LambdaTransformSound.name)!.play(); const particles = new Particles({ center: gridSystem.gridToScreenPosition(nextPosition), spawnerDimensions: { @@ -183,9 +218,4 @@ export class FunctionApplication extends Entity { }); game.addEntity(particles); } - - private playTransformSound() { - const audio = SOUNDS.get(LambdaTransformSound.name)!; - audio.play(); - } } diff --git a/src/engine/entities/FunctionBox.ts b/src/engine/entities/FunctionBox.ts index 4878c98..9cba029 100644 --- a/src/engine/entities/FunctionBox.ts +++ b/src/engine/entities/FunctionBox.ts @@ -1,13 +1,4 @@ -import { - IMAGES, - Miscellaneous, - ModalClose, - ModalOpen, - SOUNDS, - SPRITE_SPECS, - SpriteSpec, - Sprites, -} from "../config"; +import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; import { Entity, EntityNames } from "."; import { BoundingBox, @@ -16,18 +7,21 @@ import { Highlight, Interactable, LambdaTerm, + Modal, Pushable, Sprite, } from "../components"; import { Coord2D } from "../interfaces"; -import { openModal, closeModal } from "../utils"; export class FunctionBox extends Entity { private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( - Sprites.FUNCTION_BOX, + Sprites.FUNCTION_BOX ) as SpriteSpec; - constructor(gridPosition: Coord2D, code: string) { + constructor( + gridPosition: Coord2D, + private readonly code: string + ) { super(EntityNames.FunctionBox); this.addComponent( @@ -40,8 +34,8 @@ export class FunctionBox extends Entity { width: FunctionBox.spriteSpec.width, height: FunctionBox.spriteSpec.height, }, - 0, - ), + 0 + ) ); this.addComponent(new Pushable()); @@ -57,56 +51,43 @@ export class FunctionBox extends Entity { height: FunctionBox.spriteSpec.height, }, FunctionBox.spriteSpec.msPerFrame, - FunctionBox.spriteSpec.frames, - ), + FunctionBox.spriteSpec.frames + ) ); this.addComponent(new LambdaTerm(code)); - this.addComponent(makeLambdaTermHighlightComponent(this)); + this.addComponent( + new Highlight(this.onHighlight.bind(this), this.onUnhighlight.bind(this)) + ); } -} - -export const makeLambdaTermHighlightComponent = ( - entity: Entity, - text?: string, -) => { - const onUnhighlight = () => { - closeModal(); - entity.removeComponent(ComponentNames.Interactable); - }; - - const onHighlight = () => { - let modalOpen = false; - const doModalClose = () => { - SOUNDS.get(ModalClose.name)!.play(); - modalOpen = false; - closeModal(); - }; - - const interaction = () => { - if (modalOpen) { - doModalClose(); - return; - } - const code = - text ?? - entity.getComponent<LambdaTerm>(ComponentNames.LambdaTerm)!.code; - openModal( - `<div style="text-align:center"><p>${code}</p> <br> <button id="close">Close</button></div>`, - ); - modalOpen = true; - SOUNDS.get(ModalOpen.name)!.play(); - - document.getElementById("close")!.addEventListener("click", () => { - doModalClose(); - document.getElementById(Miscellaneous.CANVAS_ID)!.focus(); - }); + private interaction() { + const codeConsumer = (_code: string) => { + this.removeComponent(ComponentNames.Modal); + return { consumed: true }; }; + const { last } = this.getComponent<LambdaTerm>(ComponentNames.LambdaTerm); + this.addComponent( + new Modal({ + type: "CODE_EDITOR", + codeInit: { + code: this.code, + codeConsumer, + readonly: true, + result: { + error: last?.error && `Error: ${last.error.message}`, + }, + }, + }) + ); + } - entity.addComponent(new Interactable(interaction)); - }; + public onHighlight() { + this.addComponent(new Interactable(this.interaction.bind(this))); + } - return new Highlight(onHighlight, onUnhighlight); -}; + public onUnhighlight() { + this.removeComponent(ComponentNames.Interactable); + } +} diff --git a/src/engine/entities/LambdaFactory.ts b/src/engine/entities/LambdaFactory.ts index 770c096..61a3b0a 100644 --- a/src/engine/entities/LambdaFactory.ts +++ b/src/engine/entities/LambdaFactory.ts @@ -1,10 +1,7 @@ import { Failure, IMAGES, - LambdaSave, LambdaTransformSound, - Miscellaneous, - ModalOpen, SOUNDS, SPRITE_SPECS, SpriteSpec, @@ -19,66 +16,19 @@ import { GridSpawn, Highlight, Interactable, + Modal, Sprite, Text, } from "../components"; import { Coord2D, Direction } from "../interfaces"; -import { openModal, closeModal } from "../utils"; -import { - EditorState, - StateField, - StateEffect, - Range, - Extension, -} from "@codemirror/state"; -import { Decoration, EditorView, keymap } from "@codemirror/view"; -import { defaultKeymap } from "@codemirror/commands"; -import rainbowBrackets from "rainbowbrackets"; -import { basicSetup } from "codemirror"; +import { tryWrap } from "../utils"; import { parse } from "../../interpreter"; -interface CodeEditorState { - view: EditorView; - editorElement: HTMLElement; - syntaxError: HTMLElement; - canvas: HTMLCanvasElement; - closeButton: HTMLButtonElement; -} - -const highlightEffect = StateEffect.define<Range<Decoration>[]>(); -const highlightExtension = StateField.define({ - create() { - return Decoration.none; - }, - update(value, transaction) { - value = value.map(transaction.changes); - - for (let effect of transaction.effects) { - if (effect.is(highlightEffect)) - value = value.update({ add: effect.value, sort: true }); - } - - return value; - }, - provide: (f) => EditorView.decorations.from(f), -}); - -const FontSizeTheme = EditorView.theme({ - $: { - fontSize: "16pt", - }, -}); -const FontSizeThemeExtension: Extension = [FontSizeTheme]; -const syntaxErrorDecoration = Decoration.mark({ - class: "syntax-error", -}); - export class LambdaFactory extends Entity { private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( - Sprites.LAMBDA_FACTORY, + Sprites.LAMBDA_FACTORY ) as SpriteSpec; - private codeEditorState: CodeEditorState | null; private spawns: number; private code: string; @@ -87,7 +37,6 @@ export class LambdaFactory extends Entity { this.spawns = spawns; this.code = code; - this.codeEditorState = null; this.addComponent( new BoundingBox( @@ -99,8 +48,8 @@ export class LambdaFactory extends Entity { width: LambdaFactory.spriteSpec.width, height: LambdaFactory.spriteSpec.height, }, - 0, - ), + 0 + ) ); this.addComponent(new Text(spawns.toString())); @@ -110,8 +59,8 @@ export class LambdaFactory extends Entity { this.addComponent( new GridSpawn( this.spawns, - () => new FunctionBox({ x: 0, y: 0 }, this.code), - ), + () => new FunctionBox({ x: 0, y: 0 }, this.code) + ) ); this.addComponent(new Grid(gridPosition)); @@ -125,166 +74,68 @@ export class LambdaFactory extends Entity { height: LambdaFactory.spriteSpec.height, }, LambdaFactory.spriteSpec.msPerFrame, - LambdaFactory.spriteSpec.frames, - ), + LambdaFactory.spriteSpec.frames + ) ); this.addComponent( - new Highlight( - (direction) => this.onHighlight(direction), - () => this.onUnhighlight(), - ), + new Highlight(this.onHighlight.bind(this), this.onUnhighlight.bind(this)) ); } - private onUnhighlight() { - closeModal(); - this.removeComponent(ComponentNames.Interactable); - } - - private spawnNewLambda(direction: Direction) { - try { - parse(this.code); - } catch (e: any) { - SOUNDS.get(Failure.name)!.play(); - return; + private codeConsumer(code: string) { + const parsed = tryWrap(() => parse(code)); + if (parsed.error) { + return { error: parsed.error }; } - - const spawner = this.getComponent<GridSpawn>(ComponentNames.GridSpawn); - spawner.spawnEntity(direction); - - const textComponent = this.getComponent<Text>(ComponentNames.Text); - textComponent.text = spawner.spawnsLeft.toString(); - this.addComponent(textComponent); - - SOUNDS.get(LambdaTransformSound.name)!.play(); - } - - private openCodeEditor() { - const modalContent = - "<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>"; - openModal(modalContent); - - const startState = EditorState.create({ - doc: this.code, - extensions: [ - basicSetup, - keymap.of(defaultKeymap), - rainbowBrackets(), - highlightExtension, - FontSizeThemeExtension, - ], - }); - - const codeBox = document.getElementById("code")!; - const syntaxError = document.getElementById("syntax-error")!; - const canvas = document.getElementById( - Miscellaneous.CANVAS_ID, - ) as HTMLCanvasElement; - const closeButton = document.getElementById( - "close-modal", - ) as HTMLButtonElement; - closeButton.addEventListener("click", () => this.saveAndCloseCodeEditor()); - - const editorView = new EditorView({ - state: startState, - parent: codeBox, - }); - editorView.focus(); - - this.codeEditorState = { - view: editorView, - editorElement: codeBox, - syntaxError, - canvas, - closeButton, - }; - - SOUNDS.get(ModalOpen.name)!.play(); + this.code = code; + this.removeComponent(ComponentNames.Modal); + return { consumed: true }; } - private refreshCodeEditorText(text: string) { - if (!this.codeEditorState) { + private onHighlight(direction: Direction) { + if (direction === Direction.LEFT || direction === Direction.RIGHT) { + this.addComponent(new Interactable(() => this.spawnNewLambda(direction))); return; } - const { view } = this.codeEditorState; - view.dispatch({ - changes: { - from: 0, - to: text.length, - insert: "", - }, - }); - view.dispatch({ - changes: { - from: 0, - to: 0, - insert: text, - }, - }); + this.addComponent(new Interactable(this.interaction.bind(this))); } - private saveAndCloseCodeEditor() { - if (!this.codeEditorState) { + private interaction() { + if (this.hasComponent(ComponentNames.Modal)) { return; } - - const { canvas, view, editorElement, syntaxError } = this.codeEditorState; - const text = view.state.doc.toString(); - this.refreshCodeEditorText(text); - syntaxError.innerText = ""; - - try { - parse(text); - } catch (e: any) { - if (!e.location) { - return; - } - const { - location: { - start: { offset: start }, - end: { offset: end }, + this.addComponent( + new Modal({ + type: "CODE_EDITOR", + codeInit: { + code: this.code, + codeConsumer: this.codeConsumer.bind(this), }, - } = e; - - view.dispatch({ - effects: highlightEffect.of([ - syntaxErrorDecoration.range(start === end ? start - 1 : start, end), - ]), - }); - - syntaxError.innerText = e.message; - SOUNDS.get(Failure.name)!.play(); - return; - } - - this.code = text; - - view.destroy(); - editorElement.innerHTML = ""; - this.codeEditorState = null; - closeModal(); + }) + ); + } - canvas.focus(); - SOUNDS.get(LambdaSave.name)!.play(); + private onUnhighlight() { + this.removeComponent(ComponentNames.Modal); + this.removeComponent(ComponentNames.Interactable); } - private onHighlight(direction: Direction) { - if (direction === Direction.LEFT || direction === Direction.RIGHT) { - this.addComponent(new Interactable(() => this.spawnNewLambda(direction))); + private spawnNewLambda(direction: Direction) { + const parsed = tryWrap(() => parse(this.code)); + if (parsed.error) { + SOUNDS.get(Failure.name)!.play(); return; } - const interaction = () => { - if (this.codeEditorState) { - this.saveAndCloseCodeEditor(); - return; - } + const spawner = this.getComponent<GridSpawn>(ComponentNames.GridSpawn); + spawner.spawnEntity(direction); - this.openCodeEditor(); - }; + const textComponent = this.getComponent<Text>(ComponentNames.Text); + textComponent.text = spawner.spawnsLeft.toString(); + this.addComponent(textComponent); - this.addComponent(new Interactable(interaction)); + SOUNDS.get(LambdaTransformSound.name)!.play(); } } diff --git a/src/engine/entities/Sign.ts b/src/engine/entities/Sign.ts index 45f2986..c85ad40 100644 --- a/src/engine/entities/Sign.ts +++ b/src/engine/entities/Sign.ts @@ -1,18 +1,27 @@ -import { Entity, EntityNames, makeLambdaTermHighlightComponent } from "."; -import { BoundingBox, Colliding, Grid, Sprite } from "../components"; +import { Entity, EntityNames } from "."; +import { + BoundingBox, + Colliding, + ComponentNames, + Grid, + Highlight, + Interactable, + Modal, + Sprite, +} from "../components"; import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config"; import { Coord2D } from "../interfaces"; export class Sign extends Entity { private static spriteSpec: SpriteSpec = SPRITE_SPECS.get( - Sprites.SIGN, + Sprites.SIGN ) as SpriteSpec; - private text: string; - - constructor(text: string, gridPosition: Coord2D) { + constructor( + private readonly text: string, + gridPosition: Coord2D + ) { super(EntityNames.Sign); - this.text = text; const dimension = { width: Sign.spriteSpec.width, @@ -25,8 +34,8 @@ export class Sign extends Entity { { x: 0, y: 0 }, dimension, Sign.spriteSpec.msPerFrame, - Sign.spriteSpec.frames, - ), + Sign.spriteSpec.frames + ) ); this.addComponent( @@ -36,14 +45,40 @@ export class Sign extends Entity { y: 0, }, dimension, - 0, - ), + 0 + ) ); this.addComponent(new Grid(gridPosition)); this.addComponent(new Colliding()); - this.addComponent(makeLambdaTermHighlightComponent(this, this.text)); + this.addComponent( + new Highlight(this.onHighlight.bind(this), this.onUnhighlight.bind(this)) + ); + } + + private onHighlight() { + this.addComponent(new Interactable(this.interaction.bind(this))); + } + + private onUnhighlight() { + this.removeComponent(ComponentNames.Modal); + this.removeComponent(ComponentNames.Interactable); + } + + private interaction() { + if (this.hasComponent(ComponentNames.Modal)) { + this.removeComponent(ComponentNames.Modal); + return; + } + this.addComponent( + new Modal({ + type: "CONTENT", + contentInit: { + content: `<p>${this.text}</p>`, + }, + }) + ); } } |