Skip to content

Commit

Permalink
🚧 matlab: minimal choice+isi+feedback+iti
Browse files Browse the repository at this point in the history
  • Loading branch information
WillForan committed Jul 22, 2024
1 parent 187435b commit 86f8691
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 26 deletions.
11 changes: 8 additions & 3 deletions habit.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,20 @@
system = load_system(varargin{:});
timing = load_events(varargin{:});
system.w = setup_screen(varargin{:});
system.pos = setup_pos(system.w, varargin{:});
system.tex = load_textures(system.w, varargin{:});

%%
%% instructions
[onset, output] = instructions(system, 1);

%% start timing and data collection
record(length(timing)) = struct();
system.starttime = GetSecs();

%% run through events
for i=1:length(timing)
t = timing(i);
[onset, output] = t.func(system, t);
[onset, output] = t.func(system, t, record);
record(i).event_name = t.event_name;
record(i).output = output;
record(i).onset = onset;
Expand All @@ -25,6 +30,6 @@
info.record = record;
info.system = system;

Screen('CloseAll');
closedown();
end

33 changes: 33 additions & 0 deletions private/choice.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function [onset, output] = choice(system, t, varargin)

ideal = GetSecs()+t.onset;
Screen('DrawTexture', system.w, system.tex.ocean_bottom);
Screen('DrawTexture', system.w, system.tex.astronaut{1,1});

%% positon choice options
chest_w = 27;chest_h = 27; %TODO: use sprite
% TODO: use DrawTextures (many at once)
Screen('DrawTexture', system.w, system.tex.chest,...
[], [ system.pos.left.x system.pos.left.y system.pos.left.x+chest_w system.pos.left.y+chest_h] );
Screen('DrawTexture', system.w, system.tex.chest,...
[], [ system.pos.up.x system.pos.up.y system.pos.up.x+chest_w system.pos.up.y+chest_h] );
Screen('DrawTexture', system.w, system.tex.chest,...
[], [ system.pos.right.x system.pos.right.y system.pos.right.x+chest_w system.pos.right.y+chest_h] );

onset = Screen('Flip', system.w, ideal);

keys = KbName({'Left','Up', 'Right'});
[k rt] = waitForKeys(keys, onset + t.max_rt);
if rt > 0
idx = find(keys == k,1);
well_prob = t.chance(idx);
output.score = (rand(1) <= well_prob);
else
output.score = 0
end
oupput.onset_ideal = ideal;
output.key = k;
output.rt = rt;

% TODO: animate avatar walk in while loop?
end
13 changes: 13 additions & 0 deletions private/closedown.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function closedown()
% CLOSEDOWN - clean up PTB connections
% originally from https://github.com/LabNeuroCogDevel/froggerRPG/blob/master/private/closedown.m
ListenChar(0);
ShowCursor;
Screen('CloseAll');
Priority(0);
sca;
diary off;
close all;
%openPTBSnd('close');
% TODO: close DAQ?
end
6 changes: 6 additions & 0 deletions private/escclose.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
function escclose(keyCode)
if keyCode(KbName('escape'))
closedown()
error('early exit')
end
end
21 changes: 21 additions & 0 deletions private/feedback.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
function [onset, output] = feedback(system, t, record)
ideal = t.onset + GetSecs(); % relative time

Screen('DrawTexture', system.w, system.tex.ocean_bottom);
% reach back in time and find the previous choice event
choice = record(t.i-2).output,
if choice.score
msg = 'REWARD!'
else
msg = 'NO REWARD!'
end

% TODO: find chosen direction using choice.key
% TODO: show open/closed chest over choice location
% TODO: animate chest sprite
DrawFormattedText(system.w, msg ,...
'center','center', [255,255,255]);

onset = Screen('Flip', system.w, ideal);
output.ideal = ideal;
end
11 changes: 6 additions & 5 deletions private/fixation.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
function [onset, output] = fixation(system, t)
function [onset, output] = fixation(system, t, varargin)

% show the background
Screen('DrawTexture', system.w, system.tex.ocean_bottom);
DrawFormattedText(system.w, '+' ,'center','center', t.cross_color);

% run
onset = t.onset + system.starttime;
onset = Screen('Flip', system.w, onset);
output = [];
%onset = t.onset + system.starttime; % abs time
ideal = t.onset + GetSecs(); % relative time
onset = Screen('Flip', system.w, ideal);
output.onset_ideal = ideal;
output.onset = onset;
end
11 changes: 7 additions & 4 deletions private/instructions.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
function [onset, output] = instructions(system, number, t)
function [onset, output] = instructions(system, number, varargin)
output.instruction = number;
output.msg = 'example output';

% show the background
Screen('DrawTexture', system.w, system.tex.ocean_bottom);
Screen('DrawTexture', system.w, system.tex.chest);
%Screen('DrawTexture', system.w, system.tex.chest);
DrawFormattedText(system.w, 'Push any key to start' ,...
'center','center', [255,255,255]);

