summaryrefslogtreecommitdiff
path: root/src/engine/entities
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth@simponic.xyz>2025-03-01 12:36:47 -0700
committerElizabeth Hunt <elizabeth@simponic.xyz>2025-03-01 12:36:47 -0700
commit8dacee8f73633131fd68935c1e2493dc4beec837 (patch)
treefc9adf76fce4761b01208ba2f44e72a6838244aa /src/engine/entities
parentd903bd9a13e790cf42c84c3dc59bf89ffeae1d80 (diff)
downloadthe-abstraction-engine-8dacee8f73633131fd68935c1e2493dc4beec837.tar.gz
the-abstraction-engine-8dacee8f73633131fd68935c1e2493dc4beec837.zip
updates
Diffstat (limited to 'src/engine/entities')
-rw-r--r--src/engine/entities/FunctionApplication.ts108
-rw-r--r--src/engine/entities/FunctionBox.ts99
-rw-r--r--src/engine/entities/LambdaFactory.ts239
-rw-r--r--src/engine/entities/Sign.ts59
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>`,
+ },
+ })
+ );
}
}