level one (applications prototype finished!)
This commit is contained in:
parent
823620b2a6
commit
e6e2944056
Binary file not shown.
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
public/assets/sound/failure.wav
Normal file
BIN
public/assets/sound/failure.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/keyopen.wav
Normal file
BIN
public/assets/sound/keyopen.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/lambda_save.wav
Normal file
BIN
public/assets/sound/lambda_save.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/lambda_transform.wav
Normal file
BIN
public/assets/sound/lambda_transform.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/modal_close.wav
Normal file
BIN
public/assets/sound/modal_close.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/modal_open.wav
Normal file
BIN
public/assets/sound/modal_open.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/move_1.wav
Normal file
BIN
public/assets/sound/move_1.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/move_2.wav
Normal file
BIN
public/assets/sound/move_2.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/move_3.wav
Normal file
BIN
public/assets/sound/move_3.wav
Normal file
Binary file not shown.
BIN
public/assets/sound/move_4.wav
Normal file
BIN
public/assets/sound/move_4.wav
Normal file
Binary file not shown.
20
public/assets/sound/music/credits.txt
Normal file
20
public/assets/sound/music/credits.txt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
Reverie by
|
||||||
|
Music promoted by https://www.chosic.com/free-music/all/
|
||||||
|
Creative Commons CC BY 4.0
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Moonlight by Scott Buckley | www.scottbuckley.com.au
|
||||||
|
Music promoted by https://www.chosic.com/free-music/all/
|
||||||
|
Creative Commons CC BY 4.0
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
Jul by Scott Buckley | www.scottbuckley.com.au
|
||||||
|
Music promoted by https://www.chosic.com/free-music/all/
|
||||||
|
Creative Commons Attribution 4.0 International (CC BY 4.0)
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
||||||
|
A Kind Of Hope by Scott Buckley | www.scottbuckley.com.au
|
||||||
|
Music promoted by https://www.chosic.com/free-music/all/
|
||||||
|
Creative Commons CC BY 4.0
|
||||||
|
https://creativecommons.org/licenses/by/4.0/
|
||||||
|
|
BIN
public/assets/sound/music/hope.mp3
Normal file
BIN
public/assets/sound/music/hope.mp3
Normal file
Binary file not shown.
BIN
public/assets/sound/music/jul.mp3
Normal file
BIN
public/assets/sound/music/jul.mp3
Normal file
Binary file not shown.
BIN
public/assets/sound/music/moonlight.mp3
Normal file
BIN
public/assets/sound/music/moonlight.mp3
Normal file
Binary file not shown.
BIN
public/assets/sound/music/reverie.mp3
Normal file
BIN
public/assets/sound/music/reverie.mp3
Normal file
Binary file not shown.
@ -32,6 +32,10 @@ export const App = () => {
|
|||||||
| inspired by{" "}
|
| inspired by{" "}
|
||||||
<a href="https://hempuli.com/baba/" target="_blank">
|
<a href="https://hempuli.com/baba/" target="_blank">
|
||||||
baba is you
|
baba is you
|
||||||
|
</a>{" "}
|
||||||
|
| music by{" "}
|
||||||
|
<a href="https://www.scottbuckley.com.au" target="_blank">
|
||||||
|
scott buckley
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { TheAbstractionEngine, Game } from "../engine";
|
import { TheAbstractionEngine, Game } from "../engine";
|
||||||
import { Miscellaneous } from "../engine/config";
|
import { Miscellaneous } from "../engine/config";
|
||||||
|
import { Title } from "./Title";
|
||||||
|
|
||||||
export interface GameCanvasProps {
|
export interface GameCanvasProps {
|
||||||
width: number;
|
width: number;
|
||||||
@ -10,6 +11,8 @@ export interface GameCanvasProps {
|
|||||||
export const GameCanvas = ({ width, height }: GameCanvasProps) => {
|
export const GameCanvas = ({ width, height }: GameCanvasProps) => {
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
const [game, setGame] = useState<TheAbstractionEngine>();
|
const [game, setGame] = useState<TheAbstractionEngine>();
|
||||||
|
const [ready, setReady] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canvasRef.current && !game) {
|
if (canvasRef.current && !game) {
|
||||||
@ -21,25 +24,35 @@ export const GameCanvas = ({ width, height }: GameCanvasProps) => {
|
|||||||
|
|
||||||
theAbstractionEngine.init().then(() => {
|
theAbstractionEngine.init().then(() => {
|
||||||
theAbstractionEngine.play();
|
theAbstractionEngine.play();
|
||||||
setGame(theAbstractionEngine);
|
|
||||||
|
|
||||||
canvas.focus();
|
canvas.focus();
|
||||||
|
|
||||||
|
setGame(theAbstractionEngine);
|
||||||
|
setLoading(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => theAbstractionEngine.stop();
|
return () => theAbstractionEngine.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [canvasRef]);
|
}, [canvasRef, ready]);
|
||||||
|
|
||||||
|
if (ready) {
|
||||||
|
return (
|
||||||
|
<div className="centered-game">
|
||||||
|
<canvas
|
||||||
|
id={Miscellaneous.CANVAS_ID}
|
||||||
|
tabIndex={1}
|
||||||
|
ref={canvasRef}
|
||||||
|
width={loading ? 50 : width}
|
||||||
|
height={loading ? 50 : height}
|
||||||
|
></canvas>
|
||||||
|
{loading && <span className="loading">Loading...</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="centered-game">
|
<div className="centered-game">
|
||||||
<canvas
|
<Title setReady={setReady} />
|
||||||
id={Miscellaneous.CANVAS_ID}
|
|
||||||
tabIndex={1}
|
|
||||||
ref={canvasRef}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
17
src/components/Title.tsx
Normal file
17
src/components/Title.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
export interface TitleProps {
|
||||||
|
setReady: (ready: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Title = ({ setReady }: TitleProps) => {
|
||||||
|
return (
|
||||||
|
<div style={{ textAlign: "center" }}>
|
||||||
|
<h1>the abstraction engine</h1>
|
||||||
|
<p>a game based on the lambda calculus</p>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<hr />
|
||||||
|
<br />
|
||||||
|
<button onClick={() => setReady(true)}>ready</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -12,7 +12,6 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-family: "scientifica", monospace;
|
font-family: "scientifica", monospace;
|
||||||
transition: background 0.2s ease-in-out;
|
|
||||||
font-smooth: never;
|
font-smooth: never;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,10 +20,13 @@ body {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: radial-gradient(ellipse at top, var(--bg), transparent),
|
||||||
|
radial-gradient(ellipse at left, var(--blue), transparent),
|
||||||
|
radial-gradient(ellipse at right, var(--purple), transparent),
|
||||||
|
radial-gradient(ellipse at bottom, var(--bg), transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background-color: var(--bg);
|
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,17 +53,20 @@ a:visited {
|
|||||||
justify-content: space-around;
|
justify-content: space-around;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
|
border-bottom: 2px solid var(--yellow);
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
border-top: 1px solid var(--yellow);
|
|
||||||
border-bottom: 1px solid var(--yellow);
|
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
|
border-top: 2px solid var(--yellow);
|
||||||
|
border-radius: 0.5rem;
|
||||||
padding-top: 1rem;
|
padding-top: 1rem;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 1rem;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav {
|
.nav {
|
||||||
@ -88,12 +93,13 @@ a:visited {
|
|||||||
|
|
||||||
.centered-game canvas {
|
.centered-game canvas {
|
||||||
display: block;
|
display: block;
|
||||||
max-height: 90%;
|
max-height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
border: 1px solid var(--yellow);
|
border: 1px solid var(--yellow);
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
margin: 0;
|
margin-bottom: 2rem;
|
||||||
|
background-color: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
@ -7,6 +7,7 @@ import {
|
|||||||
Key,
|
Key,
|
||||||
LockedDoor,
|
LockedDoor,
|
||||||
Curry,
|
Curry,
|
||||||
|
FunctionApplication,
|
||||||
} from "./entities";
|
} from "./entities";
|
||||||
import {
|
import {
|
||||||
Grid,
|
Grid,
|
||||||
@ -16,6 +17,7 @@ import {
|
|||||||
Collision,
|
Collision,
|
||||||
GridSpawner,
|
GridSpawner,
|
||||||
Life,
|
Life,
|
||||||
|
Music,
|
||||||
} from "./systems";
|
} from "./systems";
|
||||||
|
|
||||||
export class TheAbstractionEngine {
|
export class TheAbstractionEngine {
|
||||||
@ -49,8 +51,9 @@ export class TheAbstractionEngine {
|
|||||||
),
|
),
|
||||||
new GridSpawner(),
|
new GridSpawner(),
|
||||||
new Collision(),
|
new Collision(),
|
||||||
new Render(this.ctx),
|
|
||||||
new Life(),
|
new Life(),
|
||||||
|
new Music(),
|
||||||
|
new Render(this.ctx),
|
||||||
].forEach((system) => this.game.addSystem(system));
|
].forEach((system) => this.game.addSystem(system));
|
||||||
|
|
||||||
const player = new Player();
|
const player = new Player();
|
||||||
@ -70,6 +73,9 @@ export class TheAbstractionEngine {
|
|||||||
|
|
||||||
const curry = new Curry({ x: 9, y: 8 });
|
const curry = new Curry({ x: 9, y: 8 });
|
||||||
this.game.addEntity(curry);
|
this.game.addEntity(curry);
|
||||||
|
|
||||||
|
const application = new FunctionApplication({ x: 5, y: 5 }, "(_INPUT key)");
|
||||||
|
this.game.addEntity(application);
|
||||||
}
|
}
|
||||||
|
|
||||||
public play() {
|
public play() {
|
||||||
|
@ -6,6 +6,7 @@ export class Grid extends Component {
|
|||||||
|
|
||||||
public gridPosition: Coord2D;
|
public gridPosition: Coord2D;
|
||||||
public movingDirection: Direction;
|
public movingDirection: Direction;
|
||||||
|
public previousDirection: Direction;
|
||||||
|
|
||||||
constructor(position: Coord2D = { x: 0, y: 0 }) {
|
constructor(position: Coord2D = { x: 0, y: 0 }) {
|
||||||
super(ComponentNames.Grid);
|
super(ComponentNames.Grid);
|
||||||
@ -13,5 +14,6 @@ export class Grid extends Component {
|
|||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.gridPosition = position;
|
this.gridPosition = position;
|
||||||
this.movingDirection = Direction.NONE;
|
this.movingDirection = Direction.NONE;
|
||||||
|
this.previousDirection = this.movingDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { type SpriteSpec, SPRITE_SPECS } from ".";
|
import { type SpriteSpec, SPRITE_SPECS } from ".";
|
||||||
|
import { SOUND_SPECS, SoundSpec } from "./sounds";
|
||||||
|
|
||||||
export const FONT = new FontFace("scientifica", "url(/fonts/scientifica.ttf)");
|
export const FONT = new FontFace("scientifica", "url(/fonts/scientifica.ttf)");
|
||||||
FONT.load().then((font) => {
|
FONT.load().then((font) => {
|
||||||
@ -6,6 +7,7 @@ FONT.load().then((font) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const IMAGES = new Map<string, HTMLImageElement>();
|
export const IMAGES = new Map<string, HTMLImageElement>();
|
||||||
|
export const SOUNDS = new Map<string, HTMLAudioElement>();
|
||||||
|
|
||||||
export const loadSpritesIntoImageElements = (
|
export const loadSpritesIntoImageElements = (
|
||||||
spriteSpecs: Partial<SpriteSpec>[],
|
spriteSpecs: Partial<SpriteSpec>[],
|
||||||
@ -35,6 +37,45 @@ export const loadSpritesIntoImageElements = (
|
|||||||
return spritePromises;
|
return spritePromises;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadSoundsIntoAudioElements = (
|
||||||
|
soundSpecs: SoundSpec[],
|
||||||
|
): Promise<void>[] => {
|
||||||
|
const soundPromises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
for (const soundSpec of soundSpecs) {
|
||||||
|
if (soundSpec.url) {
|
||||||
|
const promise = fetch(soundSpec.url)
|
||||||
|
.then((response) => response.blob())
|
||||||
|
.then((blob) => {
|
||||||
|
const audio = new Audio();
|
||||||
|
audio.src = URL.createObjectURL(blob);
|
||||||
|
audio.volume = soundSpec.volume ?? 1;
|
||||||
|
|
||||||
|
SOUNDS.set(soundSpec.name, audio);
|
||||||
|
return new Promise<void>((resolve, rej) => {
|
||||||
|
audio.oncanplaythrough = () => {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
audio.onerror = (e) => {
|
||||||
|
console.error(soundSpec);
|
||||||
|
rej(e);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
soundPromises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (soundSpec.states) {
|
||||||
|
soundPromises.push(
|
||||||
|
...loadSoundsIntoAudioElements(Array.from(soundSpec.states.values())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return soundPromises;
|
||||||
|
};
|
||||||
|
|
||||||
export const loadAssets = () =>
|
export const loadAssets = () =>
|
||||||
Promise.all([
|
Promise.all([
|
||||||
...loadSpritesIntoImageElements(
|
...loadSpritesIntoImageElements(
|
||||||
@ -43,5 +84,5 @@ export const loadAssets = () =>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
FONT.load(),
|
FONT.load(),
|
||||||
// TODO: Sound
|
...loadSoundsIntoAudioElements(Array.from(SOUND_SPECS.values())),
|
||||||
]);
|
]);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
export * from "./constants";
|
export * from "./constants";
|
||||||
export * from "./assets";
|
export * from "./assets";
|
||||||
export * from "./sprites";
|
export * from "./sprites";
|
||||||
|
export * from "./sounds";
|
||||||
|
84
src/engine/config/sounds.ts
Normal file
84
src/engine/config/sounds.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
export type SoundSpec = {
|
||||||
|
name: string;
|
||||||
|
url?: string;
|
||||||
|
volume?: number;
|
||||||
|
states?: Map<string | number, SoundSpec>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MovingSound: SoundSpec = {
|
||||||
|
name: "moving",
|
||||||
|
states: new Map([
|
||||||
|
[1, { name: "moving_1", url: "/assets/sound/move_1.wav" }],
|
||||||
|
// [2, { name: "moving_2", url: "/assets/sound/move_2.wav" }],
|
||||||
|
// [3, { name: "moving_3", url: "/assets/sound/move_3.wav" }],
|
||||||
|
[4, { name: "moving_4", url: "/assets/sound/move_4.wav" }],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LambdaTransformSound: SoundSpec = {
|
||||||
|
name: "lambdaTransform",
|
||||||
|
url: "/assets/sound/lambda_transform.wav",
|
||||||
|
volume: 0.3,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LambdaSave: SoundSpec = {
|
||||||
|
name: "lambdaSave",
|
||||||
|
url: "/assets/sound/lambda_save.wav",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Failure: SoundSpec = {
|
||||||
|
name: "failure",
|
||||||
|
url: "/assets/sound/failure.wav",
|
||||||
|
volume: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModalOpen: SoundSpec = {
|
||||||
|
name: "modalOpen",
|
||||||
|
url: "/assets/sound/modal_open.wav",
|
||||||
|
volume: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModalClose: SoundSpec = {
|
||||||
|
name: "modalClose",
|
||||||
|
url: "/assets/sound/modal_close.wav",
|
||||||
|
volume: 0.5,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const KeyOpen: SoundSpec = {
|
||||||
|
name: "keyOpen",
|
||||||
|
url: "/assets/sound/keyopen.wav",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Music: SoundSpec = {
|
||||||
|
name: "music",
|
||||||
|
states: new Map([
|
||||||
|
[
|
||||||
|
"hope",
|
||||||
|
{ name: "hope", url: "/assets/sound/music/hope.mp3", volume: 0.5 },
|
||||||
|
],
|
||||||
|
["jul", { name: "jul", url: "/assets/sound/music/jul.mp3", volume: 0.5 }],
|
||||||
|
[
|
||||||
|
"reverie",
|
||||||
|
{ name: "reverie", url: "/assets/sound/music/reverie.mp3", volume: 0.5 },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"moonlight",
|
||||||
|
{
|
||||||
|
name: "moonlight",
|
||||||
|
url: "/assets/sound/music/moonlight.mp3",
|
||||||
|
volume: 0.5,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SOUND_SPECS: SoundSpec[] = [
|
||||||
|
MovingSound,
|
||||||
|
LambdaTransformSound,
|
||||||
|
LambdaSave,
|
||||||
|
Failure,
|
||||||
|
ModalOpen,
|
||||||
|
ModalClose,
|
||||||
|
KeyOpen,
|
||||||
|
Music,
|
||||||
|
];
|
@ -8,6 +8,7 @@ export enum Sprites {
|
|||||||
KEY,
|
KEY,
|
||||||
LOCKED_DOOR,
|
LOCKED_DOOR,
|
||||||
CURRY,
|
CURRY,
|
||||||
|
BUBBLE,
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpriteSpec {
|
export interface SpriteSpec {
|
||||||
@ -96,3 +97,12 @@ const currySpriteSpec = {
|
|||||||
sheet: "/assets/curry.png",
|
sheet: "/assets/curry.png",
|
||||||
};
|
};
|
||||||
SPRITE_SPECS.set(Sprites.CURRY, currySpriteSpec);
|
SPRITE_SPECS.set(Sprites.CURRY, currySpriteSpec);
|
||||||
|
|
||||||
|
const bubbleSpriteSpec = {
|
||||||
|
msPerFrame: 200,
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
frames: 3,
|
||||||
|
sheet: "/assets/bubble.png",
|
||||||
|
};
|
||||||
|
SPRITE_SPECS.set(Sprites.BUBBLE, bubbleSpriteSpec);
|
||||||
|
@ -1,7 +1,183 @@
|
|||||||
import { Entity, EntityNames } from ".";
|
import {
|
||||||
|
Entity,
|
||||||
|
EntityNames,
|
||||||
|
FunctionBox,
|
||||||
|
Key,
|
||||||
|
Particles,
|
||||||
|
makeLambdaTermHighlightComponent,
|
||||||
|
} from ".";
|
||||||
|
import {
|
||||||
|
BoundingBox,
|
||||||
|
Colliding,
|
||||||
|
ComponentNames,
|
||||||
|
Grid,
|
||||||
|
LambdaTerm,
|
||||||
|
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 } from "../utils";
|
||||||
|
import {
|
||||||
|
DebrujinifiedLambdaTerm,
|
||||||
|
SymbolTable,
|
||||||
|
emitNamed,
|
||||||
|
interpret,
|
||||||
|
} from "../../interpreter";
|
||||||
|
|
||||||
export class FunctionApplication extends Entity {
|
export class FunctionApplication extends Entity {
|
||||||
constructor() {
|
private static spriteSpec = SPRITE_SPECS.get(Sprites.BUBBLE) as SpriteSpec;
|
||||||
|
|
||||||
|
private symbolTable: SymbolTable;
|
||||||
|
|
||||||
|
constructor(gridPosition: Coord2D, lambdaTerm: string) {
|
||||||
super(EntityNames.FunctionApplication);
|
super(EntityNames.FunctionApplication);
|
||||||
|
|
||||||
|
this.symbolTable = new SymbolTable();
|
||||||
|
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(makeLambdaTermHighlightComponent(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public handleCollision(game: Game, entity: Entity) {
|
||||||
|
if (entity.name !== EntityNames.FunctionBox) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entityGrid = entity.getComponent<Grid>(ComponentNames.Grid);
|
||||||
|
if (entityGrid.movingDirection !== Direction.NONE) {
|
||||||
|
// prevent recursive functionBox -> application creation
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const grid = this.getComponent<Grid>(ComponentNames.Grid);
|
||||||
|
const gridSystem = game.getSystem<GridSystem>(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<LambdaTerm>(
|
||||||
|
ComponentNames.LambdaTerm,
|
||||||
|
);
|
||||||
|
const functionTerm = entity.getComponent<LambdaTerm>(
|
||||||
|
ComponentNames.LambdaTerm,
|
||||||
|
);
|
||||||
|
const newCode = applicationTerm.code.replace("_INPUT", functionTerm.code);
|
||||||
|
let result: DebrujinifiedLambdaTerm | null = null;
|
||||||
|
try {
|
||||||
|
result = interpret(newCode, this.symbolTable);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { dimension } = gridSystem;
|
||||||
|
const nextPosition = gridSystem.getNewGridPosition(
|
||||||
|
grid.gridPosition,
|
||||||
|
entityGrid.previousDirection,
|
||||||
|
);
|
||||||
|
|
||||||
|
let applicationResultingEntity: Entity | null = null; // this should be its own function
|
||||||
|
if ("abstraction" in result) {
|
||||||
|
const code = emitNamed(result);
|
||||||
|
|
||||||
|
applicationResultingEntity = new FunctionBox(grid.gridPosition, code);
|
||||||
|
} else if ("name" in result) {
|
||||||
|
const { name } = result;
|
||||||
|
if (name === "key") {
|
||||||
|
applicationResultingEntity = new Key(grid.gridPosition);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fail();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
game.removeEntity(entity.id);
|
||||||
|
if (applicationResultingEntity) {
|
||||||
|
const grid = applicationResultingEntity.getComponent<Grid>(
|
||||||
|
ComponentNames.Grid,
|
||||||
|
);
|
||||||
|
grid.movingDirection = entityGrid.previousDirection;
|
||||||
|
applicationResultingEntity.addComponent(grid);
|
||||||
|
|
||||||
|
game.addEntity(applicationResultingEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.playTransformSound();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private playTransformSound() {
|
||||||
|
const audio = SOUNDS.get(LambdaTransformSound.name)!;
|
||||||
|
audio.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import {
|
import {
|
||||||
IMAGES,
|
IMAGES,
|
||||||
Miscellaneous,
|
Miscellaneous,
|
||||||
|
ModalClose,
|
||||||
|
ModalOpen,
|
||||||
|
SOUNDS,
|
||||||
SPRITE_SPECS,
|
SPRITE_SPECS,
|
||||||
SpriteSpec,
|
SpriteSpec,
|
||||||
Sprites,
|
Sprites,
|
||||||
@ -72,10 +75,15 @@ export const makeLambdaTermHighlightComponent = (entity: Entity) => {
|
|||||||
|
|
||||||
const onHighlight = () => {
|
const onHighlight = () => {
|
||||||
let modalOpen = false;
|
let modalOpen = false;
|
||||||
|
const doModalClose = () => {
|
||||||
|
SOUNDS.get(ModalClose.name)!.play();
|
||||||
|
modalOpen = false;
|
||||||
|
closeModal();
|
||||||
|
};
|
||||||
|
|
||||||
const interaction = () => {
|
const interaction = () => {
|
||||||
if (modalOpen) {
|
if (modalOpen) {
|
||||||
modalOpen = false;
|
doModalClose();
|
||||||
closeModal();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,9 +94,10 @@ export const makeLambdaTermHighlightComponent = (entity: Entity) => {
|
|||||||
`<div style="text-align:center"><p>${code}</p> <br> <button id="close">Close</button></div>`,
|
`<div style="text-align:center"><p>${code}</p> <br> <button id="close">Close</button></div>`,
|
||||||
);
|
);
|
||||||
modalOpen = true;
|
modalOpen = true;
|
||||||
|
SOUNDS.get(ModalOpen.name)!.play();
|
||||||
|
|
||||||
document.getElementById("close")!.addEventListener("click", () => {
|
document.getElementById("close")!.addEventListener("click", () => {
|
||||||
closeModal();
|
doModalClose();
|
||||||
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
|
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
Failure,
|
||||||
IMAGES,
|
IMAGES,
|
||||||
|
LambdaSave,
|
||||||
|
LambdaTransformSound,
|
||||||
Miscellaneous,
|
Miscellaneous,
|
||||||
|
ModalOpen,
|
||||||
|
SOUNDS,
|
||||||
SPRITE_SPECS,
|
SPRITE_SPECS,
|
||||||
SpriteSpec,
|
SpriteSpec,
|
||||||
Sprites,
|
Sprites,
|
||||||
@ -144,6 +149,8 @@ export class LambdaFactory extends Entity {
|
|||||||
const text = this.getComponent<Text>(ComponentNames.Text);
|
const text = this.getComponent<Text>(ComponentNames.Text);
|
||||||
text.text = spawner.spawnsLeft.toString();
|
text.text = spawner.spawnsLeft.toString();
|
||||||
this.addComponent(text);
|
this.addComponent(text);
|
||||||
|
|
||||||
|
SOUNDS.get(LambdaTransformSound.name)!.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private openCodeEditor() {
|
private openCodeEditor() {
|
||||||
@ -185,6 +192,8 @@ export class LambdaFactory extends Entity {
|
|||||||
canvas,
|
canvas,
|
||||||
closeButton,
|
closeButton,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SOUNDS.get(ModalOpen.name)!.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private refreshCodeEditorText(text: string) {
|
private refreshCodeEditorText(text: string) {
|
||||||
@ -239,6 +248,7 @@ export class LambdaFactory extends Entity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
syntaxError.innerText = e.message;
|
syntaxError.innerText = e.message;
|
||||||
|
SOUNDS.get(Failure.name)!.play();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,6 +260,7 @@ export class LambdaFactory extends Entity {
|
|||||||
closeModal();
|
closeModal();
|
||||||
|
|
||||||
canvas.focus();
|
canvas.focus();
|
||||||
|
SOUNDS.get(LambdaSave.name)!.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onHighlight(direction: Direction) {
|
private onHighlight(direction: Direction) {
|
||||||
|
@ -7,7 +7,14 @@ import {
|
|||||||
Sprite,
|
Sprite,
|
||||||
ComponentNames,
|
ComponentNames,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { IMAGES, SPRITE_SPECS, SpriteSpec, Sprites } from "../config";
|
import {
|
||||||
|
IMAGES,
|
||||||
|
KeyOpen,
|
||||||
|
SOUNDS,
|
||||||
|
SPRITE_SPECS,
|
||||||
|
SpriteSpec,
|
||||||
|
Sprites,
|
||||||
|
} from "../config";
|
||||||
import { Coord2D } from "../interfaces";
|
import { Coord2D } from "../interfaces";
|
||||||
import { Grid as GridSystem, SystemNames } from "../systems";
|
import { Grid as GridSystem, SystemNames } from "../systems";
|
||||||
import { colors } from "../utils";
|
import { colors } from "../utils";
|
||||||
@ -82,5 +89,12 @@ export class LockedDoor extends Entity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
game.addEntity(particles);
|
game.addEntity(particles);
|
||||||
|
|
||||||
|
this.playKeySound();
|
||||||
|
}
|
||||||
|
|
||||||
|
private playKeySound() {
|
||||||
|
const audio = SOUNDS.get(KeyOpen.name)!;
|
||||||
|
audio.play();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,8 +156,12 @@ export class Particles extends Entity {
|
|||||||
Math.floor(Math.random() * options.particleColors.length)
|
Math.floor(Math.random() * options.particleColors.length)
|
||||||
];
|
];
|
||||||
const position = {
|
const position = {
|
||||||
x: options.center.x + Math.cos(angle) * options.spawnerDimensions.width,
|
x:
|
||||||
y: options.center.y + Math.sin(angle) * options.spawnerDimensions.height,
|
options.center.x +
|
||||||
|
(Math.cos(angle) * options.spawnerDimensions.width) / 2,
|
||||||
|
y:
|
||||||
|
options.center.y +
|
||||||
|
(Math.sin(angle) * options.spawnerDimensions.height) / 2,
|
||||||
};
|
};
|
||||||
if (options.spawnerShape === "rectangle") {
|
if (options.spawnerShape === "rectangle") {
|
||||||
// determine a random position on the edge of the spawner based on the angle
|
// determine a random position on the edge of the spawner based on the angle
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
BoundingBox,
|
BoundingBox,
|
||||||
Control,
|
Control,
|
||||||
|
Pushable,
|
||||||
} from "../components";
|
} from "../components";
|
||||||
import { Direction } from "../interfaces/";
|
import { Direction } from "../interfaces/";
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ export class Player extends Entity {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.addComponent(new Pushable());
|
||||||
|
|
||||||
this.addComponent(new Control());
|
this.addComponent(new Control());
|
||||||
|
|
||||||
this.addComponent(new Grid());
|
this.addComponent(new Grid());
|
||||||
|
@ -6,6 +6,7 @@ import { BoundingBox, Colliding, ComponentNames, Grid } from "../components";
|
|||||||
const collisionMap: Record<string, Set<string>> = {
|
const collisionMap: Record<string, Set<string>> = {
|
||||||
[EntityNames.Key]: new Set([EntityNames.LockedDoor]),
|
[EntityNames.Key]: new Set([EntityNames.LockedDoor]),
|
||||||
[EntityNames.Curry]: new Set([EntityNames.Player]),
|
[EntityNames.Curry]: new Set([EntityNames.Player]),
|
||||||
|
[EntityNames.FunctionApplication]: new Set([EntityNames.FunctionBox]),
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Collision extends System {
|
export class Collision extends System {
|
||||||
|
@ -33,8 +33,8 @@ export class Grid extends System {
|
|||||||
this.rebuildGrid(game);
|
this.rebuildGrid(game);
|
||||||
|
|
||||||
this.highlightEntitiesLookedAt(game);
|
this.highlightEntitiesLookedAt(game);
|
||||||
this.propogateEntityMovements(game);
|
|
||||||
|
|
||||||
|
this.propogateEntityMovements(game);
|
||||||
this.updateMovingEntities(dt, game);
|
this.updateMovingEntities(dt, game);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,9 +209,11 @@ export class Grid extends System {
|
|||||||
) {
|
) {
|
||||||
game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => {
|
game.forEachEntityWithComponent(ComponentNames.Grid, (entity) => {
|
||||||
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
const grid = entity.getComponent<GridComponent>(ComponentNames.Grid)!;
|
||||||
|
|
||||||
if (grid.movingDirection === Direction.NONE) {
|
if (grid.movingDirection === Direction.NONE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
grid.previousDirection = grid.movingDirection;
|
||||||
|
|
||||||
const boundingBox = entity.getComponent<BoundingBox>(
|
const boundingBox = entity.getComponent<BoundingBox>(
|
||||||
ComponentNames.BoundingBox,
|
ComponentNames.BoundingBox,
|
||||||
@ -270,7 +272,7 @@ export class Grid extends System {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private getNewGridPosition(prev: Coord2D, direction: Direction) {
|
public getNewGridPosition(prev: Coord2D, direction: Direction) {
|
||||||
let { x: newX, y: newY } = prev;
|
let { x: newX, y: newY } = prev;
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case Direction.LEFT:
|
case Direction.LEFT:
|
||||||
@ -290,6 +292,25 @@ export class Grid extends System {
|
|||||||
return { x: newX, y: newY };
|
return { x: newX, y: newY };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public oppositeDirection(direction: Direction) {
|
||||||
|
let opposite = Direction.NONE;
|
||||||
|
switch (direction) {
|
||||||
|
case Direction.LEFT:
|
||||||
|
opposite = Direction.RIGHT;
|
||||||
|
break;
|
||||||
|
case Direction.RIGHT:
|
||||||
|
opposite = Direction.LEFT;
|
||||||
|
break;
|
||||||
|
case Direction.UP:
|
||||||
|
opposite = Direction.DOWN;
|
||||||
|
break;
|
||||||
|
case Direction.DOWN:
|
||||||
|
opposite = Direction.UP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return opposite;
|
||||||
|
}
|
||||||
|
|
||||||
private isEntityPastCenterWhenMoving(
|
private isEntityPastCenterWhenMoving(
|
||||||
direction: Direction,
|
direction: Direction,
|
||||||
gridPosition: Coord2D,
|
gridPosition: Coord2D,
|
||||||
|
@ -2,7 +2,7 @@ import { Grid as GridSystem, SystemNames, System } from ".";
|
|||||||
import { Game } from "..";
|
import { Game } from "..";
|
||||||
import { ComponentNames, Grid, Interactable } from "../components";
|
import { ComponentNames, Grid, Interactable } from "../components";
|
||||||
import { Control } from "../components/Control";
|
import { Control } from "../components/Control";
|
||||||
import { Action, KeyConstants } from "../config";
|
import { Action, KeyConstants, MovingSound, SOUNDS } from "../config";
|
||||||
import { Entity, Particles } from "../entities";
|
import { Entity, Particles } from "../entities";
|
||||||
import { Coord2D, Direction } from "../interfaces";
|
import { Coord2D, Direction } from "../interfaces";
|
||||||
import { colors } from "../utils";
|
import { colors } from "../utils";
|
||||||
@ -105,27 +105,44 @@ export class Input extends System {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (moveUp || moveLeft || moveRight || moveDown) {
|
if (moveUp || moveLeft || moveRight || moveDown) {
|
||||||
const gridPosition = gridComponent.gridPosition;
|
this.spawnParticlesAround(entity, game);
|
||||||
const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid);
|
this.playMoveSound();
|
||||||
const particles = new Particles({
|
|
||||||
center: gridSystem.gridToScreenPosition(gridPosition),
|
|
||||||
particleCount: 5,
|
|
||||||
particleShape: "circle",
|
|
||||||
particleMeanSpeed: 0.05,
|
|
||||||
particleSpeedVariance: 0.005,
|
|
||||||
particleMeanLife: 120,
|
|
||||||
particleMeanSize: 5,
|
|
||||||
particleSizeVariance: 2,
|
|
||||||
particleLifeVariance: 30,
|
|
||||||
particleColors: [colors.gray, colors.darkGray],
|
|
||||||
});
|
|
||||||
|
|
||||||
game.addEntity(particles);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
entity.addComponent(gridComponent);
|
entity.addComponent(gridComponent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private playMoveSound() {
|
||||||
|
const movingSounds = Array.from(MovingSound.states!.values());
|
||||||
|
const soundName =
|
||||||
|
movingSounds[Math.floor(Math.random() * movingSounds.length)].name;
|
||||||
|
SOUNDS.get(soundName)!.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
private spawnParticlesAround(entity: Entity, game: Game) {
|
||||||
|
const gridSystem = game.getSystem<GridSystem>(SystemNames.Grid);
|
||||||
|
const gridComponent = entity.getComponent<Grid>(ComponentNames.Grid)!;
|
||||||
|
const particles = new Particles({
|
||||||
|
center: gridSystem.gridToScreenPosition(gridComponent.gridPosition),
|
||||||
|
particleCount: 4,
|
||||||
|
spawnerShape: "circle",
|
||||||
|
spawnerDimensions: {
|
||||||
|
width: 10,
|
||||||
|
height: 10,
|
||||||
|
},
|
||||||
|
particleShape: "rectangle",
|
||||||
|
particleMeanSpeed: 0.05,
|
||||||
|
particleSpeedVariance: 0.005,
|
||||||
|
particleMeanLife: 120,
|
||||||
|
particleMeanSize: 3,
|
||||||
|
particleSizeVariance: 1,
|
||||||
|
particleLifeVariance: 30,
|
||||||
|
particleColors: [colors.gray, colors.darkGray, colors.lightPurple],
|
||||||
|
});
|
||||||
|
|
||||||
|
game.addEntity(particles);
|
||||||
|
}
|
||||||
|
|
||||||
private hasSomeKey(keys?: string[]): boolean {
|
private hasSomeKey(keys?: string[]): boolean {
|
||||||
if (keys) {
|
if (keys) {
|
||||||
return keys.some((key) => this.keys.has(key));
|
return keys.some((key) => this.keys.has(key));
|
||||||
|
40
src/engine/systems/Music.ts
Normal file
40
src/engine/systems/Music.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { System, SystemNames } from ".";
|
||||||
|
import { Music as GameMusic, SOUNDS } from "../config";
|
||||||
|
|
||||||
|
export class Music extends System {
|
||||||
|
private songs: string[] = [];
|
||||||
|
private currentSong?: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(SystemNames.Music);
|
||||||
|
|
||||||
|
this.songs = Array.from(GameMusic.states!.values()).map(
|
||||||
|
(state) => state.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private chooseRandomSong() {
|
||||||
|
return this.songs[Math.floor(Math.random() * this.songs.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public playNext() {
|
||||||
|
let nextSong = this.chooseRandomSong();
|
||||||
|
while (nextSong === this.currentSong) {
|
||||||
|
nextSong = this.chooseRandomSong();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentSong = nextSong;
|
||||||
|
SOUNDS.get(this.currentSong)?.play();
|
||||||
|
|
||||||
|
// when done, play next song
|
||||||
|
SOUNDS.get(this.currentSong)?.addEventListener("ended", () => {
|
||||||
|
this.playNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(_dt: number) {
|
||||||
|
if (!this.currentSong) {
|
||||||
|
this.playNext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,4 +6,5 @@ export namespace SystemNames {
|
|||||||
export const GridSpawner = "GridSpawner";
|
export const GridSpawner = "GridSpawner";
|
||||||
export const Collision = "Collision";
|
export const Collision = "Collision";
|
||||||
export const Life = "Life";
|
export const Life = "Life";
|
||||||
|
export const Music = "Music";
|
||||||
}
|
}
|
||||||
|
@ -7,3 +7,4 @@ export * from "./Grid";
|
|||||||
export * from "./GridSpawner";
|
export * from "./GridSpawner";
|
||||||
export * from "./Collision";
|
export * from "./Collision";
|
||||||
export * from "./Life";
|
export * from "./Life";
|
||||||
|
export * from "./Music";
|
||||||
|
@ -36,6 +36,6 @@ export const closeModal = (
|
|||||||
modal.style.display = "none";
|
modal.style.display = "none";
|
||||||
|
|
||||||
modalOpen = false;
|
modalOpen = false;
|
||||||
}, 250);
|
}, 200);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -201,7 +201,7 @@ export default (function () {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function peg$parse(input, options) {
|
function peg$parse(input, options, allowUnderscores = false) {
|
||||||
options = options !== undefined ? options : {};
|
options = options !== undefined ? options : {};
|
||||||
|
|
||||||
var peg$FAILED = {};
|
var peg$FAILED = {};
|
||||||
@ -215,7 +215,7 @@ export default (function () {
|
|||||||
var peg$c2 = ".";
|
var peg$c2 = ".";
|
||||||
var peg$c3 = "\r\n";
|
var peg$c3 = "\r\n";
|
||||||
|
|
||||||
var peg$r0 = /^[a-zA-Z0-9]/;
|
var peg$r0 = allowUnderscores ? /^[a-zA-Z0-9]_/ : /^[a-zA-Z0-9]/;
|
||||||
var peg$r1 = /^[\\\u03BB]/;
|
var peg$r1 = /^[\\\u03BB]/;
|
||||||
var peg$r2 = /^[\t-\n ]/;
|
var peg$r2 = /^[\t-\n ]/;
|
||||||
|
|
||||||
|
@ -198,22 +198,6 @@ export const betaReduce = (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const interpret = (term: string): DebrujinifiedLambdaTerm => {
|
|
||||||
const ast = parse(term);
|
|
||||||
const symbolTable = new SymbolTable();
|
|
||||||
const debrujined = debrujinify(ast, symbolTable);
|
|
||||||
|
|
||||||
let prev = debrujined;
|
|
||||||
let next = betaReduce(prev);
|
|
||||||
|
|
||||||
while (emitDebrujin(prev) !== emitDebrujin(next)) {
|
|
||||||
// alpha equivalence
|
|
||||||
prev = next;
|
|
||||||
next = betaReduce(prev);
|
|
||||||
}
|
|
||||||
return next;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const emitDebrujin = (term: DebrujinifiedLambdaTerm): string => {
|
export const emitDebrujin = (term: DebrujinifiedLambdaTerm): string => {
|
||||||
if ("index" in term) {
|
if ("index" in term) {
|
||||||
return term.index.toString();
|
return term.index.toString();
|
||||||
@ -253,3 +237,22 @@ export const emitNamed = (term: DebrujinifiedLambdaTerm): string => {
|
|||||||
|
|
||||||
throw new InvalidLambdaTermError(`Invalid lambda term: ${term}`);
|
throw new InvalidLambdaTermError(`Invalid lambda term: ${term}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const interpret = (
|
||||||
|
term: string,
|
||||||
|
symbolTable = new SymbolTable(),
|
||||||
|
allowUnderscores = false,
|
||||||
|
): DebrujinifiedLambdaTerm => {
|
||||||
|
const ast = parse(term, allowUnderscores);
|
||||||
|
const debrujined = debrujinify(ast, symbolTable);
|
||||||
|
|
||||||
|
let prev = debrujined;
|
||||||
|
let next = betaReduce(prev);
|
||||||
|
|
||||||
|
while (emitDebrujin(prev) !== emitDebrujin(next)) {
|
||||||
|
// alpha equivalence
|
||||||
|
prev = next;
|
||||||
|
next = betaReduce(prev);
|
||||||
|
}
|
||||||
|
return next;
|
||||||
|
};
|
||||||
|
@ -30,6 +30,10 @@ export const isVariable = (term: LambdaTerm): term is Variable => {
|
|||||||
return typeof term === "string";
|
return typeof term === "string";
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parse = (term: string, library = false) => {
|
export const parse = (
|
||||||
return peggyParser.parse(term, { peg$library: library });
|
term: string,
|
||||||
|
allowUnderscores = false,
|
||||||
|
library = false,
|
||||||
|
) => {
|
||||||
|
return peggyParser.parse(term, { peg$library: library }, allowUnderscores);
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user