summaryrefslogtreecommitdiff
path: root/src/engine
diff options
context:
space:
mode:
authorElizabeth Hunt <elizabeth@simponic.xyz>2025-03-06 08:44:43 -0700
committerElizabeth Hunt <elizabeth@simponic.xyz>2025-03-06 08:44:43 -0700
commit958134419d7913dc7dda0d4cd1982c51d8bd1a23 (patch)
treed06b7f84be8d43db63e360efedf467fe3d803217 /src/engine
parent78797aa175651d53df21d3f8d5c51a55649aaced (diff)
downloadthe-abstraction-engine-church-numerals.tar.gz
the-abstraction-engine-church-numerals.zip
checkpointchurch-numerals
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/TheAbstractionEngine.ts4
-rw-r--r--src/engine/entities/FunctionApplication.ts120
-rw-r--r--src/engine/levels/CarCadr.ts2
-rw-r--r--src/engine/levels/ChurchNumeralsOne.ts45
-rw-r--r--src/engine/levels/LevelNames.ts1
-rw-r--r--src/engine/levels/Tutorial.ts3
-rw-r--r--src/engine/levels/index.ts3
7 files changed, 149 insertions, 29 deletions
diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts
index 63c6274..793de7d 100644
--- a/src/engine/TheAbstractionEngine.ts
+++ b/src/engine/TheAbstractionEngine.ts
@@ -38,7 +38,9 @@ export class TheAbstractionEngine {
[
new RadialObserve(),
new Modal(),
- new Level(isDev ? LevelNames.CarCadr : LevelNames.LevelSelection),
+ new Level(
+ isDev ? LevelNames.ChurchNumeralsOne : LevelNames.LevelSelection,
+ ),
inputSystem,
facingDirectionSystem,
new Grid(
diff --git a/src/engine/entities/FunctionApplication.ts b/src/engine/entities/FunctionApplication.ts
index 4d5729f..f4201de 100644
--- a/src/engine/entities/FunctionApplication.ts
+++ b/src/engine/entities/FunctionApplication.ts
@@ -24,8 +24,9 @@ import { Game } from "..";
import { Grid as GridSystem, SystemNames } from "../systems";
import { colors, tryWrap } from "../utils";
import {
- InvalidLambdaTermError,
+ DebrujinIndex,
SymbolTable,
+ Visitors,
emitNamed,
interpret,
} from "../../interpreter";
@@ -41,16 +42,9 @@ const APPLICATION_RESULTS: Record<
export class FunctionApplication extends Entity {
private static spriteSpec = SPRITE_SPECS.get(Sprites.BUBBLE) as SpriteSpec;
- private symbolTable: SymbolTable;
-
constructor(gridPosition: Coord2D, lambdaTerm: string) {
super(EntityNames.FunctionApplication);
- this.symbolTable = new SymbolTable();
- Object.keys(APPLICATION_RESULTS).forEach((key) => {
- this.symbolTable.add(key);
- });
-
const dimension = {
width: FunctionApplication.spriteSpec.width,
height: FunctionApplication.spriteSpec.height,
@@ -151,7 +145,10 @@ export class FunctionApplication extends Entity {
);
const newCode = applicationTerm.code.replace("_INPUT", functionTerm.code);
- const result = tryWrap(() => interpret(newCode, this.symbolTable, true));
+ const { symbolTable, visitors } = this.getVisitors(game);
+ const result = tryWrap(() =>
+ interpret(newCode, symbolTable, true, visitors),
+ );
applicationTerm.last = result;
if (result.error || !result.data) {
console.error(result.error);
@@ -166,13 +163,6 @@ export class FunctionApplication extends Entity {
let applicationResultingEntity: Entity | null = null; // this should be its own function
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);
@@ -187,20 +177,100 @@ export class FunctionApplication extends Entity {
}
game.removeEntity(entity.id);
- if (applicationResultingEntity) {
- const grid = applicationResultingEntity.getComponent<Grid>(
+ if (!applicationResultingEntity) {
+ return;
+ }
+
+ applicationResultingEntity.getComponent<Grid>(
+ ComponentNames.Grid,
+ ).movingDirection = entityGrid.previousDirection;
+ game.addEntity(applicationResultingEntity);
+ }
+
+ private getVisitors(game: Game): {
+ visitors: Visitors;
+ symbolTable: SymbolTable;
+ } {
+ const directionKeywords = {
+ _LEFT: Direction.LEFT,
+ _RIGHT: Direction.RIGHT,
+ _DOWN: Direction.DOWN,
+ _UP: Direction.UP,
+ };
+ const entityKeywords = {
+ _KEY: (pos: Coord2D) => new Key(pos),
+ };
+
+ const visitors: Visitors = new Map();
+ visitors.set("_SPAWN", (_term) => {
+ const position = this.getComponent<Grid>(
ComponentNames.Grid,
- );
- grid.movingDirection = entityGrid.previousDirection;
- applicationResultingEntity.addComponent(grid);
+ ).gridPosition;
+ return {
+ abstraction: {
+ param: "_DIRECTION",
+ body: (direction) => {
+ const destinationDirection =
+ directionKeywords[
+ (direction as DebrujinIndex)
+ .name! as keyof typeof directionKeywords
+ ];
+ const destination = game
+ .getSystem<GridSystem>(SystemNames.Grid)
+ .getNewGridPosition(position, destinationDirection);
+ return {
+ abstraction: {
+ param: "_ENTITY",
+ body: (entityType) => {
+ const entityFactory =
+ entityKeywords[
+ (entityType as DebrujinIndex)
+ .name! as keyof typeof entityKeywords
+ ];
+ const newEntity = entityFactory(destination);
+ game.addEntity(newEntity);
+ return {
+ abstraction: {
+ param: "_x",
+ body: (_t) => {
+ return {
+ application: {
+ left: {
+ index: 1,
+ name: "_SPAWN",
+ },
+ args: [direction, entityType],
+ },
+ };
+ },
+ },
+ };
+ },
+ },
+ };
+ },
+ },
+ };
+ });
- game.addEntity(applicationResultingEntity);
- }
+ return {
+ visitors,
+ symbolTable: SymbolTable.from(
+ Array.from(visitors.keys())
+ .concat(Object.keys(APPLICATION_RESULTS))
+ .concat(Object.keys(directionKeywords))
+ .concat(Object.keys(entityKeywords))
+ .concat(["_x"]),
+ ),
+ };
+ }
- SOUNDS.get(LambdaTransformSound.name)!.play();
+ private addParticles(game: Game, position: Coord2D) {
+ const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid);
const { dimension } = gridSystem;
+ SOUNDS.get(LambdaTransformSound.name)!.play();
const particles = new Particles({
- center: gridSystem.gridToScreenPosition(nextPosition),
+ center: gridSystem.gridToScreenPosition(position),
spawnerDimensions: {
width: dimension.width / 2,
height: dimension.height / 2,
diff --git a/src/engine/levels/CarCadr.ts b/src/engine/levels/CarCadr.ts
index 10ff6d9..8875623 100644
--- a/src/engine/levels/CarCadr.ts
+++ b/src/engine/levels/CarCadr.ts
@@ -9,8 +9,6 @@ import {
Player,
Wall,
} from "../entities";
-import { Piston } from "../entities/Piston";
-import { Direction } from "../interfaces";
import { Grid, SystemNames } from "../systems";
import { normalRandom } from "../utils";
diff --git a/src/engine/levels/ChurchNumeralsOne.ts b/src/engine/levels/ChurchNumeralsOne.ts
new file mode 100644
index 0000000..2dbb6b5
--- /dev/null
+++ b/src/engine/levels/ChurchNumeralsOne.ts
@@ -0,0 +1,45 @@
+import { FunctionApplication, Grass, LambdaFactory, Player } from "../entities";
+import { Game } from "../Game";
+import { Grid, SystemNames } from "../systems";
+import { normalRandom } from "../utils";
+import { Level } from "./Level";
+import { LevelNames } from "./LevelNames";
+
+export class ChurchNumeralsOne extends Level {
+ constructor() {
+ super(LevelNames.ChurchNumeralsOne);
+ }
+
+ public init(game: Game) {
+ const grid = game.getSystem<Grid>(SystemNames.Grid);
+ const dimensions = grid.getGridDimensions();
+
+ const grasses = Array.from({ length: dimensions.width })
+ .fill(0)
+ .map(() => {
+ // random grass
+ return new Grass({
+ x: Math.floor(
+ normalRandom(dimensions.width / 2, dimensions.width / 4, 1.5),
+ ),
+ y: Math.floor(
+ normalRandom(dimensions.height / 2, dimensions.height / 4, 1.5),
+ ),
+ });
+ });
+
+ [
+ ...grasses,
+ new LambdaFactory({ x: 1, y: 1 }, "(\\ (f) . (\\ (x) . (f f x)))", 1),
+ new FunctionApplication(
+ { x: 2, y: 2 },
+ "(_INPUT ((_SPAWN _RIGHT) _KEY))",
+ ),
+ new FunctionApplication(
+ { x: 3, y: 3 },
+ "(_INPUT _EMPTY)",
+ ),
+ new Player({ x: 0, y: 0 }),
+ ].forEach((e) => game.addEntity(e));
+ }
+}
diff --git a/src/engine/levels/LevelNames.ts b/src/engine/levels/LevelNames.ts
index 7f3c4f1..c8182ab 100644
--- a/src/engine/levels/LevelNames.ts
+++ b/src/engine/levels/LevelNames.ts
@@ -1,5 +1,6 @@
export namespace LevelNames {
export const Tutorial = "0";
export const CarCadr = "1";
+ export const ChurchNumeralsOne = "2";
export const LevelSelection = "LevelSelection";
}
diff --git a/src/engine/levels/Tutorial.ts b/src/engine/levels/Tutorial.ts
index 97a6826..fc927da 100644
--- a/src/engine/levels/Tutorial.ts
+++ b/src/engine/levels/Tutorial.ts
@@ -36,6 +36,7 @@ export class Tutorial extends Level {
});
});
+ // TODO: new level which adds introductory syntax
const entities = [
...grasses,
new Sign(
@@ -51,7 +52,7 @@ export class Tutorial extends Level {
new Wall({ x: 11, y: 10 }),
new Curry({ x: 10, y: 10 }),
new LockedDoor({ x: 9, y: 10 }),
- new LambdaFactory({ x: 6, y: 3 }, "// TODO: Remove line\n(λ (x) . x)", 3),
+ new LambdaFactory({ x: 6, y: 3 }, "// TODO: Remove this comment\n(λ (x) . x)", 3),
new FunctionApplication({ x: 6, y: 6 }, "(_INPUT _KEY)"),
new Player({ x: 2, y: 2 }),
];
diff --git a/src/engine/levels/index.ts b/src/engine/levels/index.ts
index 216453c..f47000b 100644
--- a/src/engine/levels/index.ts
+++ b/src/engine/levels/index.ts
@@ -6,13 +6,16 @@ export * from "./CarCadr";
import { LevelNames } from ".";
import { CarCadr, LevelSelection, Tutorial, Level } from ".";
+import { ChurchNumeralsOne } from "./ChurchNumeralsOne";
export const LEVELS: Level[] = [
new LevelSelection(),
new Tutorial(),
new CarCadr(),
+ new ChurchNumeralsOne(),
];
export const LEVEL_PROGRESSION: Record<string, string[]> = {
[LevelNames.LevelSelection]: [LevelNames.Tutorial],
[LevelNames.Tutorial]: [LevelNames.CarCadr],
+ [LevelNames.CarCadr]: [LevelNames.ChurchNumeralsOne],
};