Skip to content

Commit

Permalink
fix: fixed freezing bug where timer was cancelled on invalid response
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoalee committed Sep 16, 2021
1 parent 6f02e56 commit 8dee5e0
Show file tree
Hide file tree
Showing 14 changed files with 99 additions and 3,028 deletions.
1 change: 0 additions & 1 deletion src/app/models/TaskData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ export class StroopTaskData extends TaskData {
userAnswer: UserResponse;
isCongruent: boolean;
responseTime: number;
set: number;
isCorrect: boolean;
score: number;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Component, OnInit } from "@angular/core";
import { SessionStorageService } from "src/app/services/sessionStorage.service";
import { SnackbarService } from "src/app/services/snackbar.service";
import { UserService } from "src/app/services/user.service";

@Component({
Expand All @@ -14,7 +13,6 @@ export class ParticipantDashboardComponent implements OnInit {
constructor(
private sessionStorageService: SessionStorageService,
private userService: UserService,
private snackbarService: SnackbarService
) {}

ngOnInit(): void {
Expand All @@ -28,8 +26,9 @@ export class ParticipantDashboardComponent implements OnInit {
this.userService.updateStudyUsers();
},
(err) => {
// user has already registered for this study, but no need to show error
// as it confuses users who think there's an issue
this.sessionStorageService.clearSessionStorage();
this.snackbarService.openInfoSnackbar(err.message);
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,17 +319,19 @@ export class DemandSelectionComponent extends AbstractBaseTaskComponent {
@HostListener("window:keydown", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
const thisTrial = this.taskData[this.taskData.length - 1];
this.clearHelpMessage();
this.clearMaxResponseTimer();
thisTrial.submitted = this.timerService.getCurrentTimestamp();

if (event === null) {
this.clearHelpMessage();
this.clearMaxResponseTimer();
thisTrial.userAnswer = UserResponse.NA;
thisTrial.score = 0;
thisTrial.isCorrect = false;
thisTrial.respondToNumberResponseTime = this.maxResponseTime;
super.handleRoundInteraction(null);
} else if (this.responseAllowed && this.isValidKey(event.key)) {
this.clearHelpMessage();
this.clearMaxResponseTimer();
thisTrial.respondToNumberResponseTime = this.timerService.stopTimerAndGetTime();
const selectedColor = this.currentStimulus[this.selectedPatch];
if (selectedColor === this.oddEvenColor) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,21 +247,23 @@ export class DigitSpanComponent extends AbstractBaseTaskComponent {
}

handleRoundInteraction($event: string) {
this.cancelAllTimers();
this.currentTrial.submitted = this.timerService.getCurrentTimestamp();

if ($event === null) {
this.cancelAllTimers();
this.currentTrial.isCorrect = false;
this.currentTrial.score = 0;
this.currentTrial.responseTime = this.maxResponseTime;
this.currentTrial.userAnswer = UserResponse.NA;
} else if ($event === UserResponse.NA) {
this.cancelAllTimers();
this.responseAllowed = false;
this.currentTrial.isCorrect = false;
this.currentTrial.score = 0;
this.currentTrial.responseTime = this.timerService.stopTimerAndGetTime();
this.currentTrial.userAnswer = UserResponse.NA;
} else {
this.cancelAllTimers();
this.responseAllowed = false;
this.currentTrial.responseTime = this.timerService.stopTimerAndGetTime();
this.currentTrial.userAnswer = this.padString($event);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,17 +201,18 @@ export class NBackComponent extends AbstractBaseTaskComponent {

@HostListener("window:keydown", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
this.cancelAllTimers();
this.currentTrial.submitted = this.timerService.getCurrentTimestamp();

if (event === null) {
// max time out
this.cancelAllTimers();
this.currentTrial.userAnswer = UserResponse.NA;
this.currentTrial.score = 0;
this.currentTrial.responseTime = this.maxResponseTime;
this.currentTrial.isCorrect = false;
super.handleRoundInteraction(null);
} else if (this.responseAllowed && this.isValidKey(event.key)) {
this.cancelAllTimers();
this.responseAllowed = false;
this.currentTrial.responseTime = this.timerService.stopTimerAndGetTime();
this.currentTrial.userAnswer = event.key === Key.ARROWLEFT ? UserResponse.NO : UserResponse.YES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,17 +236,18 @@ export class OddballComponent extends AbstractBaseTaskComponent {
*/
@HostListener("window:keypress", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
this.cancelAllTimers();
const thisTrial = this.taskData[this.taskData.length - 1];
thisTrial.submitted = this.timerService.getCurrentTimestamp();
if (this.responseAllowed && this.isValidKey(event.key)) {
this.cancelAllTimers();
this.responseAllowed = false;

thisTrial.userAnswer = event.key;
thisTrial.responseTime = this.timerService.stopTimerAndGetTime();

super.handleRoundInteraction(event);
} else if (event === null) {
this.cancelAllTimers();
// we reached max response time
thisTrial.responseTime = this.maxResponseTime;
thisTrial.userAnswer = UserResponse.NA;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,17 +280,18 @@ export class SmileyFaceComponent extends AbstractBaseTaskComponent {

@HostListener("window:keypress", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
this.cancelAllTimers();
const thisTrial = this.taskData[this.taskData.length - 1];
thisTrial.submitted = this.timerService.getCurrentTimestamp();
if (this.responseAllowed && this.isValidKey(event.key)) {
this.cancelAllTimers();
this.responseAllowed = false;
thisTrial.responseTime = this.timerService.stopTimerAndGetTime();
thisTrial.userAnswer = event.key === Key.Z ? UserResponse.SHORT : UserResponse.LONG;
thisTrial.keyPressed = event.key === Key.Z ? Key.Z : Key.M;

super.handleRoundInteraction(event.key);
} else if (event === null) {
this.cancelAllTimers();
// max time out
this.responseAllowed = false;
thisTrial.responseTime = this.maxResponseTime;
Expand Down
14 changes: 8 additions & 6 deletions src/app/pages/tasks/task-playables/stroop/stroop.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface StroopTaskMetadata {
durationOfFeedback: number;
durationFixationPresented: number;
numTrials: number;
numCongruent: number;
stimuliConfig: {
type: StimuliProvidedType;
stimuli: StroopStimulus[];
Expand Down Expand Up @@ -58,6 +59,7 @@ export class StroopComponent extends AbstractBaseTaskComponent {
private durationOfFeedback: number;
private durationFixationPresented: number;
private numTrials: number;
private numCongruent: number;

// high level variables
counterbalance: number;
Expand Down Expand Up @@ -114,6 +116,7 @@ export class StroopComponent extends AbstractBaseTaskComponent {
this.showFeedbackAfterEachTrial = thisOrDefault(metadata.config.showFeedbackAfterEachTrial, false);
this.durationOfFeedback = thisOrDefault(metadata.config.durationOfFeedback, 0);
this.showScoreAfterEachTrial = thisOrDefault(metadata.config.showScoreAfterEachTrial, false);
this.numCongruent = thisOrDefault(metadata.config.numCongruent, this.numTrials / 2);

this.counterbalance = config.counterBalanceGroups[config.counterbalanceNumber] as number;

Expand All @@ -128,9 +131,8 @@ export class StroopComponent extends AbstractBaseTaskComponent {
// either the stimuli has been defined in config or we generate it here from service
if (!this.stimuli) {
this.stimuli = this.dataGenService.generateStroopStimuli(
this.isPractice,
this.numTrials,
this.counterbalance
this.numCongruent
);
}
super.start();
Expand All @@ -145,6 +147,8 @@ export class StroopComponent extends AbstractBaseTaskComponent {
if (this.isDestroyed) return;
this.showFixation = false;

this.setStimuliUI(this.currentStimulus);

this.taskData.push({
userID: this.userID,
trial: ++this.trialNum,
Expand All @@ -154,14 +158,11 @@ export class StroopComponent extends AbstractBaseTaskComponent {
responseTime: 0,
isCorrect: false,
score: 0,
set: this.counterbalance,
submitted: this.timerService.getCurrentTimestamp(),
isPractice: this.isPractice,
studyId: this.studyId,
});

this.setStimuliUI(this.currentStimulus);

this.setTimer(this.maxResponseTime, () => {
this.showStimulus = false;
this.responseAllowed = false;
Expand Down Expand Up @@ -196,10 +197,10 @@ export class StroopComponent extends AbstractBaseTaskComponent {

@HostListener("window:keypress", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
this.cancelAllTimers();
const thisTrial = this.taskData[this.taskData.length - 1];
thisTrial.submitted = this.timerService.getCurrentTimestamp();
if (this.responseAllowed && this.isValidKey(event.key)) {
this.cancelAllTimers();
this.responseAllowed = false;

thisTrial.responseTime = this.timerService.stopTimerAndGetTime();
Expand All @@ -219,6 +220,7 @@ export class StroopComponent extends AbstractBaseTaskComponent {
}
super.handleRoundInteraction(thisTrial.userAnswer);
} else if (event === null) {
this.cancelAllTimers();
// max time out
thisTrial.userAnswer = UserResponse.NA;
thisTrial.score = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,17 +209,18 @@ export class TaskSwitchingComponent extends AbstractBaseTaskComponent {

@HostListener("window:keydown", ["$event"])
handleRoundInteraction(event: KeyboardEvent) {
this.cancelAllTimers();
const thisTrial = this.taskData[this.taskData.length - 1];
thisTrial.submitted = this.timerService.getCurrentTimestamp();

if (event === null) {
this.cancelAllTimers();
thisTrial.isCorrect = false;
thisTrial.responseTime = this.maxResponseTime;
thisTrial.score = 0;
super.handleRoundInteraction(null);
return;
} else if (this.responseAllowed && this.isValidKey(event.key)) {
this.cancelAllTimers();
this.responseAllowed = false;

let userAnswer: UserResponse;
Expand Down
30 changes: 29 additions & 1 deletion src/app/services/data-generation/data-generation.service.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { TestBed } from "@angular/core/testing";
import { ImageService } from "../image.service";
import { DataGenerationService } from "./data-generation.service";
import { SmileyFaceStimulus, SmileyFaceType } from "./stimuli-models";
import { SmileyFaceStimulus, SmileyFaceType, StroopStimulus } from "./stimuli-models";

describe("Data Generation Service", () => {
let service: DataGenerationService;
Expand Down Expand Up @@ -95,4 +95,32 @@ describe("Data Generation Service", () => {
expect(func).toThrow(new Error("Num rewarded cannot be greater than the number of trials"));
});
});

describe('stroop task stimuli', () => {
it('should throw an error if number of congruent trials is greater than number of trials', () => {
const func = () => {
service.generateStroopStimuli(1, 2);
}
expect(func).toThrow(new Error("Number of congruent trials must be fewer than number of trials"));
});

it('should generate the correct number of congruent trials', () => {
const generatedStroopStimuli = service.generateStroopStimuli(60, 40);
const numCongruentTrials = generatedStroopStimuli.reduce((acc: number, currVal: StroopStimulus) => {
return currVal.congruent ? acc + 1 : acc;
}, 0);
expect(numCongruentTrials).toEqual(40);
expect(generatedStroopStimuli.length).toEqual(60);
});

it('should generate the correct congruent and noncongruent trials', () => {
const generatedStroopStimuli = service.generateStroopStimuli(60, 40);

generatedStroopStimuli.forEach(element => {
element.congruent ?
expect(element.color).toEqual(element.word) :
expect(element.color).not.toEqual(element.word);
});
});
});
});
58 changes: 43 additions & 15 deletions src/app/services/data-generation/data-generation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import {
TrailMakingStimulus,
TrailMakingTrialType,
} from "./stimuli-models";
import { StroopSet } from "./raw-data/stroop-data-list";
import { NBackSet } from "./raw-data/nback-data-list";
import { Color } from "src/app/models/InternalDTOs";
import { DemandSelectionImageNames } from "./raw-data/demand-selection-image-list";
Expand Down Expand Up @@ -184,21 +183,50 @@ export class DataGenerationService {

// end of oddball data generation

generateStroopStimuli(isPractice: boolean, numTrials: number, counterbalance: number): StroopStimulus[] {
const stroopSets = Object.keys(StroopSet);
generateStroopStimuli(numTrials: number, numCongruent: number): StroopStimulus[] {
if (numTrials < numCongruent) throw new Error("Number of congruent trials must be fewer than number of trials");

// subtract 1 because one set is the practice set
if (counterbalance < 1 || counterbalance > stroopSets.length - 1)
throw new Error("No such stroop group exists");
if (isPractice) {
if (numTrials > StroopSet.practice.length)
throw new Error("number of trials greater than number of practice trials");
return StroopSet.practice.slice(0, numTrials);
} else {
const selectedSet = StroopSet[counterbalance] as StroopStimulus[];
if (numTrials > selectedSet.length)
throw new Error("number of trials greater than number of stroop trials");
return selectedSet.slice(0, numTrials);
const generatedStimuli: StroopStimulus[] = new Array(numTrials);
const congruentIndices = generateRandomNonrepeatingNumberList(numCongruent, 0, numTrials);

for(let i = 0; i < generatedStimuli.length; i++) {
const color = this.getNewColor();
const isCongruentTrial = congruentIndices.includes(i);

if (isCongruentTrial) {
generatedStimuli[i] = {
color: color,
congruent: true,
word: color
}
} else {
let nonCongruentColor = color;
while(color === nonCongruentColor) {
nonCongruentColor = this.getNewColor();
}

generatedStimuli[i] = {
color: color,
congruent: false,
word: nonCongruentColor
}
}
}

return generatedStimuli;
}

private getNewColor(): 'red' | 'blue' | 'green' {
const num = getRandomNumber(0, 3)
switch (num) {
case 0:
return 'red';
case 1:
return 'blue';
case 2:
return 'green';
default:
break;
}
}

Expand Down
Loading

0 comments on commit 8dee5e0

Please sign in to comment.