diff --git a/public/assets/sign.png b/public/assets/sign.png new file mode 100644 index 0000000..a778b3b Binary files /dev/null and b/public/assets/sign.png differ diff --git a/src/components/GameCanvas.tsx b/src/components/GameCanvas.tsx index b6c585d..18dd52e 100644 --- a/src/components/GameCanvas.tsx +++ b/src/components/GameCanvas.tsx @@ -11,7 +11,7 @@ export interface GameCanvasProps { export const GameCanvas = ({ width, height }: GameCanvasProps) => { const canvasRef = useRef(null); const [game, setGame] = useState(); - const [ready, setReady] = useState(false); + const [ready, setReady] = useState(true); // false); const [loading, setLoading] = useState(true); useEffect(() => { diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts index 2c29406..4a0effa 100644 --- a/src/engine/TheAbstractionEngine.ts +++ b/src/engine/TheAbstractionEngine.ts @@ -33,7 +33,7 @@ export class TheAbstractionEngine { const facingDirectionSystem = new FacingDirection(inputSystem); [ - new Level(LevelNames.LevelSelection), + new Level(LevelNames.Tutorial), inputSystem, facingDirectionSystem, new Grid( diff --git a/src/engine/config/sprites.ts b/src/engine/config/sprites.ts index f8912d5..481dbb9 100644 --- a/src/engine/config/sprites.ts +++ b/src/engine/config/sprites.ts @@ -11,6 +11,7 @@ export enum Sprites { BUBBLE, PORTAL, GRASS, + SIGN, } export interface SpriteSpec { @@ -126,3 +127,12 @@ const grassSpriteSpec = { sheet: "/assets/grass.png", }; SPRITE_SPECS.set(Sprites.GRASS, grassSpriteSpec); + +const signSpriteSpec = { + msPerFrame: 200, + width: 64, + height: 64, + frames: 3, + sheet: "/assets/sign.png", +}; +SPRITE_SPECS.set(Sprites.SIGN, signSpriteSpec); diff --git a/src/engine/entities/EntityNames.ts b/src/engine/entities/EntityNames.ts index b4fbbb2..17b5519 100644 --- a/src/engine/entities/EntityNames.ts +++ b/src/engine/entities/EntityNames.ts @@ -10,4 +10,5 @@ export namespace EntityNames { export const Particles = "Particles"; export const Portal = "Portal"; export const Grass = "Grass"; + export const Sign = "Sign"; } diff --git a/src/engine/entities/FunctionApplication.ts b/src/engine/entities/FunctionApplication.ts index d907eca..175534c 100644 --- a/src/engine/entities/FunctionApplication.ts +++ b/src/engine/entities/FunctionApplication.ts @@ -34,6 +34,10 @@ import { interpret, } from "../../interpreter"; +const APPLICATION_RESULTS: Record Entity> = { + _KEY: (gridPosition: Coord2D) => new Key(gridPosition), +}; + export class FunctionApplication extends Entity { private static spriteSpec = SPRITE_SPECS.get(Sprites.BUBBLE) as SpriteSpec; @@ -43,7 +47,9 @@ export class FunctionApplication extends Entity { super(EntityNames.FunctionApplication); this.symbolTable = new SymbolTable(); - this.symbolTable.add("key"); + Object.keys(APPLICATION_RESULTS).forEach((key) => { + this.symbolTable.add(key); + }); const dimension = { width: FunctionApplication.spriteSpec.width, @@ -109,9 +115,10 @@ export class FunctionApplication extends Entity { ComponentNames.LambdaTerm ); const newCode = applicationTerm.code.replace("_INPUT", functionTerm.code); + let result: DebrujinifiedLambdaTerm | null = null; try { - result = interpret(newCode, this.symbolTable); + result = interpret(newCode, this.symbolTable, true); } catch (e) { console.error(e); fail(); @@ -131,8 +138,9 @@ export class FunctionApplication extends Entity { applicationResultingEntity = new FunctionBox(grid.gridPosition, code); } else if ("name" in result) { const { name } = result; - if (name === "key") { - applicationResultingEntity = new Key(grid.gridPosition); + const entityFactory = APPLICATION_RESULTS[name]; + if (entityFactory) { + game.addEntity(entityFactory(nextPosition)); } } else { fail(); diff --git a/src/engine/entities/FunctionBox.ts b/src/engine/entities/FunctionBox.ts index dac2174..7b70567 100644 --- a/src/engine/entities/FunctionBox.ts +++ b/src/engine/entities/FunctionBox.ts @@ -67,7 +67,10 @@ export class FunctionBox extends Entity { } } -export const makeLambdaTermHighlightComponent = (entity: Entity) => { +export const makeLambdaTermHighlightComponent = ( + entity: Entity, + text?: string +) => { const onUnhighlight = () => { closeModal(); entity.removeComponent(ComponentNames.Interactable); @@ -87,9 +90,9 @@ export const makeLambdaTermHighlightComponent = (entity: Entity) => { return; } - const code = entity.getComponent( - ComponentNames.LambdaTerm - )!.code; + const code = + text ?? + entity.getComponent(ComponentNames.LambdaTerm)!.code; openModal( `

${code}


` ); diff --git a/src/engine/entities/Sign.ts b/src/engine/entities/Sign.ts new file mode 100644 index 0000000..a11fba6 --- /dev/null +++ b/src/engine/entities/Sign.ts @@ -0,0 +1,49 @@ +import { Entity, EntityNames, makeLambdaTermHighlightComponent } from "."; +import { BoundingBox, Colliding, Grid, 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 + ) as SpriteSpec; + + private text: string; + + constructor(text: string, gridPosition: Coord2D) { + super(EntityNames.Sign); + this.text = text; + + const dimension = { + width: Sign.spriteSpec.width, + height: Sign.spriteSpec.height, + }; + + this.addComponent( + new Sprite( + IMAGES.get(Sign.spriteSpec.sheet)!, + { x: 0, y: 0 }, + dimension, + Sign.spriteSpec.msPerFrame, + Sign.spriteSpec.frames + ) + ); + + this.addComponent( + new BoundingBox( + { + x: 0, + y: 0, + }, + dimension, + 0 + ) + ); + + this.addComponent(new Grid(gridPosition)); + + this.addComponent(new Colliding()); + + this.addComponent(makeLambdaTermHighlightComponent(this, this.text)); + } +} diff --git a/src/engine/entities/index.ts b/src/engine/entities/index.ts index 260db5b..284a2bd 100644 --- a/src/engine/entities/index.ts +++ b/src/engine/entities/index.ts @@ -11,3 +11,4 @@ export * from "./FunctionApplication"; export * from "./Particles"; export * from "./Portal"; export * from "./Grass"; +export * from "./Sign"; diff --git a/src/engine/levels/Tutorial.ts b/src/engine/levels/Tutorial.ts index 694c8ff..b720346 100644 --- a/src/engine/levels/Tutorial.ts +++ b/src/engine/levels/Tutorial.ts @@ -6,6 +6,7 @@ import { LambdaFactory, LockedDoor, Player, + Sign, Wall, } from "../entities"; @@ -16,14 +17,15 @@ export class Tutorial extends Level { public init(game: Game): void { const entities = [ - new Player({ x: 2, y: 2 }), + new Sign("TODO: Explain entities", { x: 4, y: 3 }), new Wall({ x: 10, y: 9 }), new Wall({ x: 10, y: 11 }), 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 FunctionApplication({ x: 6, y: 6 }, "(_INPUT key)"), + new FunctionApplication({ x: 6, y: 6 }, "(_INPUT _KEY)"), + new Player({ x: 2, y: 2 }), ]; entities.forEach((entity) => game.addEntity(entity)); diff --git a/src/interpreter/PeggyParser.js b/src/interpreter/PeggyParser.js index 121cf9d..f5376b3 100644 --- a/src/interpreter/PeggyParser.js +++ b/src/interpreter/PeggyParser.js @@ -215,7 +215,7 @@ export default (function () { var peg$c2 = "."; var peg$c3 = "\r\n"; - var peg$r0 = /^[a-zA-Z0-9]/; + var peg$r0 = options.allowUnderscores ? /^[a-zA-Z0-9_]/ : /^[a-zA-Z0-9]/; var peg$r1 = /^[\\\u03BB]/; var peg$r2 = /^[\t-\n ]/; diff --git a/src/interpreter/parser.ts b/src/interpreter/parser.ts index 5e3be0f..930b953 100644 --- a/src/interpreter/parser.ts +++ b/src/interpreter/parser.ts @@ -30,6 +30,10 @@ export const isVariable = (term: LambdaTerm): term is Variable => { return typeof term === "string"; }; -export const parse = (term: string, library = false) => { - return peggyParser.parse(term, { peg$library: library }); +export const parse = ( + term: string, + allowUnderscores = false, + library = false +) => { + return peggyParser.parse(term, { peg$library: library, allowUnderscores }); };