Skip to main content
Skip table of contents

Creating a touchscreen whac-a-mole with the TOUCHPixx

In this demo, we simulate a whack-a-mole game to demonstrate how to use the TOUCHPixx data.

To use the TOUCHPixx, a calibration must be done to convert from TOUCHPixx coordinates to screen coordinates. Since we are working on a simple 2-dimension space, two points (two corners) is enough to offer a linear mapping to the entire touch screen.

To start using the TOUCHPixx, we must set the digital inputs properly. This is done with Datapixx('EnableTouchpixx');. As the RESPONSEPixx, we must stabilize inputs on the TOUCHPixx Datapixx('SetTouchpixxStabilizeDuration', 0.01), where 0.01 means a press must last 10 ms before it is considered an input.

When there is no press, Datapixx('GetTouchpixxCoordinates'); returns zero for both x and y. This function returns an array which includes the x coordinate in the first position and y coordinate in the second. These are raw coordinates and must be calibrated. A linear mapping must be created with the two known coordinates by simply creating an equation . Since we took two points, we can find the unknown .

To log all touches, we start a logger Datapixx('SetTouchpixxLog'); and we set the mode to be continuous Datapixx('EnableTouchpixxLogContinuousMode'); so that we can sweep and still have a recorded input.

To get the coordinates, we first ask for a status to know if any input occurred: status = Datapixx('GetTouchpixxStatus'); and check the newLogFrames arguments of status. Once we know there are new frames, we can read the presses and timetags: [touches timetags] = Datapixx('ReadTouchpixxLog', status.newLogFrames);. Remember that these are raw coordinates and must be converted.

MATLAB
function TouchpixxWhacAMoleDemo()
% TouchpixxWhacAMoleDemo()
%
% Whack-A-Mole game using TOUCHPixx touch panel.
%
% History:
%
% Nov 23, 2012  paa     Written
% Nov  3, 2014  dml     Revised

AssertOpenGL;

