Skip to content

Commit

Permalink
Speech bubble advice
Browse files Browse the repository at this point in the history
* Advisors can now give advice in the form of speech bubbles
* Added adviceFormat flags for voice and speech bubble
* adviceFormat flags respected in doDotTask and doQuizTask
  • Loading branch information
mjaquiery committed Feb 23, 2018
1 parent fd238f6 commit e9712c2
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 59 deletions.
4 changes: 3 additions & 1 deletion HaloEffect/build_trials.m
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@
end
%-- add breaks and instruction points
if trials(t-1).block ~= trials(t).block % first trial in a new block
trials(t).break = true;
if trials(t).block ~= 1 % no break after the intro; it's unnecessary
trials(t).break = true;
end
if ismember(trials(t).block, cfg.instructionBlocks)
trials(t).instr = ['block' int2str(trials(t).block)];
end
Expand Down
37 changes: 20 additions & 17 deletions HaloEffect/screenTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@
trials(2).dotdifference = cfg.stim.initialDotDifference;

%% Begin testing elements
cfg.currentTrialNumber = starttrial;
cfg.currentTrial = trials(starttrial);
t = 1;
cfg.currentTrialNumber = starttrial;
cfg.currentTrial = trials(starttrial);
t = 1;

% add all static elements
%draw_static(Sc, cfg)
Expand All @@ -89,24 +89,27 @@

%showAdvisorPolitics(1);

% for t = 1:5
for t = 1:2
trials(t).advisorId = 3;
% trials(t).taskType = cfg.taskType.quiz;
% trials = doQuizTask(trials, t);
% end
%
% quizFeedback(trials,1,5);

defineQuiz();
for t = 1:106
if t > length(trials)
trials = [trials trials(t-1)];
end
trials(t).taskType = cfg.taskType.quiz;
trials(t).question = cfg.quiz{cfg.quizOrder(t)};
trials(t).cor = rand() < .7;
trials(t).taskType = cfg.taskType.dots;
trials = doDotTask(trials, t);
end

quizFeedback(trials);
%quizFeedback(trials,1,5);

% defineQuiz();
% for t = 1:106
% if t > length(trials)
% trials = [trials trials(t-1)];
% end
% trials(t).taskType = cfg.taskType.quiz;
% trials(t).question = cfg.quiz{cfg.quizOrder(t)};
% trials(t).cor = rand() < .7;
% end

% quizFeedback(trials);

%% close PTB
Screen('CloseAll');
Expand Down
3 changes: 3 additions & 0 deletions HaloEffect/set_cfg_settings.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
cfg.taskType.dots = 1;
cfg.taskType.quiz = 2;

cfg.quizOptions.adviceFormat = cfg.adviceFormat.speechBubble;
cfg.dotTask.adviceFormat = cfg.adviceFormat.speechBubble;

%% initialize variables
% define stimulus related variables
cfg.stim.durstim = .160; % stimulus duration
Expand Down
34 changes: 30 additions & 4 deletions core/confidenceSlider/display_response_.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
function [ft] = display_response_(temp,varargin)
function [ft] = display_response_(temp, cj1, trial)
% Usage:
% [ft] = display_response(temp[, cj1])
% [ft] = display_response(temp[, cj1[, trial]])
%
% Inputs:
% temp: vector containing haschanged boolean and current confidence
% judgement
% cj1: first confidece judgement if one is available
% trial: if present, uses information in trial to draw advice overlay
%
% Outputs:
% ft - the time the screen flip occurred
Expand All @@ -17,8 +18,30 @@
show_cj1 = false;
else
show_cj1 = true;
cj1 = varargin{1};
int1 = sign(cj1);
if nargin < 3
trial = [];
else
% advice string
if trial.obsacc == 1 % advisor correct
if trial.wherelarger == 1 % target on left
adviceString = 'on the LEFT.';
else
adviceString = 'on the RIGHT.';
end
else % advisor incorrect
if trial.wherelarger == 1 % target on left
adviceString = 'on the RIGHT.';
else
adviceString = 'on the LEFT.';
end
end
adviceString = ['I think there were more dots ' adviceString];
% arguments for speech bubble drawing
x = Sc.center(1);
y = 300;
bubbleArgs = struct('offset', [x y], 'bubbleFrameColor', cfg.display.dotTask.adviceBubbleColor);
end
end
gs = round(cfg.bar.gap_size/2);
[haschanged,cj] = deal(temp(1),temp(2));
Expand All @@ -38,9 +61,12 @@
cfg.bar.barrect(3)-cfg.bar.cursorwidth.*.5,cfg.bar.maxScale);
end
cj1rect = CenterRectOnPoint([0,0,cfg.bar.cursorwidth,cfg.bar.cursorheight],...
positions(abs(cj1)), ...
positions(abs(cj1)), ...
Sc.rect(4).*cfg.bar.positiony);
Screen('FillRect', Sc.window, cfg.bar.color.cj1, cj1rect );
if ~isempty(trial)
drawAdvisorBubble(trial.advisorId, adviceString, bubbleArgs);
end
end