% run
onset = Screen('Flip', system.w, t.onset);
onset = Screen('Flip', system.w, 0);
acceptkeys = KbName({'Space','Left','Up','Right','Return','1'});
[output.k output.rt] = waitForKeys(acceptkeys,Inf);
end
39 changes: 28 additions & 11 deletions private/load_events.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,34 @@

% TODO: programaticly set
% heavy lifting done by .func
timing(1).event_name = 'instruct1';
timing(1).func = @(system, varargin) instructions(system, 1, varargin{:});
timing(1).onset = 0;
i = 1;
timing(i).event_name = 'choice';
timing(i).func = @choice;
timing(i).chance = [1,1,1]; % left, up, right
timing(i).max_rt = 2;
timing(i).i = i;

timing(2).event_name = 'iti';
timing(2).onset = 5;
timing(2).cross_color = [255,255,255]; % white
timing(2).func = @fixation;
i=i+1;
timing(i).event_name = 'isi';
timing(i).dur = 2;
timing(i).cross_color = [0,0,255];%blue
timing(i).func = @fixation;
timing(i).onset=0; % as soon as choice ends
timing(i).i = i;

i=i+1;
timing(i).event_name = 'feedback';
timing(i).dur = 2;
timing(i).func = @feedback;
timing(i).onset=timing(i-1).dur
timing(i).i = i;

i=i+1;
timing(i).event_name = 'iti';
timing(i).dur = 3;
timing(i).cross_color = [255,255,255]; % white
timing(i).func = @fixation;
timing(i).onset=timing(i-1).dur
timing(i).i = i;

timing(3).event_name = 'isi';
timing(3).onset = 10;
timing(3).cross_color = [0,0,255];%blue
timing(3).func = @fixation;
end
25 changes: 22 additions & 3 deletions private/load_textures.m
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,31 @@
fprintf('# loading textures\n');
imgs = [dir('out/imgs/*.png')];

avatar_sprites = {'astronaut', 'shark','lizard_blue', 'alient_green'};
well_sprites = {'chest_sprites', 'wellcoin_sprites'};
for f = imgs'
[~, fname, ~] = fileparts(f.name);
[~, fname, ~] = fileparts(f.name); fname(fname=='-')='_';
[img, ~, alpha] = imread(fullfile(f.folder,f.name));
img(:,:,4) = alpha;
% TODO: adjust images to screen size
tex_struct.(fname) = Screen('MakeTexture',w, img);
% TODO: scale images to screen size

% sprites within one file. have animations frames in a grid
if ismember(fname, avatar_sprites)
% 4 rows 3 cols
sprites = cell(4,4);
[x,y,_] =size(img);
h= floor(y/4);
wd = floor(x/4);
for i=1:4; for j=1:4;
ii=(i-1)*h+1; jj=(j-1)*h+1;
img_sub = img(jj:(jj+wd-1),ii:(ii+h-1),:);
tmp_tex = Screen('MakeTexture',w, img_sub);
sprites(i,j) = tmp_tex;
end;end
tex_struct.(fname) = sprites;
else
tex_struct.(fname) = Screen('MakeTexture',w, img);
end
end

end
10 changes: 10 additions & 0 deletions private/setup_pos.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
function pos = setup_pos(w, varargin);
% TODO: use screen size?
pos.left.x=10;
pos.left.y=400;

pos.up.x=200;
pos.up.y=200;

pos.right.x=400;
pos.right.y=400;
43 changes: 43 additions & 0 deletions private/waitForKeys.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
function [key,RT] = waitForKey(acceptkeysidx,timeout)
% WAITFORKEY -- wait for given key index (e.g from KbName()) until timeout
% return key=0 and RT=-Inf if no acceptable key push given before timeout
% originally from https://github.com/LabNeuroCogDevel/audodd/blob/master/waitForKey.m
%
% Usage:
% % 2 seconds to respond a (return k=1) or b (return k=2)
% % no response, k=0, rt=-Inf
% [k rt] = waitForKey( KbName({'a','b'}), GetSecs() + 2);
%
% % wait forever for the scanner to send a trigger (trg key is = )
% [k rt] = waitForKey( KbName({'=+'}), Inf);
%
% Note:
% accepts multiple keys down as response endorsing first acceptable key


% no response can be meaningful if too fast
% wait 60ms before trying to record a response
% TODO: determine more approprate min response time limit
% also reduce hold over from previous trial errors
fastestResponseTime=.06;
WaitSecs(fastestResponseTime);

key=0;
RT=-Inf;
% continue until we get a keypress we like
% or we run out of time
while(RT<=0 && GetSecs() < timeout )
[keyPressed, thisRT, keyCode] = KbCheck;
if keyPressed && any( keyCode(acceptkeysidx) )
RT=thisRT;

%NB! we only pick the first (in order of acceptkeys)
% if subject is holding down more than one key, will not record both!
% will warn to screen about mutliple pushes though
key=find(keyCode(acceptkeysidx) ,1);
if(nnz(keyCode(acceptkeysidx) ) > 1)
fprintf('WARNING: multple good button pushes! reporting first button is pushed\n');
end
end
end
end

0 comments on commit 86f8691

Please sign in to comment.