import { Entity, EntityNames, FunctionBox, Key, Particles } from "."; import { BoundingBox, Colliding, ComponentNames, Grid, Highlight, Interactable, LambdaTerm, Modal, Sprite, } from "../components"; import { Failure, IMAGES, LambdaTransformSound, SOUNDS, SPRITE_SPECS, SpriteSpec, Sprites, } from "../config"; import { Coord2D, Direction } from "../interfaces"; import { Game } from ".."; import { Grid as GridSystem, SystemNames } from "../systems"; import { colors, tryWrap } from "../utils"; import { InvalidLambdaTermError, SymbolTable, emitNamed, interpret, } from "../../interpreter"; const APPLICATION_RESULTS: Record null | Entity> = { _KEY: (gridPosition: Coord2D) => new Key(gridPosition), _EMPTY: (_gridPosition: Coord2D) => null, }; 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, }; this.addComponent( new BoundingBox( { x: 0, y: 0, }, dimension, 0 ) ); this.addComponent(new Grid(gridPosition)); this.addComponent(new LambdaTerm(lambdaTerm)); this.addComponent( new Sprite( IMAGES.get(FunctionApplication.spriteSpec.sheet)!, { x: 0, y: 0 }, dimension, FunctionApplication.spriteSpec.msPerFrame, FunctionApplication.spriteSpec.frames ) ); this.addComponent(new Colliding(this.handleCollision.bind(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( 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) { if (entity.name !== EntityNames.FunctionBox) { return; } const entityGrid = entity.getComponent(ComponentNames.Grid); if (entityGrid.movingDirection !== Direction.NONE) { // prevent recursive functionBox -> application creation return; } const grid = this.getComponent(ComponentNames.Grid); const gridSystem = game.getSystem(SystemNames.Grid); const fail = () => { entityGrid.movingDirection = gridSystem.oppositeDirection( entityGrid.previousDirection ); entity.addComponent(entityGrid); const failureSound = SOUNDS.get(Failure.name)!; failureSound.play(); }; const applicationTerm = this.getComponent( ComponentNames.LambdaTerm ); const functionTerm = entity.getComponent( ComponentNames.LambdaTerm ); const newCode = applicationTerm.code.replace("_INPUT", functionTerm.code); const result = tryWrap(() => interpret(newCode, this.symbolTable, true)); applicationTerm.last = result; if (result.error || !result.data) { console.error(result.error); fail(); return; } const nextPosition = gridSystem.getNewGridPosition( grid.gridPosition, entityGrid.previousDirection ); 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); } if ("name" in data) { const { name } = data; const entityFactory = APPLICATION_RESULTS[name]; if (entityFactory) { const entity = entityFactory(nextPosition) entity && game.addEntity(entity); } } game.removeEntity(entity.id); if (applicationResultingEntity) { const grid = applicationResultingEntity.getComponent( ComponentNames.Grid ); grid.movingDirection = entityGrid.previousDirection; applicationResultingEntity.addComponent(grid); game.addEntity(applicationResultingEntity); } SOUNDS.get(LambdaTransformSound.name)!.play(); const { dimension } = gridSystem; const particles = new Particles({ center: gridSystem.gridToScreenPosition(nextPosition), spawnerDimensions: { width: dimension.width / 2, height: dimension.height / 2, }, particleCount: 10, spawnerShape: "circle", particleShape: "circle", particleMeanSpeed: 0.25, particleSpeedVariance: 0.15, particleMeanLife: 150, particleMeanSize: 2, particleSizeVariance: 1, particleLifeVariance: 20, particleColors: [ colors.lightAqua, colors.blue, colors.green, colors.lightGreen, ], }); game.addEntity(particles); } }