Files
DeviceManager/src/lib/charrecorder/core/step.ts
Thea Schöbl b8b903c5e1 refactor: update to Svelte 5 preview
feat: add charrecorder
feat: dynamic os layouts for CC1
2024-08-01 00:28:38 +02:00

133 lines
3.3 KiB
TypeScript

import { ROBOT_THRESHOLD } from "./player";
import type { LiveReplayEvent, ReplayEvent, TextToken } from "./types";
/**
* This is the "heart" of the player logic
*/
export class ReplayStepper {
held = new Map<string, boolean>();
text: TextToken[];
cursor = 0;
challenge: TextToken[];
ghostCount: number;
mistakeCount = 0;
constructor(initialReplay: ReplayEvent[] = [], challenge = "") {
this.challenge = challenge.split("").map((text) => ({
stamp: 0,
duration: 0,
code: "",
text,
source: "ghost",
correct: true,
}));
this.text = [...this.challenge];
this.ghostCount = this.challenge.length;
for (const key of initialReplay) {
this.step(...key);
}
}
step(
...[output, code, at, duration]: ReplayEvent | LiveReplayEvent
): TextToken | undefined {
let token: TextToken | undefined = undefined;
if (output === "Backspace") {
if (this.held.has("ControlLeft") || this.held.has("ControlRight")) {
let wordIndex = 0;
for (let i = this.cursor - 1; i >= 0; i--) {
if (/\w+/.test(/** @type {TextToken} */ this.text[i]!.text)) {
wordIndex = i;
} else if (wordIndex !== 0) {
break;
}
}
this.text.splice(wordIndex, this.cursor - wordIndex);
} else if (this.cursor !== 0) {
this.text.splice(this.cursor - 1, 1);
}
this.cursor = Math.min(
this.cursor,
this.text.length - this.ghostCount + 1,
);
}
if (output.length === 1) {
token = {
stamp: at,
duration,
code,
text: output,
source:
duration === undefined
? undefined
: duration < ROBOT_THRESHOLD
? "robot"
: "human",
correct: true,
};
this.text.splice(this.cursor, 0, token);
}
if (code === "ArrowLeft" || code === "Backspace") {
this.cursor = Math.max(this.cursor - 1, 0);
}
if (code === "ArrowRight" || output.length === 1) {
this.cursor = Math.min(
this.cursor + 1,
this.text.length - this.ghostCount,
);
}
if (code === "Enter") {
token = {
stamp: at,
code,
duration,
text: "\n",
source:
duration === undefined
? undefined
: duration < ROBOT_THRESHOLD
? "robot"
: "human",
correct: true,
};
this.text.splice(this.cursor, 0, token);
this.cursor++;
}
if (this.challenge.length > 0) {
let challengeIndex = 0;
this.mistakeCount = 0;
for (let i = 0; i < this.text.length - this.ghostCount; i++) {
if (this.text[i]!.text === this.challenge[challengeIndex]?.text) {
this.text[i]!.correct = true;
} else {
this.mistakeCount++;
this.text[i]!.correct = false;
}
challengeIndex++;
}
const currentGhostCount = this.ghostCount;
this.ghostCount = Math.max(0, this.challenge.length - challengeIndex);
this.text.splice(
this.text.length - currentGhostCount,
Math.max(0, currentGhostCount - this.ghostCount),
...this.challenge.slice(
challengeIndex,
challengeIndex + Math.max(0, this.ghostCount - currentGhostCount),
),
);
}
return token;
}
}