% define response cursor position
Expand Down
24 changes: 16 additions & 8 deletions core/confidenceSlider/drag_slider.m
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
function [cj,resp_t,interval,hasconfirmed] = drag_slider(cj1)
function [cj,resp_t,interval,hasconfirmed] = drag_slider(cj1, trial)
% Usage:
% [cj resp_t interval hasconfirmed] = drag_slider([cj1])
% [cj resp_t interval hasconfirmed] = drag_slider([cj1, [trial]])
% Inputs:
% cj1: first confidence judgement. If present cj1 is shown in shaded color
% trial: if included, data from trial can be used to draw an advice overlay
%
% Outputs:
% cj: current value of the slider position
Expand All @@ -15,12 +16,11 @@
global cfg; % configuration object
global Sc; % Screen object

if nargin < 1
cj1 = [];
end
if nargin < 2, trial = []; end
if nargin < 1, cj1 = []; end

%% initialize variables
resp = 0; buttons=[]; haschanged=false; hasconfirmed=false;int=0;
resp = 0; buttons=[]; haschanged=false; hasconfirmed=false; int=0;

%% display cursor
if isempty(cj1)
Expand All @@ -30,7 +30,11 @@
else
% Show mouse pointer
ShowCursor('Arrow');
ft = display_response_([haschanged,resp+int],cj1);
if isempty(trial)
ft = display_response_([haschanged,resp],cj1);
else
ft = display_response_([haschanged, resp], cj1, trial);
end
end

%% collect response
Expand Down Expand Up @@ -58,7 +62,11 @@
if isempty(cj1)
ft = display_response_([haschanged,resp]);
else
ft = display_response_([haschanged,resp],cj1);
if isempty(trial)
ft = display_response_([haschanged,resp],cj1);
else
ft = display_response_([haschanged, resp], cj1, trial);
end
end
end

Expand Down
6 changes: 6 additions & 0 deletions core/defineQuiz.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ function defineQuiz()
global cfg; % configuration object
global Sc; % screen object