try
    % We are assuming that the DATAPixx is connected to the highest number screen.
    % If it isn't, then assign screenNumber explicitly here.
    screenNumber=max(Screen('Screens'));

    % We use the imaging pipeline to open a window so that we can get microsecond accurate stimulus onset timetags
    PsychImaging('PrepareConfiguration');
    PsychImaging('AddTask', 'General', 'UseDataPixx');
    [w, wRect] = PsychImaging('OpenWindow', screenNumber, 0);
    winWidth = wRect(3) - wRect(1);
    winHeight = wRect(4) - wRect(2);

    % After OpenWindow so it's under the text generated by Screen
    fprintf('\nTOUCHPixx Whack-A-Mole Demo\n');

    % Configure DATAPixx/TOUCHPixx
    Datapixx('SetVideoMode', 0);                        % Normal passthrough
    Datapixx('EnableTouchpixx');                        % Turn on TOUCHPixx hardware driver
    Datapixx('SetTouchpixxStabilizeDuration', 0.01);    % Stabilize inputs for calibration
    Datapixx('RegWrRd');

    % Put up first touch calibration target near top-left corner, and acquire TOUCHPixx coordinates
    calDispX1 = 100;
    calDispY1 = 100;
    backCol = [128 128 128];
    Screen('FillRect', w, backCol, wRect);
    calCol = [255 255 255];
    Screen('FillRect', w, calCol, [calDispX1-50 calDispY1-50 calDispX1+50 calDispY1+50]);
    textCol = [0 0 0];
    Screen('TextFont',w, 'Courier New');
    Screen('TextSize',w, floor(50 * winWidth/1920));
    DrawFormattedText(w, 'Touch center of first calibration square', 'center', 'center', textCol);
    Screen('Flip', w);
    touchPt = [0 0];                        % Wait for press
    while touchPt == [0 0]
        Datapixx('RegWrRd');
        touchPt = Datapixx('GetTouchpixxCoordinates');
    end;
    calTouchX1 = touchPt(1);
    calTouchY1 = touchPt(2);
    Screen('FillRect', w, backCol, wRect);  % Erase calibration square
    Screen('Flip', w);
    isPressed = 1;                          % Wait until panel release
    while isPressed
        Datapixx('RegWrRd');
    	status =  Datapixx('GetTouchpixxStatus');
        isPressed = status.isPressed;
    end;

    % Do same for a second calibration target near bottom-right corner of display
    calDispX2 = winWidth - 100;
    calDispY2 = winHeight - 100;
    Screen('FillRect', w, backCol, wRect);
    Screen('FillRect', w, calCol, [calDispX2-50 calDispY2-50 calDispX2+50 calDispY2+50]);
    DrawFormattedText(w, 'Touch center of second calibration square', 'center', 'center', textCol);
    Screen('Flip', w);
    touchPt = [0 0];                        % Wait for press
    while touchPt == [0 0]
        Datapixx('RegWrRd');
        touchPt = Datapixx('GetTouchpixxCoordinates');
    end;
    calTouchX2 = touchPt(1);
    calTouchY2 = touchPt(2);
    Screen('FillRect', w, backCol, wRect);  % Erase calibration square
    Screen('Flip', w);
    isPressed = 1;                          % Wait until panel release
    while isPressed
        Datapixx('RegWrRd');
    	status =  Datapixx('GetTouchpixxStatus');
        isPressed = status.isPressed;
    end;

    % Calculate linear mapping between touch coordinates and display coordinates
    mx = (calDispX2 - calDispX1) / (calTouchX2 - calTouchX1);
    my = (calDispY2 - calDispY1) / (calTouchY2 - calTouchY1);
    bx = (calTouchX1 * calDispX2 - calTouchX2 * calDispX1) / (calTouchX1 - calTouchX2);
    by = (calTouchY1 * calDispY2 - calTouchY2 * calDispY1) / (calTouchY1 - calTouchY2);

    % Whacking instructions
    Screen('FillRect', w, backCol, wRect);
    DrawFormattedText(w, 'Whack moles when they appear\nTouch screen to start', 'center', 'center', textCol);
    Screen('Flip', w);
    isPressed = 0;                          % Wait until panel release
    while ~isPressed
        Datapixx('RegWrRd');
    	status =  Datapixx('GetTouchpixxStatus');
        isPressed = status.isPressed;
    end;
    while isPressed                         % Wait until panel release
        Datapixx('RegWrRd');
    	status =  Datapixx('GetTouchpixxStatus');
        isPressed = status.isPressed;
    end;
    
    % Loop for each mole to whack
    for i = 1:4
        % Wait for a random 1-2 second mole target onset
        status = Datapixx('GetVideoStatus');
        refreshRate = status.verticalFrequency;
        onsetDelay = floor((1 + rand(1)) * refreshRate);
        Screen('FillRect', w, backCol, wRect);
        for onsetFrame = 1:onsetDelay;
            Screen('Flip', w);
        end

        % Draw mole
        Screen('FillRect', w, backCol, wRect);
        moleX = floor(wRect(1) + winWidth/8 + winWidth * 0.75 * rand(1));
        moleY = floor(wRect(2) + winHeight/8 + winHeight * 0.75 * rand(1));
        moleSize = 150;
    	moleCol = [100 100 0];
        Screen('FillOval', w, moleCol, [moleX-moleSize/2 moleY-moleSize/2 moleX+moleSize/2 moleY+moleSize/2]);
        Screen('FillOval', w, [0 0 0], [moleX-moleSize/4 moleY-moleSize/4 moleX-moleSize/8 moleY-moleSize/8]);        
        Screen('FillOval', w, [0 0 0], [moleX+moleSize/8 moleY-moleSize/4 moleX+moleSize/4 moleY-moleSize/8]);        
        Screen('FrameArc', w, [0 0 0], [moleX-moleSize/3 moleY-moleSize/3 moleX+moleSize/3 moleY+moleSize/3], 135, 90, 20);        
        PsychDataPixx('LogOnsetTimestamps', 1); % Tells imaging pipeline to capture DATAPixx microsecond-accurate hardware stimulus onset timestamp
        Screen('Flip', w);
        moleTimetag = PsychDataPixx('GetLastOnsetTimestamp');

        % Start TOUCHPixx event logging
        Datapixx('SetTouchpixxLog');        % Configure TOUCHPixx logging with default buffer
        Datapixx('EnableTouchpixxLogContinuousMode');   % Continuous logging during a touch, so we recognize a sweep-a-mole
        Datapixx('StartTouchpixxLog');
        Datapixx('RegWrRd');
        
        % Wait around until mole gets whack'd
        whacked = 0;
        while ~whacked
        
            % Escape if key pressed
            if KbCheck
                break;
            end

            % How much TOUCHPixx data is available to read?
            Datapixx('RegWrRd');                    % Update registers for GetTouchpixxStatus
            status = Datapixx('GetTouchpixxStatus');
            if status.newLogFrames                  % We have new TOUCHPixx logged data to read?
                [touches timetags] = Datapixx('ReadTouchpixxLog', status.newLogFrames);
                for iTouch = 1:status.newLogFrames  % Examine each logged TOUCHPixx datum
                    touchX = touches(1,iTouch);
                    touchY = touches(2,iTouch);
                    if (touchX ~= 0 & touchY ~= 0)  % Confirm datum is not a panel release
                        
                        % Convert touch panel coordinates to pixel coordinates
                        whackX = mx * touchX + bx;
                        whackY = my * touchY + by;
                        
                        % Detect a winning whac
                        if (abs(whackX - moleX) < moleSize/2 & abs(whackY - moleY) < moleSize/2)
                            whacked = 1;
                            responseTime = timetags(iTouch) - moleTimetag;
                            disp(sprintf('Mole at (%d,%d) whacked in %d milliseconds', moleX, moleY, floor(responseTime*1000)));
                            break;
                        end
                    end
                end
            end
        end
         
        Datapixx('StopTouchpixxLog');
        Datapixx('RegWrRd');        
    end

    Datapixx('DisableTouchpixx');    % Turn off TOUCHPixx hardware driver
    Datapixx('RegWrRd');
    Screen('CloseAll');
    ShowCursor;
    return;
catch
    Screen('CloseAll');
    ShowCursor;
    psychrethrow(psychlasterror);
end
JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.