Skip to content

Commit

Permalink
refactor(algo): Merge self-paced into practice-test
Browse files Browse the repository at this point in the history
  • Loading branch information
CaedenPH committed Mar 31, 2024
1 parent 7b41ad2 commit 8244ca5
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 134 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,6 @@ Practice mode:
- Presents up to 20 screens to acquit the user with how the CogSpeed test works.
- These rounds are un-judged, and do not contribute to the remainder of the test.
- Roughly 4 correct answers in a row with an art less than ``right_count_art_less_than`` will continue on to self-paced mode. This is the only path to continue to self-paced mode.
- If more than 20 screens pass without the above-mentioned condition, the test will exit unsuccessfully with error code 4.
- If more than 20 screens pass without the above-mentioned condition, the test will exit unsuccessfully with error code 4.

- Merges self-paced into practice-test
148 changes: 72 additions & 76 deletions src/routes/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { ProcessResultsPage } from "./results";
* Note the order of the rounds is as follows:
* 0: training
* 1: practice
* 2: self-paced startup
* 3: machine-paced
* 4: postblock
* 5: self-paced restart
Expand All @@ -37,7 +36,7 @@ export class CogSpeedGame {
currentTimeout: number = -1;

// Round types
currentRound: 0 | 1 | 2 | 3 | 4 | 5 | 6 = 0;
currentRound: 0 | 1 | 2 | 3 | 4 | 5 = 0;

// Hold the timeout of the last two blocks
previousBlockTimeouts: number[] = [-1];
Expand All @@ -55,12 +54,17 @@ export class CogSpeedGame {
maxTestTimeout: NodeJS.Timeout | undefined;
currentRoundTimeout: NodeJS.Timeout | undefined;

// Misc
isInPause: number | null;

constructor(
public config: Config,
private app: Application | null = null,
private ui: CogSpeedGraphicsHandler | null = null,
private sleepData: SleepData | null = null,
) {}
) {
this.isInPause = null;
}

/**
* Returns the fraction of correct answers in the rolling mean
Expand All @@ -72,7 +76,7 @@ export class CogSpeedGame {
let i = this.previousAnswers.length - 1;
while (this.config.machine_paced.rolling_average.mean_size > totalAnswers) {
const answer = this.previousAnswers[i];
if (!answer || answer.roundType !== 3) break;
if (!answer || answer.roundType !== 2) break;

if (answer.isCorrectOrIncorrectFromPrevious) {
if (answer.status === "correct") correctAnswers += 1;
Expand Down Expand Up @@ -121,17 +125,17 @@ export class CogSpeedGame {
const rounds = {
0: this.trainingRound,
1: this.practiceMode,
2: this.selfPacedStartupRound,
3: this.machinePacedRound,
4: this.postBlockRound,
5: this.selfPacedRestartRound,
6: this.finalRounds,
2: this.machinePacedRound,
3: this.postBlockRound,
4: this.selfPacedRestartRound,
5: this.finalRounds,
};
rounds[this.currentRound].bind(this)();
}

async displayCorrectAnswer() {
if (!this.ui?.inputButtons) return;
this.isInPause = this.answer;

const answerSprite = this.ui.inputButtons[6 - this.answer];
for (let i = 0; i < 3; i ++){
Expand All @@ -141,6 +145,7 @@ export class CogSpeedGame {

// TODO: Exit if incorrect button was pressed immediately
if (await this.ui.waitForKeyPress(answerSprite, this.config.practice_mode.no_response_duration)) {
this.isInPause = null;
return
}
return this.stop(4);
Expand Down Expand Up @@ -171,30 +176,7 @@ export class CogSpeedGame {
* then the test fails.
*
*
*
* Round type 1
*/
async practiceMode() {
// 1) Set no response timeout
clearTimeout(this.currentRoundTimeout);
this.currentRoundTimeout = setTimeout(this.displayCorrectAnswer.bind(this), this.config.practice_mode.no_response_duration);

const practiceTestAnswers = this.previousAnswers.filter((answer) => answer.roundType === 1);

// 2) If there have been (roughly 4) correct answers in a row under (roughly 2600ms), continue to self-paced
if (practiceTestAnswers.slice(-this.config.practice_mode.max_right_count).filter((answer) => answer.status === "correct").length === this.config.practice_mode.max_right_count
&& practiceTestAnswers.slice(-this.config.practice_mode.max_right_count).reduce((a, b) => a + b.timeTaken, 0) / this.config.practice_mode.max_right_count < this.config.practice_mode.right_count_art_less_than) {
this.currentRound = 2;
return this.selfPacedStartupRound();
}

// 2) If more than (roughly 20) answers have occurred without (roughly 4) successful answers, exit test unsuccessfully
if (practiceTestAnswers.length > this.config.practice_mode.total_answer_count) {
return this.stop(4);
}
}

/**
* Merge: self-paced
* Self paced startup rounds to deduce a baseline to start the
* machine-paced at.
*
Expand All @@ -203,41 +185,48 @@ export class CogSpeedGame {
* 3. Checks for more than total-correct-count without max-right-count in a row [exit unsuccessfully]
* 4. Checks for max-right-count in a row -> [next round]
*
* Round type 2
* Round type 1
*/
async selfPacedStartupRound() {
async practiceMode() {
// 1) Set no response timeout (roughly 6000ms)
clearTimeout(this.currentRoundTimeout);
this.currentRoundTimeout = setTimeout(this.stop.bind(this), this.config.self_paced.no_response_duration);

// 2) Max wrong limit (roughly 5)
const selfPacedAnswers = this.previousAnswers.filter((answer) => answer.roundType === 2);
const wrongAnswers = selfPacedAnswers.filter((answer) => answer.status === "incorrect");
if (wrongAnswers.length >= this.config.self_paced.max_wrong_count) return this.stop(2);

// 3) More than (roughly 12) correct answers that are less than (roughly 3000ms)
// But not (roughly 4) correct answers in a row
const correctAnswers = selfPacedAnswers.filter(
(answer) => answer.status === "correct" && answer.timeTaken <= this.config.self_paced.max_correct_duration,
);
if (correctAnswers.length >= this.config.self_paced.total_correct_count) return this.stop(2);

// 4) If (roughly 4) correct answers in a row
// We move to the next round
const lastNAnswers = selfPacedAnswers.slice(-this.config.self_paced.max_right_count);
if (lastNAnswers.filter((answer) => answer.status === "correct").length === this.config.self_paced.max_right_count) {
this.currentRound = 3;
// Set machine paced timeout
this.currentRoundTimeout = setTimeout(this.displayCorrectAnswer.bind(this), this.config.practice_mode.no_response_duration);

const practiceTestAnswers = this.previousAnswers.filter((answer) => answer.roundType === 1);

const lastNAnswers = practiceTestAnswers.slice(-this.config.practice_mode.max_right_count);
// 2) If there have been (roughly 4) correct answers in a row under (roughly 2600ms), continue to self-paced
if (lastNAnswers.filter((answer) => answer.status === "correct").length === this.config.practice_mode.max_right_count
&& lastNAnswers.reduce((a, b) => a + b.timeTaken, 0) / this.config.practice_mode.max_right_count < this.config.practice_mode.right_count_art_less_than) {
this.currentRound = 2;
this.currentTimeout =
Math.min(
lastNAnswers.map((answer) => answer.timeTaken).reduce((a, b) => a + b, 0) / 4,
lastNAnswers.map((answer) => answer.timeTaken).reduce((a, b) => a + b, 0) / this.config.practice_mode.max_right_count,
this.config.machine_paced.max_start_duration,
) - this.config.machine_paced.initial_speedup_amount; // Minimum response time (roughly 100ms)
// Call next round
return this.machinePacedRound();
}

// 3) Max wrong limit
const wrongAnswers = practiceTestAnswers.filter((answer) => answer.status === "incorrect");
if (wrongAnswers.length >= this.config.self_paced.max_wrong_count) {
return this.stop(4);
}

// 3) If more than (roughly 20) answers have occurred without (roughly 4) successful answers, exit test unsuccessfully
if (practiceTestAnswers.length > this.config.practice_mode.total_answer_count) {
return this.stop(4);
}
}

// KEEP: REF [depr?]
// async selfPacedStartupRound() {
// // 3) More than (roughly 12) correct answers that are less than (roughly 3000ms)
// // But not (roughly 4) correct answers in a row
// const correctAnswers = selfPacedAnswers.filter(
// (answer) => answer.status === "correct" && answer.timeTaken <= this.config.self_paced.max_correct_duration,
// );

/**
* Machine paced rounds attempt to force a block by slowly increasing the timeout
*
Expand All @@ -246,11 +235,11 @@ export class CogSpeedGame {
* 3. Check if roll mean limit exceeded [go to self-paced-restart mode]
* 4. Set no response timeout [next round]
*
* Round type 3
* Round type 2
*/
async machinePacedRound() {
// 1) Determine speedup and slowdown amount based on the ratio last answer
const lastAnswer = this.previousAnswers.slice(-1).filter((answer) => answer.roundType === 3)[0];
const lastAnswer = this.previousAnswers.slice(-1).filter((answer) => answer.roundType === 2)[0];
// If there is a last answer, change the timeout
if (lastAnswer) {
if (lastAnswer.status === "correct") {
Expand All @@ -273,12 +262,12 @@ export class CogSpeedGame {
// Indicating that the user has blocked
const lastNAnswers = this.previousAnswers
.slice(-this.config.machine_paced.blocking.no_input_count)
.filter((answer) => answer.roundType === 3);
.filter((answer) => answer.roundType === 2);
if (
lastNAnswers.filter((answer) => answer.status === "no response").length ===
this.config.machine_paced.blocking.no_input_count
) {
this.currentRound = 4;
this.currentRound = 3;
// Add the block time to the previousBlockTimeouts
this.previousBlockTimeouts.push(this.currentTimeout);
// Slow down the timeout (roughly 275ms)
Expand All @@ -291,7 +280,7 @@ export class CogSpeedGame {
// So we place the user in an SP Restart Phase
const correctRatio = this.getCorrectRollingMean();
if (correctRatio < this.config.machine_paced.rolling_average.threshold) {
this.currentRound = 5;
this.currentRound = 4;
this.numberOfRollMeanLimitExceedences++;
return this.selfPacedRestartRound();
}
Expand All @@ -309,7 +298,7 @@ export class CogSpeedGame {
* 1. Checks if two consecutive similar-timed blocks exist [exit successfully]
* 2. Check if there are too many blocks [exit unsuccessfully]
*
* Round type 4
* Round type 3
*/
async postBlockRound(): Promise<void> {
const lastTwoBlocks = this.previousBlockTimeouts.slice(-2);
Expand All @@ -318,7 +307,7 @@ export class CogSpeedGame {
lastTwoBlocks.length === 2 &&
Math.abs(lastTwoBlocks[0] - lastTwoBlocks[1]) < this.config.machine_paced.blocking.duration_delta
) {
this.currentRound = 6;
this.currentRound = 5;
return this.finalRounds();
}

Expand All @@ -331,18 +320,18 @@ export class CogSpeedGame {
// If the last (roughly 2) answers were correct, continue to machine paced
const lastNAnswers = this.previousAnswers
.slice(-this.config.machine_paced.blocking.min_correct_answers)
.filter((answer) => answer.roundType === 4);
.filter((answer) => answer.roundType === 3);
if (
lastNAnswers.filter((answer) => answer.status === "correct").length ===
this.config.machine_paced.blocking.min_correct_answers
) {
this.currentRound = 3;
this.currentRound = 2;
return this.machinePacedRound();
}

// 3) If there are (roughly 3) answers wrong before (roughly 2) correct answers in a row
// So end test unsuccessfully
const lastPostBlockAnswers = this.previousAnswers.filter((answer) => answer.roundType === 4);
const lastPostBlockAnswers = this.previousAnswers.filter((answer) => answer.roundType === 3);
if (
lastPostBlockAnswers.filter((answer) => answer.status === "incorrect").length ===
this.config.machine_paced.blocking.max_wrong_answers
Expand All @@ -362,19 +351,19 @@ export class CogSpeedGame {
*
* @augments CogSpeedGame.postBlockRound
*
* Round type 5
* Round type 4
*/
async selfPacedRestartRound():Promise<void> {
// 1) We can exit self paced restart successfully with (roughly 2) correct answers in a row
// If the last (roughly 2) answers were correct, continue to machine paced
const lastNAnswers = this.previousAnswers
.slice(-this.config.machine_paced.blocking.min_correct_answers)
.filter((answer) => answer.roundType === 5);
.filter((answer) => answer.roundType === 4);
if (
lastNAnswers.filter((answer) => answer.status === "correct").length ===
this.config.machine_paced.blocking.min_correct_answers
) {
this.currentRound = 3;
this.currentRound = 2;
this.currentTimeout -= this.config.machine_paced.incorrect.base_duration;
return this.machinePacedRound();
}
Expand All @@ -384,11 +373,11 @@ export class CogSpeedGame {
const lastNonSelfPacedRestartAnswer = this.previousAnswers
.slice()
.reverse()
.findIndex((answer) => answer.roundType !== 5);
.findIndex((answer) => answer.roundType !== 4);

const lastSelfPacedRestartAnswers = this.previousAnswers
.slice(-lastNonSelfPacedRestartAnswer)
.filter((answer) => answer.roundType === 5);
.filter((answer) => answer.roundType === 4);
if (
lastSelfPacedRestartAnswers.filter((answer) => answer.status === "incorrect").length ===
this.config.machine_paced.blocking.max_wrong_answers
Expand All @@ -401,12 +390,12 @@ export class CogSpeedGame {
/**
* The final unscored rounds that act as confusion rounds.
*
* Round type 6
* Round type 5
*/
async finalRounds() {
const lastNRounds = this.previousAnswers
.slice(-this.config.number_of_endmode_rounds)
.filter((answer) => answer.roundType === 6);
.filter((answer) => answer.roundType === 5);
if (lastNRounds.length === this.config.number_of_endmode_rounds) {
return this.stop(0); // Successfully exit (only way to successfully exit)
}
Expand All @@ -418,7 +407,13 @@ export class CogSpeedGame {
* @param {number | boolean} location The location of the button clicked or false if no response
* @param {number} timeClicked The time (performance.now) the button was clicked
*/
public buttonClicked(location: number | null = null, timeClicked: number | null = null): void {
public buttonClicked(location: number | null = null, timeClicked: number | null = null): boolean {
if (this.ui && this.isInPause !== null && this.isInPause != location) {
// It should only respond to one in this mode
this.ui.rippleAnimation(this.ui.inputButtons[6 - this.answer]);
return false;
}

timeClicked = timeClicked || performance.now();

const previousAnswer = this.previousAnswers[this.previousAnswers.length - 1];
Expand Down Expand Up @@ -484,6 +479,7 @@ export class CogSpeedGame {
}

this.nextRound();
return true;
}

/**
Expand Down Expand Up @@ -544,7 +540,7 @@ export class CogSpeedGame {
const highestBlockTime = Math.max(...this.previousBlockTimeouts.slice(1, blockCount + 1));

const firstMachinePacedRound: GameAnswer | undefined = this.previousAnswers.filter(
(answer) => answer.roundType === 3,
(answer) => answer.roundType === 2,
)[0];

const totalMachinePacedAnswers = filterByRoundType(this.previousAnswers, 3);
Expand Down
8 changes: 4 additions & 4 deletions src/ui/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export class CogSpeedGraphicsHandler {
public readyDemoTextures: Texture[];

answerSprite: Sprite | null;
inputButtons: Sprite[] | null;
inputButtons: Sprite[];

constructor(public app: Application) {
this.gearWellTexture = Texture.from(gearWellTextureImage);
Expand Down Expand Up @@ -114,7 +114,7 @@ export class CogSpeedGraphicsHandler {
// Load button assets
this.smallButtons = this.loadButtons();
this.answerSprite = null;
this.inputButtons = null;
this.inputButtons = [];
}

public async emulateLoadingTime() {
Expand Down Expand Up @@ -379,8 +379,8 @@ export class CogSpeedGraphicsHandler {
const button = buttons[i - 1];
button.eventMode = "dynamic";
button.on("pointerdown", () => {
game.buttonClicked(7 - i);
this.rippleAnimation(button);
const ripple = game.buttonClicked(7 - i);
if (ripple) this.rippleAnimation(button);
});
}

Expand Down
Loading

0 comments on commit 8244ca5

Please sign in to comment.