%% Predefined settings (don't override)
if ~isfield(cfg, 'quizOptions') || ~ isfield(cfg.quizOptions, 'adviceFormat')
cfg.quizOptions.adviceFormat = cfg.adviceFormat.voice;
end

%% Load quiz questions
cfg.quiz = xml2struct(xmlread([cfg.path.stims cfg.path.slash 'generalKnowledgeQuestions.xml']));
cfg.quiz = cfg.quiz.quiz.question;
Expand All @@ -25,6 +30,7 @@ function defineQuiz()
cfg.display.quiz.qPosY = Sc.size(2) * .2;
cfg.display.quiz.aPosY = Sc.size(2) * .35;
cfg.display.quiz.aOffsetX = Sc.size(1) * .25;
cfg.display.quiz.adviceBubbleColor = [0 0 0];
cfg.display.quiz.feedback.textSize = 18;
cfg.display.quiz.feedback.lineSpacing = 1.2;
cfg.display.quiz.feedback.questionsPerPage = 53;
Expand Down
31 changes: 22 additions & 9 deletions core/doDotTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
global cfg; % configuration object
global Sc; % screen object

%% At some point move this stuff to a 'defineDotTask' function
if ~isfield(cfg, 'dotTask') || ~isfield(cfg.dotTask, 'adviceFormat')
cfg.dotTask.adviceFormat = cfg.adviceFormat.voice;
end
if ~isfield(cfg.display, 'dotTask') || ~isfield(cfg.display.dotTask, 'adviceBubbleColor')
cfg.display.dotTask.adviceBubbleColor = [0 0 0];
end

% define time: if first trial of block getTime; if normal trial, time =
% end of previous trial
if t == 1 || cfg.restarted == 1 || trials(t-1).break == 1
Expand Down Expand Up @@ -100,13 +108,13 @@
%% define advisor behaviour
% NOTE: advisor behaviour depends on initial judgements, not
% post-advice judgements
if ~isnan(trials(t).advisorId)
if hasAdvice(trials(t))
% advisors are 60/70/80 or 80/70/60 agreement in correct (based on
% confidence) and 30% agreement when participant is incorrect
if trials(t).cor1 == 1
toi = [trials(1:t).cor1] == 1 & ... % use last 2 blocks for reference dsitribution
([trials(1:t).block] == trials(t).block-1 | [trials(1:t).block] == trials(t).block-2);
if ~isNaN(trials(t).overrideAdviceType)
if ~isnan(trials(t).overrideAdviceType)
adviceType = trials(t).overrideAdviceType;
else
adviceType = cfg.advisor(trials(t).advisorId).adviceType;
Expand Down Expand Up @@ -135,14 +143,19 @@

%% Advice and final decision
if hasAdvice(trials(t))
load_observer_audio;
present_advice;

trials(t).responsetime2 = GetSecs;
if bitand(cfg.dotTask.adviceFormat, cfg.adviceFormat.voice)
load_observer_audio;
present_advice;
end
trials(t).time_responseStart2 = GetSecs;
% prompt new confidence judgment
[trials(t).cj2, trials(t).time_response2, trials(t).int2] = ...
drag_slider(trials(t).cj1);

if bitand(cfg.dotTask.adviceFormat, cfg.adviceFormat.speechBubble)
[trials(t).cj2, trials(t).time_response2, trials(t).int2] = ...
drag_slider(trials(t).cj1, trials(t));
else
[trials(t).cj2, trials(t).time_response2, trials(t).int2] = ...
drag_slider(trials(t).cj1, trials(t));
end
% define new timestamp
time = trials(t).time_response2;

Expand Down
54 changes: 41 additions & 13 deletions core/doQuizTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@
else
time = trials(t-1).time_endTrial;
end

trials(t).question = cfg.quiz{cfg.quizOrder(t)};

cfg.taskManager.quiz.trialNumber = cfg.taskManager.quiz.trialNumber + 1;
trials(t).question = cfg.quiz{cfg.quizOrder(cfg.taskManager.quiz.trialNumber)};
if trials(t).wherelarger == 1
% correct answer on the left
L.text = trials(t).question.answer;
Expand Down Expand Up @@ -107,7 +108,7 @@

HideCursor();

if trials(t).advisorId == 0
if trials(t).advisorId == 0 || cfg.quizOptions.adviceFormat == cfg.adviceFormat.none
% tidy up
Screen('TextSize', Sc.window, oldTextSize);
PsychPortAudio('Close');
Expand Down Expand Up @@ -141,7 +142,7 @@
if trials(t).cor1 == 1
toi = [trials(1:t).cor1] == 1 & ... % use last 2 blocks for reference dsitribution
([trials(1:t).block] == trials(t).block-1 | [trials(1:t).block] == trials(t).block-2);
if ~isNaN(trials(t).overrideAdviceType)
if ~isnan(trials(t).overrideAdviceType)
adviceType = trials(t).overrideAdviceType;
else
adviceType = cfg.advisor(trials(t).advisorId).adviceType;
Expand All @@ -165,24 +166,26 @@
trials(t).step = NaN;
end

load_observer_audio;
WaitSecs(cfg.stim.quiz.RSI2);

% masks for present_advice and prevent_delay
mask1 = [1 0 1 2 0];
mask2 = mask1;

if ~isnan(trials(t).advisorId)
present_advice;
else
if bitand(cfg.quizOptions.adviceFormat, cfg.adviceFormat.voice)
load_observer_audio;
WaitSecs(cfg.stim.quiz.RSI2);
% advice presentation
if ~isnan(trials(t).advisorId)
present_advice;
end
end
if isnan(trials(t).advisorId)
present_delay;
end

ShowCursor('Arrow');

%% Get a second answer
if hasAdvice(trials(t))
lastResponse = NaN;
trials(t).time_responseStart2 = GetSecs();
while true
% acquire response
[x, ~, buttons] = GetMouseWrapper();
Expand Down Expand Up @@ -213,13 +216,16 @@
if response ~= lastResponse
% draw the screen
drawQuiz(Q, L, R);
if bitand(cfg.quizOptions.adviceFormat, cfg.adviceFormat.speechBubble)
drawQuizAdviceOverlay(trials(t));
end
draw_static([1 0 1 2 1]);
if ~isnan(response)
drawQuizMarkers(firstResponse, cfg.bar.color.cj1, response, cfg.bar.color.cursor);
Screen('Flip', Sc.window);
else
drawQuizMarkers(firstResponse, cfg.bar.color.cj1);
[~, trials(t).time_responseStart1] = Screen('Flip', Sc.window);
Screen('Flip', Sc.window);
end
lastResponse = response;
end
Expand Down Expand Up @@ -269,6 +275,28 @@ function drawQuiz(Q, L, R)
end
end

function drawQuizAdviceOverlay(trial)
%% Draw an overlay of a speech bubble with the advisor's portrait
% Matt Jaquiery, Feb 2018
%
% usage: drawQuizAdviceOverlay(trial)
%
% Inputs:
% trial: current trial object
global cfg;
global Sc;
% fetch the option endorsed by the advisor
if trial.obsacc == 1
adviceString = trial.question.answer;
else
adviceString = trial.question.distractor;
end

adviceString = ['I think the answer is ' adviceString '.'];
x = Sc.center(1);
y = 300;
args = struct('offset', [x y], 'bubbleFrameColor', cfg.display.quiz.adviceBubbleColor);
drawAdvisorBubble(trial.advisorId, adviceString, args);

function response = getQuizResponse(mouse_x)
%% Return the scale value designated by the current mouse position
Expand Down
Loading

0 comments on commit e9712c2

Please sign in to comment.