diff --git a/public/assets/function_factory.png b/public/assets/function_factory.png
index 2c46758..e070be6 100644
Binary files a/public/assets/function_factory.png and b/public/assets/function_factory.png differ
diff --git a/src/App.tsx b/src/App.tsx
index aadff37..0ae052f 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -30,7 +30,7 @@ export const App = () => {
simponic
{" "}
| inspired by{" "}
-
+
baba is you
diff --git a/src/css/modal.css b/src/css/modal.css
index a609ef9..c10070e 100644
--- a/src/css/modal.css
+++ b/src/css/modal.css
@@ -1,3 +1,5 @@
+@import url("./colors.css");
+
.modal {
display: none;
position: fixed;
@@ -15,11 +17,11 @@
.modal-content {
display: flex;
- background-color: #282828;
- color: #ebdbb2;
+ background-color: var(--bg);
+ color: var(--text);
margin: auto;
padding: 20px;
- border: 1px solid #928374;
+ border: 1px solid var(--yellow);
width: 40%;
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2);
animation: scaleUp 0.25s;
diff --git a/src/engine/TheAbstractionEngine.ts b/src/engine/TheAbstractionEngine.ts
index 2db599b..7f71fd5 100644
--- a/src/engine/TheAbstractionEngine.ts
+++ b/src/engine/TheAbstractionEngine.ts
@@ -1,7 +1,13 @@
import { Game } from ".";
import { Miscellaneous, loadAssets } from "./config";
-import { Player, FunctionBox, Wall } from "./entities";
-import { Grid, FacingDirection, Input, Render } from "./systems";
+import { Player, FunctionBox, Wall, LambdaFactory } from "./entities";
+import {
+ Grid,
+ FacingDirection,
+ Input,
+ Render,
+ LambdaFactory as LambdaFactorySpawnSystem,
+} from "./systems";
export class TheAbstractionEngine {
private game: Game;
@@ -32,6 +38,7 @@ export class TheAbstractionEngine {
height: Miscellaneous.GRID_CELL_HEIGHT,
},
),
+ new LambdaFactorySpawnSystem(),
new Render(this.ctx),
].forEach((system) => this.game.addSystem(system));
@@ -45,6 +52,9 @@ export class TheAbstractionEngine {
const wall = new Wall({ x: 5, y: 3 });
this.game.addEntity(wall);
+
+ const factory = new LambdaFactory({ x: 6, y: 6 }, "λ x . (x)", 10);
+ this.game.addEntity(factory);
}
public play() {
diff --git a/src/engine/components/ComponentNames.ts b/src/engine/components/ComponentNames.ts
index 5e9b589..1f41d18 100644
--- a/src/engine/components/ComponentNames.ts
+++ b/src/engine/components/ComponentNames.ts
@@ -8,4 +8,6 @@ export namespace ComponentNames {
export const Interactable = "Interactable";
export const Pushable = "Pushable";
export const Colliding = "Colliding";
+ export const LambdaSpawn = "LambdaSpawn";
+ export const Text = "Text";
}
diff --git a/src/engine/components/Highlight.ts b/src/engine/components/Highlight.ts
index 5875057..66ec74b 100644
--- a/src/engine/components/Highlight.ts
+++ b/src/engine/components/Highlight.ts
@@ -1,4 +1,5 @@
import { Component, ComponentNames } from ".";
+import { Direction } from "../interfaces";
export class Highlight extends Component {
public isHighlighted: boolean;
@@ -6,8 +7,8 @@ export class Highlight extends Component {
private onUnhighlight: Function;
constructor(
- onHighlight: Function,
- onUnhighlight: Function,
+ onHighlight: (direction: Direction) => void,
+ onUnhighlight: () => void,
isHighlighted: boolean = false,
) {
super(ComponentNames.Highlight);
@@ -17,10 +18,10 @@ export class Highlight extends Component {
this.onUnhighlight = onUnhighlight;
}
- public highlight() {
+ public highlight(direction: Direction) {
if (!this.isHighlighted) {
this.isHighlighted = true;
- this.onHighlight();
+ this.onHighlight(direction);
}
}
diff --git a/src/engine/components/LambdaSpawn.ts b/src/engine/components/LambdaSpawn.ts
new file mode 100644
index 0000000..c45092a
--- /dev/null
+++ b/src/engine/components/LambdaSpawn.ts
@@ -0,0 +1,29 @@
+import { Component, ComponentNames } from ".";
+import { Direction } from "../interfaces";
+
+export class LambdaSpawn extends Component {
+ public direction: Direction | null;
+ public spawnsLeft: number;
+ public code: string = "";
+
+ constructor(
+ spawnsLeft: number,
+ code: string,
+ direction: Direction | null = null,
+ ) {
+ super(ComponentNames.LambdaSpawn);
+
+ this.spawnsLeft = spawnsLeft;
+ this.direction = direction;
+ this.code = code;
+ }
+
+ public spawn(direction: Direction) {
+ if (this.spawnsLeft <= 0) {
+ return;
+ }
+
+ this.direction = direction;
+ this.spawnsLeft -= 1;
+ }
+}
diff --git a/src/engine/components/Sprite.ts b/src/engine/components/Sprite.ts
index 82d7011..c623bac 100644
--- a/src/engine/components/Sprite.ts
+++ b/src/engine/components/Sprite.ts
@@ -46,7 +46,7 @@ export class Sprite extends Component {
}
public draw(ctx: CanvasRenderingContext2D, drawArgs: DrawArgs) {
- const { center, rotation, tint, opacity } = drawArgs;
+ const { center, rotation, tint, opacity, backgroundText } = drawArgs;
ctx.save();
ctx.translate(center.x, center.y);
@@ -59,6 +59,17 @@ export class Sprite extends Component {
ctx.globalAlpha = opacity;
}
+ if (backgroundText) {
+ // draw text
+ const { fillStyle, font, textAlign, text } = backgroundText;
+ ctx.fillStyle = fillStyle;
+ ctx.font = font;
+ ctx.textAlign = textAlign;
+
+ const height = ctx.measureText("M").width;
+ ctx.fillText(text, center.x, center.y + height / 2);
+ }
+
ctx.drawImage(
this.sheet,
...this.getSpriteArgs(),
diff --git a/src/engine/components/Text.ts b/src/engine/components/Text.ts
new file mode 100644
index 0000000..94dc7a7
--- /dev/null
+++ b/src/engine/components/Text.ts
@@ -0,0 +1,22 @@
+import { Component, ComponentNames } from ".";
+
+export class Text extends Component {
+ public text: string = "";
+ public fillStyle: string;
+ public font: string;
+ public textAlign: CanvasTextAlign;
+
+ constructor(
+ text: string,
+ fillStyle = "white",
+ font = "25px scientifica",
+ textAlign: CanvasTextAlign = "center",
+ ) {
+ super(ComponentNames.Text);
+
+ this.text = text;
+ this.fillStyle = fillStyle;
+ this.font = font;
+ this.textAlign = textAlign;
+ }
+}
diff --git a/src/engine/components/index.ts b/src/engine/components/index.ts
index 4ae886a..104ba2d 100644
--- a/src/engine/components/index.ts
+++ b/src/engine/components/index.ts
@@ -9,3 +9,5 @@ export * from "./Highlight";
export * from "./Interactable";
export * from "./Pushable";
export * from "./Colliding";
+export * from "./LambdaSpawn";
+export * from "./Text";
diff --git a/src/engine/config/assets.ts b/src/engine/config/assets.ts
index bf41461..5ce13e8 100644
--- a/src/engine/config/assets.ts
+++ b/src/engine/config/assets.ts
@@ -1,5 +1,10 @@
import { type SpriteSpec, SPRITE_SPECS } from ".";
+export const FONT = new FontFace("scientifica", "url(/fonts/scientifica.ttf)");
+FONT.load().then((font) => {
+ document.fonts.add(font);
+});
+
export const IMAGES = new Map();
export const loadSpritesIntoImageElements = (
@@ -37,5 +42,6 @@ export const loadAssets = () =>
(key) => SPRITE_SPECS.get(key) as SpriteSpec,
),
),
+ FONT.load(),
// TODO: Sound
]);
diff --git a/src/engine/config/sprites.ts b/src/engine/config/sprites.ts
index 7cb8adf..39ad260 100644
--- a/src/engine/config/sprites.ts
+++ b/src/engine/config/sprites.ts
@@ -4,6 +4,7 @@ export enum Sprites {
PLAYER,
FUNCTION_BOX,
WALL,
+ LAMBDA_FACTORY,
}
export interface SpriteSpec {
@@ -56,3 +57,12 @@ const wallSpriteSpec = {
sheet: "/assets/wall.png",
};
SPRITE_SPECS.set(Sprites.WALL, wallSpriteSpec);
+
+const lambdaFactorySpriteSpec = {
+ msPerFrame: 200,
+ width: 64,
+ height: 64,
+ frames: 3,
+ sheet: "/assets/function_factory.png",
+};
+SPRITE_SPECS.set(Sprites.LAMBDA_FACTORY, lambdaFactorySpriteSpec);
diff --git a/src/engine/entities/EntityNames.ts b/src/engine/entities/EntityNames.ts
index ffcc937..3ad31d0 100644
--- a/src/engine/entities/EntityNames.ts
+++ b/src/engine/entities/EntityNames.ts
@@ -2,4 +2,5 @@ export namespace EntityNames {
export const Player = "Player";
export const FunctionBox = "FunctionBox";
export const Wall = "Wall";
+ export const LambdaFactory = "LambdaFactory";
}
diff --git a/src/engine/entities/FunctionBox.ts b/src/engine/entities/FunctionBox.ts
index e5d031a..b7015f2 100644
--- a/src/engine/entities/FunctionBox.ts
+++ b/src/engine/entities/FunctionBox.ts
@@ -57,7 +57,7 @@ export class FunctionBox extends Entity {
this.addComponent(
new Highlight(
- () => this.onHighlight(),
+ (_direction) => this.onHighlight(),
() => this.onUnhighlight(),
),
);
diff --git a/src/engine/entities/LambdaFactory.ts b/src/engine/entities/LambdaFactory.ts
new file mode 100644
index 0000000..1483b9d
--- /dev/null
+++ b/src/engine/entities/LambdaFactory.ts
@@ -0,0 +1,109 @@
+import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
+import { Entity, EntityNames } from ".";
+import {
+ BoundingBox,
+ Colliding,
+ ComponentNames,
+ Grid,
+ Highlight,
+ Interactable,
+ LambdaSpawn,
+ Sprite,
+ Text,
+} from "../components";
+import { Coord2D, Direction } from "../interfaces";
+import { openModal, closeModal } from "../utils";
+
+export class LambdaFactory extends Entity {
+ private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
+ Sprites.LAMBDA_FACTORY,
+ ) as SpriteSpec;
+
+ private code: string;
+ private spawns: number;
+
+ constructor(gridPosition: Coord2D, code: string, spawns: number) {
+ super(EntityNames.LambdaFactory);
+
+ this.code = code;
+ this.spawns = spawns;
+
+ this.addComponent(
+ new BoundingBox(
+ {
+ x: 0,
+ y: 0,
+ },
+ {
+ width: LambdaFactory.spriteSpec.width,
+ height: LambdaFactory.spriteSpec.height,
+ },
+ 0,
+ ),
+ );
+
+ this.addComponent(new Text(spawns.toString()));
+
+ this.addComponent(new Colliding());
+
+ this.addComponent(new LambdaSpawn(this.spawns, this.code));
+
+ this.addComponent(new Grid(gridPosition));
+
+ this.addComponent(
+ new Sprite(
+ IMAGES.get(LambdaFactory.spriteSpec.sheet)!,
+ { x: 0, y: 0 },
+ {
+ width: LambdaFactory.spriteSpec.width,
+ height: LambdaFactory.spriteSpec.height,
+ },
+ LambdaFactory.spriteSpec.msPerFrame,
+ LambdaFactory.spriteSpec.frames,
+ ),
+ );
+
+ this.addComponent(
+ new Highlight(
+ (direction) => this.onHighlight(direction),
+ () => this.onUnhighlight(),
+ ),
+ );
+ }
+
+ private onUnhighlight() {
+ closeModal();
+ this.removeComponent(ComponentNames.Interactable);
+ }
+
+ private onHighlight(direction: Direction) {
+ if (direction === Direction.LEFT || direction === Direction.RIGHT) {
+ const interaction = () => {
+ const spawner = this.getComponent(
+ ComponentNames.LambdaSpawn,
+ );
+ spawner.spawn(direction);
+
+ const text = this.getComponent(ComponentNames.Text);
+ text.text = spawner.spawnsLeft.toString();
+ this.addComponent(text);
+ };
+
+ this.addComponent(new Interactable(interaction));
+ return;
+ }
+
+ let modalOpen = false;
+ const interaction = () => {
+ if (modalOpen) {
+ modalOpen = false;
+ closeModal();
+ return;
+ }
+ modalOpen = true;
+ openModal(this.code);
+ };
+
+ this.addComponent(new Interactable(interaction));
+ }
+}
diff --git a/src/engine/entities/index.ts b/src/engine/entities/index.ts
index e63b272..a049350 100644
--- a/src/engine/entities/index.ts
+++ b/src/engine/entities/index.ts
@@ -3,3 +3,4 @@ export * from "./EntityNames";
export * from "./Player";
export * from "./FunctionBox";
export * from "./Wall";
+export * from "./LambdaFactory";
diff --git a/src/engine/interfaces/Draw.ts b/src/engine/interfaces/Draw.ts
index 6561a01..78de7ed 100644
--- a/src/engine/interfaces/Draw.ts
+++ b/src/engine/interfaces/Draw.ts
@@ -6,4 +6,10 @@ export interface DrawArgs {
tint?: string;
opacity?: number;
rotation?: number;
+ backgroundText?: {
+ text: string;
+ font: string;
+ fillStyle: string;
+ textAlign: CanvasTextAlign;
+ };
}
diff --git a/src/engine/systems/Grid.ts b/src/engine/systems/Grid.ts
index 28ca5ea..8756320 100644
--- a/src/engine/systems/Grid.ts
+++ b/src/engine/systems/Grid.ts
@@ -39,7 +39,7 @@ export class Grid extends System {
}
private highlightEntitiesLookedAt(game: Game) {
- const highlightableEntities = new Set();
+ const highlightableEntities: [string, Direction][] = [];
game.forEachEntityWithComponent(
ComponentNames.FacingDirection,
@@ -64,23 +64,23 @@ export class Grid extends System {
}
this.grid[lookingAt.y][lookingAt.x].forEach((id) => {
- highlightableEntities.add(id);
+ highlightableEntities.push([id, facingDirection.currentDirection]);
});
},
);
- highlightableEntities.forEach((id) => {
+ highlightableEntities.forEach(([id, direction]) => {
const entity = game.getEntity(id)!;
if (entity.hasComponent(ComponentNames.Highlight)) {
const highlight = entity.getComponent(
ComponentNames.Highlight,
)!;
- highlight.highlight();
+ highlight.highlight(direction);
}
});
game.forEachEntityWithComponent(ComponentNames.Highlight, (entity) => {
- if (!highlightableEntities.has(entity.id)) {
+ if (!highlightableEntities.find(([id]) => id === entity.id)) {
const highlight = entity.getComponent(
ComponentNames.Highlight,
)!;
diff --git a/src/engine/systems/LambdaFactory.ts b/src/engine/systems/LambdaFactory.ts
new file mode 100644
index 0000000..1263eae
--- /dev/null
+++ b/src/engine/systems/LambdaFactory.ts
@@ -0,0 +1,34 @@
+import { System, SystemNames } from ".";
+import { Game } from "..";
+import { ComponentNames, Grid, LambdaSpawn } from "../components";
+import { FunctionBox } from "../entities";
+
+export class LambdaFactory extends System {
+ constructor() {
+ super(SystemNames.LambdaFactory);
+ }
+
+ public update(_dt: number, game: Game) {
+ game.forEachEntityWithComponent(ComponentNames.LambdaSpawn, (entity) => {
+ const lambdaSpawn = entity.getComponent(
+ ComponentNames.LambdaSpawn,
+ )!;
+ const hasGrid = entity.hasComponent(SystemNames.Grid);
+
+ if (!lambdaSpawn.direction || !hasGrid) {
+ return;
+ }
+
+ const grid = entity.getComponent(SystemNames.Grid)!;
+
+ const lambda = new FunctionBox(grid.gridPosition, lambdaSpawn.code);
+ const lambdaGrid = lambda.getComponent(SystemNames.Grid)!;
+ lambdaGrid.movingDirection = lambdaSpawn.direction;
+ lambda.addComponent(lambdaGrid);
+ game.addEntity(lambda);
+
+ lambdaSpawn.direction = null;
+ entity.addComponent(lambdaSpawn);
+ });
+ }
+}
diff --git a/src/engine/systems/Render.ts b/src/engine/systems/Render.ts
index 83daa52..f273deb 100644
--- a/src/engine/systems/Render.ts
+++ b/src/engine/systems/Render.ts
@@ -1,5 +1,11 @@
import { System, SystemNames } from ".";
-import { BoundingBox, ComponentNames, Highlight, Sprite } from "../components";
+import {
+ BoundingBox,
+ Text,
+ ComponentNames,
+ Highlight,
+ Sprite,
+} from "../components";
import { Game } from "..";
import { clamp } from "../utils";
import { DrawArgs } from "../interfaces";
@@ -50,6 +56,15 @@ export class Render extends System {
);
drawArgs.tint = highlight.isHighlighted ? "red" : undefined;
}
+ if (entity.hasComponent(ComponentNames.Text)) {
+ const text = entity.getComponent(ComponentNames.Text);
+ drawArgs.backgroundText = {
+ text: text.text,
+ font: text.font,
+ fillStyle: text.fillStyle,
+ textAlign: text.textAlign,
+ };
+ }
sprite.draw(this.ctx, drawArgs);
});
diff --git a/src/engine/systems/SystemNames.ts b/src/engine/systems/SystemNames.ts
index 0de5857..85d1539 100644
--- a/src/engine/systems/SystemNames.ts
+++ b/src/engine/systems/SystemNames.ts
@@ -3,4 +3,5 @@ export namespace SystemNames {
export const Input = "Input";
export const FacingDirection = "FacingDirection";
export const Grid = "Grid";
+ export const LambdaFactory = "LambdaFactory";
}
diff --git a/src/engine/systems/index.ts b/src/engine/systems/index.ts
index 18ffa2e..4490ee2 100644
--- a/src/engine/systems/index.ts
+++ b/src/engine/systems/index.ts
@@ -4,3 +4,4 @@ export * from "./Render";
export * from "./Input";
export * from "./FacingDirection";
export * from "./Grid";
+export * from "./LambdaFactory";