Skip to content

Commit

Permalink
100 start information task sang et al (#101)
Browse files Browse the repository at this point in the history
* feat: added information task files

* feat: updated information task

* feat: completed information task
  • Loading branch information
nicoalee authored May 29, 2023
1 parent 9092390 commit a49c9ff
Show file tree
Hide file tree
Showing 10 changed files with 345 additions and 1 deletion.
10 changes: 10 additions & 0 deletions src/app/models/ParticipantData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ export class IowaGamblingTaskData extends BaseParticipantData {
feePaid: number;
}

export class InformationTaskData extends BaseParticipantData {
roundNum: number;
trialScore: number;
cumulativeRoundScore: number;
cumulativeRoundLength: number;
trialResponseTime: number;
exploited: boolean;
expectedToExploit: boolean;
}

export class ParticipantData {
userId: string;
studyId: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<div class="container-fluid wrapper task-text flex-column-center">
<div *ngIf="taskStarted" class="full-box flex-column-center">
<div class="my-3">
<div style="font-size: 2rem">Click on the deck or an uncovered card</div>
</div>
<div class="cards-container" style="height: 400px">
<div *ngFor="let card of cardsDrawn" class="card-container">
<div
(click)="handleRoundInteraction('existingCard', card)"
class="card {{ largestDrawnCardValue === card ? 'largest-card' : '' }}"
>
{{ card }}
</div>
</div>
</div>
<div class="flex-row-space-between" style="flex-grow: 1; width: 100%">
<div style="width: 350px">
<div style="font-size: 2rem">{{ stimuli.cardValues.length - cardsDrawn.length }} cards in the deck</div>
<img
(click)="handleRoundInteraction('newCard', undefined)"
style="width: 200px; height: 200px"
class="deck"
src="/assets/images/stimuli/informationtask/deckOfCards.png"
alt="Deck of cards image"
/>
</div>
<div style="flex-grow: 1">
<div style="font-size: 3rem; margin-bottom: 20px">
[
<span *ngFor="let value of valuesSelected; let i = index">
{{ value }}{{ i < valuesSelected.length - 1 ? ', ' : '' }}
</span>
]
</div>
<div style="font-size: 2rem; display: flex; justify-content: center">
<div style="margin-right: 40px">Turns Taken: {{ valuesSelected.length }}</div>
<div style="margin-right: 40px">
Turns Left: {{ stimuli.cardValues.length - valuesSelected.length }}
</div>
<div>Total Points: {{ totalPoints }}</div>
</div>
</div>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.card {
border-radius: 4px;
border: 4px solid black;
color: green;
padding: 1rem;
display: flex;
width: 100px;
height: 150px;
justify-content: center;
font-size: 3rem;
font-weight: bold;
cursor: pointer;
-webkit-user-select: none; /* Safari */
-ms-user-select: none; /* IE 10 and IE 11 */
user-select: none; /* Standard syntax */

&:hover {
box-shadow: 1px 1px 10px black;
}
}

.card-container {
width: 10%;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
}

.cards-container {
width: 100%;
height: 400px;
display: flex;
flex-wrap: wrap;
}

.deck {
&:hover {
opacity: 0.6;
cursor: pointer;
}
}

.largest-card {
color: red !important;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { InformationTaskComponent } from './information-task.component';

describe('InformationTaskComponent', () => {
let component: InformationTaskComponent;
let fixture: ComponentFixture<InformationTaskComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ InformationTaskComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(InformationTaskComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import { Component, HostListener, OnInit } from '@angular/core';
import { AbstractBaseTaskComponent } from '../base-task';
import { TimerService } from 'src/app/services/timer.service';
import { LoaderService } from 'src/app/services/loader/loader.service';
import { thisOrDefault, throwErrIfNotDefined } from 'src/app/common/commonMethods';
import { TaskPlayerState } from '../task-player/task-player.component';
import { StimuliProvidedType } from 'src/app/models/enums';
import { ComponentName } from 'src/app/services/component-factory.service';
import { DataGenerationService } from 'src/app/services/data-generation/data-generation.service';
import { InformationTaskData } from 'src/app/models/ParticipantData';
import { InformationTaskStimuliSet, InformationTaskStimulus } from 'src/app/services/data-generation/stimuli-models';
import { TranslateService } from '@ngx-translate/core';

interface InformationTaskMetadata {
componentName: ComponentName;
componentConfig: {
numTrials: number;
roundNum: number;
isPractice: boolean;
stimuliConfig: {
type: StimuliProvidedType;
stimuli: InformationTaskStimuliSet;
};
};
}

export enum InformationTaskCache {
TOTAL_SCORE = 'information-task-total-score',
OPTIMAL_SCORE = 'information-task-optimal-score',
STATUS_TEXT = 'information-task-status-text',
}

@Component({
selector: 'app-information-task',
templateUrl: './information-task.component.html',
styleUrls: ['./information-task.component.scss'],
})
export class InformationTaskComponent extends AbstractBaseTaskComponent {
// config variables variables
private numTrials: number;
private roundNum: number; // determines which deck to use (1-6), translates into blockNum
private isPractice: boolean;

// high level variables
taskData: InformationTaskData[];
stimuli: InformationTaskStimuliSet;
currentStimuliIndex: number = 0;
cardsDrawn: number[];
valuesSelected: number[];
taskStarted: boolean = false;
roundStartTime: number;
trialNum: number = 0;

// local state variables

get currentStimulus(): InformationTaskStimulus {
return this.stimuli.cardValues[this.currentStimuliIndex];
}

get currentTrial(): InformationTaskData {
// will return null if taskData is not defined or if it has length of 0
return this.taskData?.length > 0 ? this.taskData[this.taskData.length - 1] : null;
}

get largestDrawnCardValue(): number {
return this.cardsDrawn.reduce((acc, curr) => (curr > acc ? curr : acc), 0);
}

get totalPoints(): number {
return this.valuesSelected.reduce((acc, curr) => acc + curr, 0);
}

// translation mapping
translationMapping = {
scoreStatusTextLower: {
en: 'You scored lower',
fr: '',
},
scoreStatusTextEqual: {
en: 'You were equal!',
fr: '',
},
scoreStatusTextHigher: {
en: 'You scored higher!',
fr: '',
},
};

constructor(
protected timerService: TimerService,
protected dataGenService: DataGenerationService,
protected loaderService: LoaderService,
private translateService: TranslateService
) {
super(loaderService);
}

configure(metadata: InformationTaskMetadata, config?: TaskPlayerState) {
try {
this.userID = throwErrIfNotDefined(config.userID, 'no user ID defined');
this.studyId = throwErrIfNotDefined(config.studyID, 'no study code defined');

this.numTrials = throwErrIfNotDefined(metadata.componentConfig.numTrials, 'num trials not defined');
this.roundNum = throwErrIfNotDefined(metadata.componentConfig.roundNum, 'roundNum is not defined');
this.isPractice = thisOrDefault(metadata.componentConfig.isPractice, false);
} catch (error) {
throw new Error('values not defined, cannot start study');
}

this.config = config;
if (metadata.componentConfig.stimuliConfig.type === StimuliProvidedType.HARDCODED)
this.stimuli = metadata.componentConfig.stimuliConfig.stimuli;
}

start(): void {
console.log(this.stimuli);
// configure game
this.taskStarted = true;

this.taskData = [];
this.cardsDrawn = [];
this.valuesSelected = [];
this.currentStimuliIndex = 0;
this.trialNum = 0;

this.roundStartTime = Date.now();
super.start();
}

beginRound() {
this.timerService.clearTimer();
this.timerService.startTimer();
}

handleRoundInteraction(cardType: 'newCard' | 'existingCard', existingCardVal?: number) {
if (!this.taskStarted) return;

const newCardVal = this.currentStimulus;

let cardValue: number;
if (cardType === 'newCard') {
this.cardsDrawn.push(newCardVal.cardValue);
cardValue = newCardVal.cardValue;
} else {
cardValue = existingCardVal;
}

this.valuesSelected.push(cardValue);

this.taskData.push({
userID: this.userID,
studyId: this.studyId,
submitted: this.timerService.getCurrentTimestamp(),
isPractice: this.isPractice,
trial: ++this.trialNum,
roundNum: this.roundNum,
trialScore: cardValue,
cumulativeRoundLength: Date.now() - this.roundStartTime,
cumulativeRoundScore:
this.taskData.length <= 0
? cardValue
: this.taskData[this.taskData.length - 1].cumulativeRoundScore + cardValue,
exploited: cardType === 'existingCard',
expectedToExploit: newCardVal.expectedToExploit,
trialResponseTime: this.timerService.stopTimerAndGetTime(),
});
super.handleRoundInteraction(null);
}

async completeRound() {
super.completeRound();
}

async decideToRepeat(): Promise<void> {
if (this.trialNum >= this.numTrials) {
this.taskStarted = false;
const totalScore = this.taskData.reduce((acc, currVal) => {
return acc + currVal.trialScore;
}, 0);

const optimalScore = this.stimuli.optimalScore;

console.log({ totalScore, optimalScore });

const statusText =
totalScore < optimalScore
? this.translationMapping.scoreStatusTextLower[this.translateService.currentLang]
: totalScore === optimalScore
? this.translationMapping.scoreStatusTextEqual[this.translateService.currentLang]
: this.translationMapping.scoreStatusTextHigher[this.translateService.currentLang];

// this will replace the previous round
this.config.setCacheValue(InformationTaskCache.TOTAL_SCORE, totalScore);
this.config.setCacheValue(InformationTaskCache.OPTIMAL_SCORE, optimalScore);
this.config.setCacheValue(InformationTaskCache.STATUS_TEXT, statusText);

super.decideToRepeat();
} else {
this.currentStimuliIndex++;
this.beginRound();
return;
}
}
}
2 changes: 2 additions & 0 deletions src/app/pages/tasks/task.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { BlankComponent } from './blank/blank.component';
import { ProbabilisticLearningTaskComponent } from './task-playables/probabilistic-learning-task/probabilistic-learning-task.component';
import { OptionDisplayComponent } from './shared/option-display/option-display.component';
import { IowaGamblingTaskComponent } from './task-playables/iowa-gambling-task/iowa-gambling-task.component';
import { InformationTaskComponent } from './task-playables/information-task/information-task.component';

@NgModule({
declarations: [
Expand All @@ -64,6 +65,7 @@ import { IowaGamblingTaskComponent } from './task-playables/iowa-gambling-task/i
TaskPlayerComponent,
TaskSwitchingComponent,
TrailMakingComponent,
InformationTaskComponent,

EmbeddedPageComponent,

Expand Down
3 changes: 3 additions & 0 deletions src/app/services/component-factory.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { EmbeddedPageComponent } from '../pages/tasks/task-playables/embedded-pa
import { InfoDisplayComponent } from '../pages/tasks/task-playables/info-display/info-display.component';
import { ProbabilisticLearningTaskComponent } from '../pages/tasks/task-playables/probabilistic-learning-task/probabilistic-learning-task.component';
import { IowaGamblingTaskComponent } from '../pages/tasks/task-playables/iowa-gambling-task/iowa-gambling-task.component';
import { InformationTaskComponent } from '../pages/tasks/task-playables/information-task/information-task.component';

export enum ComponentName {
// Generic components
Expand All @@ -42,6 +43,7 @@ export enum ComponentName {
FACE_NAME_ASSOCIATION_COMPONENT = 'FACENAMEASSOCIATIONCOMPONENT',
PLT_COMPONENT = 'PLTCOMPONENT',
IOWA_GAMBLING_COMPONENT = 'IOWAGAMBLINGCOMPONENT',
INFORMATION_TASK_COMPONENT = 'INFORMATIONTASKCOMPONENT',

// Special Components
EMBEDDED_PAGE_COMPONENT = 'EMBEDDEDPAGECOMPONENT',
Expand Down Expand Up @@ -72,6 +74,7 @@ const ComponentMap = {
[ComponentName.INFO_DISPLAY_COMPONENT]: InfoDisplayComponent,
[ComponentName.PLT_COMPONENT]: ProbabilisticLearningTaskComponent,
[ComponentName.IOWA_GAMBLING_COMPONENT]: IowaGamblingTaskComponent,
[ComponentName.INFORMATION_TASK_COMPONENT]: InformationTaskComponent,
};

@Injectable({
Expand Down
10 changes: 10 additions & 0 deletions src/app/services/data-generation/stimuli-models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,13 @@ export class IowaGamblingTaskStimulus {
moneyWon: number;
feePaid: number;
}

export class InformationTaskStimuliSet {
optimalScore: number;
cardValues: InformationTaskStimulus[];
}

export class InformationTaskStimulus {
cardValue: number;
expectedToExploit: boolean;
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a49c9ff

Please sign in to comment.