Jeremy

Forum Replies Created

Viewing 15 posts - 1,321 through 1,335 (of 1,522 total)
  • Author
    Posts
  • in reply to: Fillers that are consistent across different groups #5208
    Jeremy
    Keymaster

    Some errors are not fatal: as long as you know what triggers them (eg. the intentional absence of a Group column in the filler table) but you are still able to run the experiment and collect the data you need, it’s ok to ignore them (your participants won’t see them once you’re ready for data collection and use DebugOff()). Just make sure you don’t overlook other errors when skipping them over.

    Jeremy

    in reply to: Logging the participant ID #5205
    Jeremy
    Keymaster

    Hello,

    You are passing three arguments to the trial log command, when it should take only two: the first argument is the name of the column to be added, the second argument is the value of that column on each trial row in the results file.

    If you simply get the participant’s id by looking up the URL (as opposed to, say, asking them to type it in a field) then you don’t need to use a Var element, just use:

    .log( "ParticipantID" , GetURLParameter("id") )

    Jeremy

    in reply to: Fillers that are consistent across different groups #5204
    Jeremy
    Keymaster

    Hi,

    The solution should indeed be the same—as you saw on the other topic, the table used for the filler items does not have a group column and yet it just works. You just want to make sure you use two Template commands to use the two different tables, as illustrated on the other topic.

    Jeremy

    in reply to: Mixing fillers and items #5199
    Jeremy
    Keymaster

    Hi Angelica,

    You can always use regular javascript alongside PennController commands. So you can do this:

    customTrial = label => variable => newTrial( label ,
        defaultText
            .center()
        ,
        // Display target word 
        newText("listen_for", "Listen for:")
            .css({"width": "300px"})
            .italic()
        ,
        newText("target_word", variable.target)
            .css({"font-size": "200%", "width": "300px"})        
        ,
        newText("next", "Press the spacebar to continue.")
            .css({"width": "300px"})
            .italic()
        ,
        newCanvas(300, 300)
            .add(0, 100, getText("listen_for"))
            .add(0, 150, getText("target_word"))
            .add(0, 250, getText("next"))
            .print()
        ,
        newKey(" ")
            .wait()
        ,
        clear()
        ,
        // Play audio stimulus and record reaction time
        newImage("fixation_cross", "fixation_cross.png")
            .size(300, 300)
        ,
        newCanvas(300, 300)
            .add(0, 0, getImage("fixation_cross"))
            .print()
        ,
        newKey("reaction_time", " ")                   
            .log("all")                                 
        ,
        newAudio("stimulus", variable.stimulus)
            .play()
            .wait() 
        ,
    	clear()
        ,
        // Comprehension question
        (variable.question?[newText("question", variable.question)
            .cssContainer({"width": "600px", "font-size": "150%", "height": "50px"})
        ,
        newText("answer_1", variable.answer_1)
            .cssContainer({"width": "300px"})
        ,
        newText("answer_2", variable.answer_2)
            .cssContainer({"width": "300px"})
        ,
        newCanvas(600, 300)
            .add(0, 150, getText("question"))
            .add(0, 200, getText("answer_1"))
            .add(300, 200, getText("answer_2"))
            .print()
        ,
        newKey("FJ")
            .wait()
            .log()
        ]:null)
    )
    .log("group",       variable.group)
    .log("item",        variable.item)
    .log("condition",   variable.cond2)
    .log("target",      variable.target)
    .log("correct",    	variable.correct)

    Then you can use it like this:

    // experimental item trial 
    Template("test_items", customTrial("items") )
    
    // Filler item trial 
    Template("test_fillers", customTrial("fillers") )

    Jeremy

    in reply to: Manipulating text from CSV stimuli #5195
    Jeremy
    Keymaster

    Great!

    If anyone else is having the same issue, one solution is to inject some HTML in your sentence, for example: Please identify the last <span style='color: red; font-weight: bold;'>sound<span>.

    Jeremy

    in reply to: Filled TextInput #5194
    Jeremy
    Keymaster

    Hello,

    Yes, you can use .test.text to check the content of a TextInput element, and doing so in the wait command will only validate the click if the test is successful. In your case, you can use regular expressions to check for appropriate numbers, just replace your very last .wait() line with this:

    .wait(
        getTextInput("age").test.text( /^(\d|1[0-8])$/ )
            .failure( newText("Age should be a number between 0 and 18").print() )
        .and( getTextInput("weight").test.text( /^\d+$/ )
            .failure( newText("Weight should be a numeric value").print() )
        ).and( getTextInput("height").test.text( /^\d+$/ )
            .failure( newText("Height should be a numeric value").print() )
        )
    )

    Jeremy

    in reply to: Manipulating a PennController table? #5193
    Jeremy
    Keymaster

    Hi Mieke,

    You could test __counter_value_from_server__ in your Template command and assign labels accordingly: Template( row => newTrial( __counter_value_from_server__%2?"secondblock":"firstblock" , /* ... */ ) )

    Note: __counter_value_from_server__ is only defined after your script has been evaluated, so you cannot use it outside Template (whose execution is delayed to fetch the tables)

    Jeremy

    in reply to: Manipulating a PennController table? #5186
    Jeremy
    Keymaster

    Hi Mieke,

    Yes, you can definitely accomplish what you want with PennController, but I think you’d need to take a slightly different approach. Let me give you an example, starting with a table of this format (of course you can use a CSV file directly instead of using AddTable):

    AddTable("myTable", `Item,Sentence,Group
    0,tree lost its leaves,possessive
    0,tree fell on itself,reflexive
    1,pond had its water drained,possessive
    1,pond replenished itself,reflexive
    2,cloud kept its shape,possessive
    2,cloud stood by itself,reflexive
    3,rock had its top smoothed,possessive
    3,rock spun on itself,reflexive
    4,hill hid a cave at its top,possessive
    4,hill hid on itself,reflexive`)

    Here I have 5 trials, numbered from 0 to 4, which appear in one of two conditions (possessive vs reflexive) every other time the experiment is run, because of the Group column. Nothing crucial relies on this, it’s just for the sake of illustration.

    Now I’ll define some variables in the script to configure the design you describe:

    firstBlockLength = 2
    secondBlockLength = 3
    primes = "EU"
    firstBlockQuantifiers = "EU"
    secondBlockQuantifiers = "EUN"

    I’m saying: there will be 2 trials in the first block and 3 trials in the second (you would replace those numbers with 30 and 24 instead). I’m also saying: each prime (regardless of block) will be either ‘E’ (for existential) or ‘U’ (for universal), and each in-sentence quantifier in the first block will be either ‘E’ (to be replaced with the determiner “A”) or ‘U’ (to be replaced with “Every”) and each in-sentence quantifier in the second block will be ‘E,’ ‘U’ or ‘N’ (to be replaced with “No”).

    Note: the numbers in my example are not ideal, since the second block has 3 trials over which you cannot properly distribute 2 prime types—just make sure the number of trials in each block is a multiple of the number of prime types and the numbers of quantifiers in both trials; 24 and 30 both being multiples of both 2 and 3, you should be fine

    Now here’s some javascript magic to automatically associate your trials with the desired conditions:

    // Generates a random, even array of LENGTH characters picked from a string
    randomArray = (length,list) => list.split('').map(v=>v.repeat(parseInt(length/list.length)))
                            .flat(1).sort(v=>Math.random()>=0.5)
    
    listOfPrimesFirst = randomArray(firstBlockLength,primes)
    listOfPrimesSecond = randomArray(secondBlockLength,primes)
    listOfQuantifiersFirst = randomArray(firstBlockLength,firstBlockQuantifiers)
    listOfQuantifiersSecond = randomArray(secondBlockLength,secondBlockQuantifiers)
    
    translator = {E: "One", U: "Every", N: "No"}
    // Randomly order the indices first
    indices = [...new Array(5)].map((v,i)=>i).sort(v=>Math.random()>=0.5)
    // Now assign properties to trials 
    trials = []
    listOfQuantifiersFirst.map( (v,i) => trials[indices[i]] = {
        block: "first",
        prime: translator[listOfPrimesFirst[i]], 
        quantifier: translator[v]
    } )
    listOfQuantifiersSecond.map( (v,i) => trials[indices[firstBlockLength+i]] = {
        block: "second", 
        prime: translator[listOfPrimesSecond[i]], 
        quantifier: translator[v]
    } )

    Once this is in place, you can use Template and GetTable().filter to generate trials:

    Template(
        // Only use the rows where Item appears in the indices of the first block
        GetTable("myTable").filter( row=>indices.slice(0,firstBlockLength).indexOf(Number(row.Item))>=0 ) 
        ,
        row => newTrial( 'firstblock' ,
            newText("space","Please press Space").print(),
            newKey(" ").wait(),
            getText("space").remove()
            ,
            newText( "prime" , trials[row.Item].prime ).log().print(),
            newTimer(20).start().log().wait(),  // 20ms prime
            getText("prime").remove()
            ,
            newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence})
                .print().log().wait().remove()
        )
        .log( "Group" , row.Group )
        .log( "Block" , trials[row.Item].block )
        .log( "Sentence" , row.Sentence )
        .log( "Prime" , trials[row.Item].prime )
        .log( "Quantifier" , trials[row.Item].quantifier )
    )
    
    
    Template(
        // Only use the rows where Item appears in the indices of the second block
        GetTable("myTable").filter( row=>indices.slice(firstBlockLength,).indexOf(Number(row.Item))>=0 ) 
        ,
        row => newTrial( 'secondblock' ,
            newButton("Please click here").print().wait().remove()
            ,
            newText( "prime" , trials[row.Item].prime ).log().print(),
            newTimer(10).start().log().wait(),  // 10ms prime (more-or-less, hardware constraints)
            getText("prime").remove()
            ,
            newController("DashedSentence", {s: trials[row.Item].quantifier+' '+row.Sentence})
                .print().log().wait().remove()
        )
        .log( "Group" , row.Group )
        .log( "Block" , trials[row.Item].block )
        .log( "Sentence" , row.Sentence )
        .log( "Prime" , trials[row.Item].prime )
        .log( "Quantifier" , trials[row.Item].quantifier )
    )

    Let me know if you have questions about adapting this to your case

    Jeremy

    in reply to: conditional trials #5183
    Jeremy
    Keymaster

    Hello,

    You could do something like this:

    Template( "myTable.csv" ,
         row => newTrial( "experiment" ,        
            newController("DashedSentence", {s: row.Sentence}).print().log().wait()
                .remove()
            ,      
            (row.Question?[
              newText("Question", row.Question).print()             
              ,
              newKey("FJ")
                .wait()
                .log()       
            ]:null)
            ,
            [etc]

    Jeremy

    in reply to: Multi Character Key Press #5179
    Jeremy
    Keymaster

    Hi Sam,

    EDIT: actually you don’t have to use the Function element as I suggest below, but you’ll still need a little bit of javascript when setting and testing your Var element:

    Template( "fulldesign.csv" , variable =>
      newTrial( "task" ,
        newVar("fullstring", "")
        ,
        newText("Please type your guess").print()
        ,
        newKey("guess", "")
            .callback( 
                getVar("fullstring").set( v=>v+getKey("guess").value )
            )
        ,
        newKey("Enter").wait()
        ,
        getVar("fullstring").test.is( v=>v.match(new RegExp(variable.answer+"$","i")) )
            .success( newText("Correct!").print() )
            .failure( newText("Incorrect").print() )
        ,
        newButton("next").print().wait()
      )
    )

    End of edit

    I think you’ll have to inject some javascript in there, using the Function element:

    Template( "fulldesign.csv" , variable =>
      newTrial( "task" ,
        newText("Please type your guess").print()
        ,
        newFunction(function(){
            this.fullstring = "";
            let oldOnkeydown = document.onkeydown;
            document.onkeydown = e=>{
                if (oldOnkeydown instanceof Function) oldOnkeydown.call(document, e);
                this.fullstring += e.key;
            };
        }).call()
        ,
        newKey("Enter").wait()
        ,
        newFunction(function(){
            return this.fullstring.match(new RegExp(variable.answer+"$","i"))!=null; 
        }).test.is( true )
            .success( newText("Correct!").print() )
            .failure( newText("Incorrect").print() )
        ,
        newButton("next").print().wait()
      )
    )

    Two comments on your code:

    • The setVar command (as all PennController commands) is executed immediately when the script encounters it, so unless it is in a callback command, it will only be executed once. In your case, you call it immediately after creating the Key element, so your Var element will remain empty.
    • The after command adds an element to the right of another element when printed on the page, but it does not change the value of either element

    Jeremy

    • This reply was modified 5 years, 2 months ago by Jeremy. Reason: Added a Function-less script
    in reply to: Recording Audio #5175
    Jeremy
    Keymaster

    Hello Gemma,

    This is really weird, it’s an error that comes from an old version of the PHP script from this page, I remember fixing it (on August 15, 2019 according to the logs). Did you make sure your PHP script is up to date?

    Jeremy

    in reply to: Automatic credit granting, and group assignment #5174
    Jeremy
    Keymaster

    That’s because the counter value from the URL is interpreted as a string but the switch statement tests for numbers. Use Number(GetURLParameter("withsquare")) to make sure the counter value from the URL is interpreted as a number.

    Jeremy

    in reply to: Automatic credit granting, and group assignment #5168
    Jeremy
    Keymaster

    No difference, I was just trying to make the forum’s syntax-highlighter happy

    in reply to: Automatic credit granting, and group assignment #5165
    Jeremy
    Keymaster

    Hello!

    1. Everything looks OK as far as I can tell, but I think you should be able to test SONA’s credit granting if you can create a dummy account. At least this is what I did in the past, and I setup my SONA projects to only accept my dummy account as a participant (maybe I also password-protected them?) and I was able to do a proper testrun

    2. Do you mean that the credit token will be different for different SONA experiments? I forget. If so, you could check the value of withsquare from the URL at the top of your script and set a variable accordingly that you use to generate your link:

    credittoken = "";
    
    switch (GetURLParameter("withsquare")) {
      case 0:
        credittoken = "hellothere";
        break;
      case 1:
        credittoken = "byenow";
        break;
      case 2:
        credittoken = "salutsalut";
        break;
      case 3:
        credittoken = "bonbahaplus";
        break;
    }
    
    // ...
    
        newText('Click here to confirm my submission on SONA.')

    Jeremy

    in reply to: Quit Trial after 3rd Attempt #5163
    Jeremy
    Keymaster

    Hello Sam,

    I see how things become tricky here: in order to reach the end of the trial, you will need to validate the Button’s wait command. This is fine as long as you give the correct answer, because then the test on your TextInput is a success. However, you also want to end your trial after 3 wrong answers, which means validating the wait, but we’ve already established that this situation corresponds to a failed test on your TextInput, so your wait command won’t be validated!

    One solution here is to not call wait on this specific button, but on another one instead, and handle your tests in a callback instead. I’m pasting a full functional script below: I slightly rearranged your code to my preferences, but of course feel free to move things back to how they were. I also updated the command names to reflect the changes in PennController 1.7. And finally, the AddTable bit is here just to create a table named fulldesign.csv in case someone wants to test the code who doesn’t have an appropriate table (as was my case).

    PennController.ResetPrefix(null)
    
    Sequence( "start", "task", "end" )
    
    newTrial("start", newButton("start").print().wait())
    newTrial("end", newButton("end").print().wait())
    
    AddTable("fulldesign.csv", `sen,answer,n
    what's 2+2?,4,1
    what's my cat's name?,buffy,2
    is this fun?,so-so,3`)
    
    Template( "fulldesign.csv" ,
        variable => newTrial( "task" ,
            newVar("attempt").global()
                .test.is(3).failure(
            // beginning of Var test
            newText("warning", "Your guess shouldn't be empty")
            ,
            newText(variable.sen).print()
            ,
            newText("input","Enter your guess:   ")
                .after(newTextInput("guess").log("final"))
                .print()
            ,
            getVar("attempt").set(0)
            ,
            newButton("bb", "Continue")
                .print()
                .callback( 
                    getTextInput("guess").test.text(variable.answer)
                    .failure( getTextInput("guess").test.text("")
                        .success(getText("warning").print())
                        .failure(
                            getVar("attempt").set( v => v+1 ).test.is(3)
                                .success( 
                                    getButton("bb").disable(),
                                    getButton("next").print() 
                                )
                        )
                    )
                    .success( getButton("next").click() )
                )
            ,
            newButton("next", "Sorry, you failed 3 times")
                .wait()
            // end of Var test
            )
        )
        .log( "ParticipantID", GetURLParameter("id") )
        .log("trial", variable.n)
    )

    Let me know if you have any questions

    Jeremy

Viewing 15 posts - 1,321 through 1,335 (of 1,522 total)