2024-03-02 06:00:47 -07:00
|
|
|
import {
|
|
|
|
IMAGES,
|
|
|
|
Miscellaneous,
|
|
|
|
SPRITE_SPECS,
|
|
|
|
SpriteSpec,
|
|
|
|
Sprites,
|
|
|
|
} from "../config";
|
2024-03-02 04:02:20 -07:00
|
|
|
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";
|
2024-03-04 16:31:27 -07:00
|
|
|
import { EditorState, StateField, StateEffect, Range } from "@codemirror/state";
|
|
|
|
import { Decoration, EditorView, keymap } from "@codemirror/view";
|
|
|
|
import { defaultKeymap } from "@codemirror/commands";
|
|
|
|
import rainbowBrackets from "rainbowbrackets";
|
|
|
|
import { basicSetup } from "codemirror";
|
|
|
|
import { parse } from "../../interpreter";
|
|
|
|
|
|
|
|
interface CodeEditorState {
|
|
|
|
view: EditorView;
|
|
|
|
editorElement: HTMLElement;
|
|
|
|
}
|
|
|
|
|
|
|
|
const highlightEffect = StateEffect.define<Range<Decoration>[]>();
|
|
|
|
const highlightExtension = StateField.define({
|
|
|
|
create() {
|
|
|
|
return Decoration.none;
|
|
|
|
},
|
|
|
|
update(value, transaction) {
|
|
|
|
value = value.map(transaction.changes);
|
|
|
|
|
|
|
|
for (let effect of transaction.effects) {
|
|
|
|
if (effect.is(highlightEffect))
|
|
|
|
value = value.update({ add: effect.value, sort: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
provide: (f) => EditorView.decorations.from(f),
|
|
|
|
});
|
2024-03-02 04:02:20 -07:00
|
|
|
|
|
|
|
export class LambdaFactory extends Entity {
|
|
|
|
private static spriteSpec: SpriteSpec = SPRITE_SPECS.get(
|
|
|
|
Sprites.LAMBDA_FACTORY,
|
|
|
|
) as SpriteSpec;
|
|
|
|
|
2024-03-04 16:31:27 -07:00
|
|
|
private codeEditorState: CodeEditorState | null;
|
2024-03-02 04:02:20 -07:00
|
|
|
private code: string;
|
|
|
|
private spawns: number;
|
|
|
|
|
|
|
|
constructor(gridPosition: Coord2D, code: string, spawns: number) {
|
|
|
|
super(EntityNames.LambdaFactory);
|
|
|
|
|
|
|
|
this.code = code;
|
|
|
|
this.spawns = spawns;
|
2024-03-04 16:31:27 -07:00
|
|
|
this.codeEditorState = null;
|
2024-03-02 04:02:20 -07:00
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-03-04 16:31:27 -07:00
|
|
|
private spawnNewLambda(direction: Direction) {
|
|
|
|
const spawner = this.getComponent<LambdaSpawn>(ComponentNames.LambdaSpawn);
|
|
|
|
spawner.spawn(direction);
|
2024-03-02 04:02:20 -07:00
|
|
|
|
2024-03-04 16:31:27 -07:00
|
|
|
const text = this.getComponent<Text>(ComponentNames.Text);
|
|
|
|
text.text = spawner.spawnsLeft.toString();
|
|
|
|
this.addComponent(text);
|
|
|
|
}
|
2024-03-02 04:02:20 -07:00
|
|
|
|
2024-03-04 16:31:27 -07:00
|
|
|
private onHighlight(direction: Direction) {
|
|
|
|
if (direction === Direction.LEFT || direction === Direction.RIGHT) {
|
|
|
|
this.addComponent(new Interactable(() => this.spawnNewLambda(direction)));
|
2024-03-02 04:02:20 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let modalOpen = false;
|
2024-03-04 16:31:27 -07:00
|
|
|
let editorView: EditorView | null = null;
|
|
|
|
|
|
|
|
const syntaxErrorDecoration = Decoration.mark({
|
|
|
|
class: "syntax-error",
|
|
|
|
});
|
|
|
|
|
2024-03-02 06:00:47 -07:00
|
|
|
const close = () => {
|
2024-03-04 16:31:27 -07:00
|
|
|
if (editorView) {
|
|
|
|
const text = editorView.state.doc.toString();
|
|
|
|
|
|
|
|
// remove all text from the editor
|
|
|
|
editorView.dispatch({
|
|
|
|
changes: {
|
|
|
|
from: 0,
|
|
|
|
to: text.length,
|
|
|
|
insert: "",
|
|
|
|
},
|
|
|
|
});
|
|
|
|
// add the new text to the editor
|
|
|
|
editorView.dispatch({
|
|
|
|
changes: {
|
|
|
|
from: 0,
|
|
|
|
to: 0,
|
|
|
|
insert: text,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
parse(text);
|
|
|
|
} catch (e: any) {
|
|
|
|
if (!e.location) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const {
|
|
|
|
location: {
|
|
|
|
start: { offset: start },
|
|
|
|
end: { offset: end },
|
|
|
|
},
|
|
|
|
} = e;
|
|
|
|
|
|
|
|
editorView.dispatch({
|
|
|
|
effects: highlightEffect.of([
|
|
|
|
syntaxErrorDecoration.range(
|
|
|
|
start === end ? start - 1 : start,
|
|
|
|
end,
|
|
|
|
),
|
|
|
|
]),
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById("syntax-error")!.innerText = e.message;
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const spawner = this.getComponent<LambdaSpawn>(
|
|
|
|
ComponentNames.LambdaSpawn,
|
|
|
|
);
|
|
|
|
spawner.code = text;
|
|
|
|
this.addComponent(spawner);
|
|
|
|
}
|
|
|
|
|
2024-03-02 06:00:47 -07:00
|
|
|
modalOpen = false;
|
|
|
|
closeModal();
|
|
|
|
|
|
|
|
document.getElementById(Miscellaneous.CANVAS_ID)!.focus();
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2024-03-02 04:02:20 -07:00
|
|
|
const interaction = () => {
|
|
|
|
if (modalOpen) {
|
2024-03-02 06:00:47 -07:00
|
|
|
close();
|
2024-03-02 04:02:20 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
modalOpen = true;
|
2024-03-04 16:31:27 -07:00
|
|
|
|
|
|
|
openModal(
|
|
|
|
"<div class='code'><div id='code'></div><br><p id='syntax-error' class='error'></p><button id='close-modal'>Save</button></div>",
|
|
|
|
);
|
|
|
|
const spawner = this.getComponent<LambdaSpawn>(
|
|
|
|
ComponentNames.LambdaSpawn,
|
|
|
|
);
|
|
|
|
|
|
|
|
const startState = EditorState.create({
|
|
|
|
doc: spawner.code,
|
|
|
|
extensions: [
|
|
|
|
basicSetup,
|
|
|
|
keymap.of(defaultKeymap),
|
|
|
|
rainbowBrackets(),
|
|
|
|
highlightExtension,
|
|
|
|
],
|
|
|
|
});
|
|
|
|
|
|
|
|
const codeBox = document.getElementById("code")!;
|
|
|
|
editorView = new EditorView({
|
|
|
|
state: startState,
|
|
|
|
parent: codeBox,
|
|
|
|
});
|
|
|
|
|
|
|
|
editorView.focus();
|
2024-03-02 06:00:47 -07:00
|
|
|
|
|
|
|
document.getElementById("close-modal")!.addEventListener("click", close);
|
2024-03-02 04:02:20 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
this.addComponent(new Interactable(interaction));
|
|
|
|
}
|
|
|
|
}
|