add text on lambda factory

This commit is contained in:
Elizabeth Hunt 2024-03-02 04:02:20 -07:00
parent 06bb417720
commit 4b9349b3f8
Signed by: simponic
GPG Key ID: 52B3774857EB24B1
22 changed files with 281 additions and 18 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -30,7 +30,7 @@ export const App = () => {
simponic
</a>{" "}
| inspired by{" "}
<a href="https://hempuli.com/baba/" target="_blank" className="tf">
<a href="https://hempuli.com/baba/" target="_blank">
baba is you
</a>
</span>

View File

@ -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;

View File

@ -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() {

View File

@ -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";
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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(),

View File

@ -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;
}
}

View File

@ -9,3 +9,5 @@ export * from "./Highlight";
export * from "./Interactable";
export * from "./Pushable";
export * from "./Colliding";
export * from "./LambdaSpawn";
export * from "./Text";

View File

@ -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<string, HTMLImageElement>();
export const loadSpritesIntoImageElements = (
@ -37,5 +42,6 @@ export const loadAssets = () =>
(key) => SPRITE_SPECS.get(key) as SpriteSpec,
),
),
FONT.load(),
// TODO: Sound
]);

View File

@ -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);

View File

@ -2,4 +2,5 @@ export namespace EntityNames {
export const Player = "Player";
export const FunctionBox = "FunctionBox";
export const Wall = "Wall";
export const LambdaFactory = "LambdaFactory";
}

View File

@ -57,7 +57,7 @@ export class FunctionBox extends Entity {
this.addComponent(
new Highlight(
() => this.onHighlight(),
(_direction) => this.onHighlight(),
() => this.onUnhighlight(),
),
);

View File

@ -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<LambdaSpawn>(
ComponentNames.LambdaSpawn,
);
spawner.spawn(direction);
const text = this.getComponent<Text>(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));
}
}

View File

@ -3,3 +3,4 @@ export * from "./EntityNames";
export * from "./Player";
export * from "./FunctionBox";
export * from "./Wall";
export * from "./LambdaFactory";

View File

@ -6,4 +6,10 @@ export interface DrawArgs {
tint?: string;
opacity?: number;
rotation?: number;
backgroundText?: {
text: string;
font: string;
fillStyle: string;
textAlign: CanvasTextAlign;
};
}

View File

@ -39,7 +39,7 @@ export class Grid extends System {
}
private highlightEntitiesLookedAt(game: Game) {
const highlightableEntities = new Set<string>();
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<Highlight>(
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<Highlight>(
ComponentNames.Highlight,
)!;

View File

@ -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<LambdaSpawn>(
ComponentNames.LambdaSpawn,
)!;
const hasGrid = entity.hasComponent(SystemNames.Grid);
if (!lambdaSpawn.direction || !hasGrid) {
return;
}
const grid = entity.getComponent<Grid>(SystemNames.Grid)!;
const lambda = new FunctionBox(grid.gridPosition, lambdaSpawn.code);
const lambdaGrid = lambda.getComponent<Grid>(SystemNames.Grid)!;
lambdaGrid.movingDirection = lambdaSpawn.direction;
lambda.addComponent(lambdaGrid);
game.addEntity(lambda);
lambdaSpawn.direction = null;
entity.addComponent(lambdaSpawn);
});
}
}

View File

@ -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<Text>(ComponentNames.Text);
drawArgs.backgroundText = {
text: text.text,
font: text.font,
fillStyle: text.fillStyle,
textAlign: text.textAlign,
};
}
sprite.draw(this.ctx, drawArgs);
});

View File

@ -3,4 +3,5 @@ export namespace SystemNames {
export const Input = "Input";
export const FacingDirection = "FacingDirection";
export const Grid = "Grid";
export const LambdaFactory = "LambdaFactory";
}

View File

@ -4,3 +4,4 @@ export * from "./Render";
export * from "./Input";
export * from "./FacingDirection";
export * from "./Grid";
export * from "./LambdaFactory";