Jeremy

Forum Replies Created

Viewing 15 posts - 1,366 through 1,380 (of 1,522 total)
  • Author
    Posts
  • in reply to: Force response? #5042
    Jeremy
    Keymaster

    Hi,

    Sure, adding an error message is actually pretty easy:

    newText("emptyinput", "Please type your answer in the field above").color("red").bold()
    ,
    getTextInput("inputanswer")
        .wait( 
            // Using a regular expression here: at least one non-empty character
            getTextInput("inputanswer").test.text(/[^\s]/) 
                .failure( getText("emptyinput").print() )
        )
    ,
    getText("emptyinput").remove()
    

    There currently is no straightforward way to check your participants’ devices, but you can inject some javascript using the Function element. This page for example describes one method, we could incorporate it into pcibex like this:

    newTrial(
      newFunction( () => navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i) )
        .testNot.is(null)
        .success(  
          newText("We are sorry but this experiment was not designed for mobile devices. Please try again with a non-mobile device. Thank you.").print()
          ,
          newTimer(1).wait()
        )
      ,
      newButton("Start the experiment").print().wait()
    )

    Note that this is definitely not 100% reliable, but any tech-savvy enough person who wants to fool your experiment will be able to anwyay

    Also note that some platforms, like Prolific, do not accept this kind of filtering after your participants have enrolled for your experiment on the platform

    Jeremy

    in reply to: Repeat Trial After Feedback #5039
    Jeremy
    Keymaster

    Hi Farzaneh,

    Yeah, PCIbex is not great at handling loops at the moment, I should work on a solution for that. In the meantime, what you can do is first create all the elements you will manipulate at the beginning of your trial, and put the bit that needs to be re-executed upon failure in an array, so you can reference it multiple times, like this:

    // We'll refer to this sequence of commands multiple times
    let launch = [
        newTimer(500)
            .start()
            .wait()
        ,
        getAudio("BeepBeep.mp3")
            .play()
            .wait()
        ,
        newTimer(500)
            .start()
            .wait()
         ,
        getAudio("PracSHSR.mp3")
            .play()
        ,
        getImage("one")
            .size(380,380)
            .css("border", "solid 1px black")
        ,
        getImage("two")
            .size(380,380)
            .css("border", "solid 1px black")
        ,
        getImage("three")
            .size(380,380)
            .css("border", "solid 1px black")
        ,
        getCanvas("images")
            .add( 0 , 0, getImage("one") )
            .add( 400 , 0 , getImage("two") )
            .add( 800, 0, getImage("three") )
            .print()
        ,
        getAudio("PracSHSR.mp3")
            .wait()
        ,
        getSelector("choice").enable()
    ]
    
    newTrial(
        // Create your elements first
        newAudio("BeepBeep.mp3"),
        newAudio("PracSHSR.mp3"),
        newAudio("Beep.mp3")
        ,
        newImage("one", "Man.png"),
        newImage("two", "Boy.png"),
        newImage("three", "Girl.png")
        ,
        newCanvas("images", 1200 , 400)
        ,
        newText("positiveFeedback", "Good job!"),
        newText("negativeFeedback", "Wrong answer. Try again.")
        ,
        newSelector("choice")
        ,
        // Now execute the sequence of commands above
        ...launch
        ,
        getSelector("choice")
            .add(getImage ("one"), getImage ("two"), getImage ("three") )
            .keys ("F", "J", "T")
            .log()
            // Only release 'wait' if correct choice
            .wait(
                getSelector("choice").test.selected( getImage("two") )
                .failure( 
                    // If wrong choice...
                    getText("negativeFeedback")
                        .center()
                        .print()
                    ,
                    newTimer(2000).start().wait()
                    ,
                    getText("negativeFeedback").remove()
                    ,
                    // Make sure to disable selection again
                    getSelector("choice").disable()
                    ,
                    // And take the Canvas off the page
                    getCanvas("images").remove()
                    ,
                    // Now re-execute the sequence of commands above
                    ...launch
                )
            )
            .disable()
        ,
        getText("positiveFeedback")
            .center()
            .print()
        ,
        newTimer(2000)
            .start()
            .wait()
        ,
        getAudio("Beep.mp3")
            .play()
            .wait("first")
        ,
        newTimer(1000)
            .start()
            .wait()
    )

    Let me know if you have questions

    in reply to: conditions in self-paced reading experiment #5038
    Jeremy
    Keymaster

    Hi,

    I’m not sure which Text element represents your context sentence in the script that you included in your message, but here are a few notes:

    • The wait command has no effect on Text elements, it doesn’t “mean” anything (what would be the relevant event to wait for?)—so you can get rid of the .wait() command on your first Text element
    • Remember that the script reads your commands and executes them line by line, from the top down, which means that it will start by printing your first Text element (the one that contains row.S) then “wait” (= no effect) and immediately remove that element. So you won’t have time to see it being displayed on the page. You probably want to move that .remove() command somewhere else, or get rid of it entirely if you want to keep row.S displayed on the screen for the duration of the whole trial
    • Much like the wait commands on Text elements, the print command has no effect on Key elements, again it doesn’t “mean” anything (what would there be to display?)—so there too, you can get rid of the .print() commands on your Key elements
    • Your last two Scale elements are both named correct, which will be confusing once you .log them because they will be hard to distinguish in your results file, you should consider renaming the second one “article” or something

    The script I showed in my previous message was just an example. It shows ‘tricky’ questions along with two possible answers, for example the first row shows the question “What is the result of 2+3*4?” and prints two possible answers: answer1 = “14” and answer2 = “20”—we know that the result of 2+3*4 is 14 (because multiplication takes precedence) so in the table we added a column named “correct” where the value for the first row is “answer1,” the correct answer. If you look at the two .log commands attached to the closing parenthesis of the newTrial command, the first one precisely references row.correct, which makes sure that the lines in the results file will also report which of answer1 or answer2 was the correct choice for every trial generated in that Template command from the table named “myTable.”

    Jeremy

    in reply to: disable a button after selection #5033
    Jeremy
    Keymaster

    Hi,

    So what happens is that your script first prints your Text element, then creates your two Button elements without printing them, because you don’t call print on them, and then halts on the wait command of your No button. Of course you can never release the wait because the button was never printed to start with so you cannot click on it. What you want instead is something like this:

    newTrial("NYCheck",
        newText("q","Are you currently residing in New York State?")
            .print()
        ,
        newCanvas(200, 100)
          .add( 0  , 0 , newButton("yes","Yes") )
          .add( 50 , 0 , newButton("no","No")   )
          .print()
        ,
        getButton("no","No")
            .callback(
                getText("q").text("Thank you. You may now close this window.")
                ,
                getButton("no").remove(),
                getButton("yes").remove()
            )
        ,
        getButton("yes").wait() // will wait forever if button removed
    )

    Jeremy

    Jeremy
    Keymaster

    Right, sorry, I hadn’t anticipated the Text elements not being randomized along with their respective Scale elements

    The reason why the after and before contents don’t get carried along is because the shuffled elements could actually themselves be in an after/before relationship (as in the example on the documentation page).

    At this point the only solution I see is to create Canvas elements that contain your Scale+Text elements, like this:

    newCanvas("active_scale_container", "auto", "1.5em").print(),
    newScale("active_scale", 7)
        .after( newText("active_label", "  active") )
        .slider()
        .print(0,0,getCanvas("active_scale_container"))
    ,
    newCanvas("exciting_scale_container", "auto" , "1.5em").print(),
    newScale("exciting_scale", 7)
        .after( newText("exciting_label", "  exciting") )
        .slider()
        .print(0,0,getCanvas("exciting_scale_container"))
    ,
    newSelector()
        .add( getCanvas("active_scale_container"), getCanvas("exciting_scale_container") )
        .shuffle()
        .disable()

    Unfortunately you have to specify an arbitrary height, because of the CSS rules that PennController applies to Canvas elements, but 1.5em sounds like a reasonable height.

    Jeremy

    Jeremy
    Keymaster

    Hi,

    Your use of the Selector element to shuffle the Scale elements is on the right track, but then calling .wait on that Selector means that you want your participant to simply click on one of the two scales. You should probably replace that .wait() command with .disable() to turn your Selector into a dummy element (you just use it to shuffle the Scales) and add a Validation button at the end where you check that both scales have an option selected, like this:

    newButton("Validate")
      .print()
      .wait( getScale("active_scale").test.selected().and(getScale("exciting_scale").test.selected()) )

    Jeremy

    in reply to: Script won't 'test'; just says loading. #5024
    Jeremy
    Keymaster

    Hi,

    Do you have the Debugger popin window open at the bottom-right corner of the page when you test your experiment? If so, does it report anything in the Errors tab?

    There can be many reasons why the experiment crashes at launch. One sneaky bug I found out about recently was that having filenames with upper-case extensions in your Resources folder will make the experiment crash.

    Jeremy

    in reply to: Two-screen trials? #5023
    Jeremy
    Keymaster

    Yes, just decouple the printing of the TextInput from its wait command, like this:

    newTextInput("inputanswer").print().log()
    ,
    newText("Validate by pressing Enter").italic().print()
    ,
    getTextInput("inputanswer").wait()

    Jeremy

    in reply to: Two-screen trials? #5020
    Jeremy
    Keymaster

    Hi,

    Simply use the .remove command on the Text elements that you no longer want on the new screen. It’s easier if you give them a name first, like this:

    Template( variable => 
      newTrial( "experiment" ,
        newTimer(500)
            .start()
            .wait()
        ,
        defaultText
            .print()
            .center()
        ,
        newText("sentence", variable.sentence) // named your Text element 'sentence'
        ,
        newText("pressspace" , "Press the spacebar to continue.") // named your Text element 'pressspace'
            .css("margin-top", "2em") // cleaner than 3 br's
            .italic()
        ,
        newKey(" ")
            .wait()
        ,
        getText("sentence").remove(),  // remove your Text elements
        getText("pressspace").remove() // here
        ,
        newText("Press Enter to submit your answer.")
            .css("margin-bottom", "2em") // same thing as above
            .italic()
        ,
        newText("question", variable.question)
        ,
        newTextInput("inputanswer")
            .print()
            .log()
            .wait()
        ,
        newVar("answer")
            .global() // I suspect you want to make this global so you can use it in another log below
            .set( getTextInput("inputanswer") )
      ) // this parenthesis should be here                        
      .log( "SubjID"     , getVar("ID")    )
    ) // this one remains here 

    Jeremy

    in reply to: conditions in self-paced reading experiment #5017
    Jeremy
    Keymaster

    Hi,

    Did you make sure you used .remove on the right element? Here is an example:

    newText( "context" , "This is the context sentence").print()
    ,
    newText("space", "Press space to continue").italic().print()
    ,
    newKey(" ").wait()
    ,
    getText("context").remove(),
    getText("space").remove()
    ,
    newController("DashedSentence", {s: "This is the dashed sentence"})
      .print()
      .log()
      .wait()

    If you don’t need to check whether the participant’s answer is correct at runtime (ie. you don’t need to give positive or negative feedback, for example) then simply indicate which answer is correct in a column of your table and reference it in a .log command on the closing parenthesis of newTrial. Same thing about group. You can find an illustration on page 8 of the tutorial (“Trial templates & tables”). Here is a short example:

    AddTable("myTable", `question,answer1,answer2,correct,group
    What is the result of 2+3*4?,14,20,answer1,tricky
    Who is the only other child of my sibling's parents?,my cousin,me,answer2,tricky
    What is the result of 2+3+4?,9,24,answer1,control
    Who is the only child of my parents?,my cousin,me,answer2,control`)
    
    Template( "myTable" , row =>
      newTrial(
        newText( row.question ).print()
        ,
        newText( "answer1" , row.answer1 ).print(),
        newText( "answer2" , row.answer2 ).print()
        ,
        newSelector("answer").add( getText("answer1") , getText("answer2") ).log().wait()
      )
      .log( "correct" , row.correct )
      .log( "group" , row.group )
    )

    And yes, the DashedSentence controller records reaction times, and PCIbex makes sure to always record everything that original Ibex controllers record, so you will be able to find reaction times as extra columns for your trial in your results file as long as you use .log on your Controller element. Make sure to always take a testrun yourself just before sending your experiment’s link to your participants, and check that the results file reports all you need for analyses.

    Jeremy

    • This reply was modified 5 years, 3 months ago by Jeremy.
    in reply to: Priming in reading-aloud experiment #5011
    Jeremy
    Keymaster

    Hi Monica,

    I would say you can partly accomplish what you want using the VoiceRecorder element. Take a look at the required setup here.

    To give you an example of a code that does something close to what you want (skipping the setup part):

    newTrial(
      newAudio( "sound.mp3" ).play().wait()
      ,
      newTimer(500).start().wait()
      ,
      newText( "Now please repeat the word your heard" ).print()
      ,
      newVoiceRecorder("sample").log().record()
      ,
      newText("recording...").print()
      ,
      newTimer(2000).start().wait()
      , 
      getText("recording...").remove()
      ,
      getVoiceRecorder("sample").stop()
    )

    Now as you can see, there is no reaction time per se, as VoiceRecorder elements currently do not automatically detect when people start and stop speaking. You could analyze your collected samples with a software like Praat to see how long it took your participants to start speaking. The code above uses a 2s time window, but if you’re willing to ask your participants to press a key, you could let them do that to signal that they are done recording.

    Let me know if you have questions

    Jeremy

    in reply to: Reaction times of a selector response #5009
    Jeremy
    Keymaster

    Hi Kayla,

    The reason why there is no ‘reaction time’ column by default is precisely because the starting point is arbitrary: which event do you consider relevant to start measuring how fast your participants are to answer?
    By default PennController logs when the trial starts, so if that’s what you want, you can use that EventTime to subtract from the selection’s EventTime. If you’d rather refer to, say, the display of an image as your starting point, then you can use the .log command on the Image element (or its containing Canvas) and you’ll have a line in your results file reporting when the image was printed on the screen.
    The very last page of the tutorial includes an R script that illustrates how to calculate reaction times using the EventTime column (with the beginning of the trial as the starting point).

    Yet another option is to calculate the RTs upon runtime, and add a “ReactionTime” column to your trials using .log on newTrial. For that, you need to use a Var element, like this:

    Template( row =>
      newTrial(
        newButton("Start the trial").print().wait().remove()
        ,
        newSelector("choice").once()
        ,
        defaultImage.size(200,200).selector("choice")
        ,
        newCanvas( 400 , 200 )
          .add(   0 , 0 , newImage( row.target     ) )
          .add( 200 , 0 , newImage( row.competitor ) )
          .print()
        ,
        newVar("RT").global().set( v => Date.now() )
        ,
        getSelector("choice").shuffle().wait()
        ,
        getVar("RT").set( v => Date.now() - v )
        ,
        newTimer(500).start().wait()
      )
      .log( "ReactionTime" , getVar("RT") )
    )

    Jeremy

    in reply to: Can't get results properly recorded #5006
    Jeremy
    Keymaster

    Well, this is a bug, I’ll fix it in the next release

    In the meantime you can use .log("all") which does not bug and still records the first (and subsequent) keypress(es)

    Jeremy

    in reply to: Can't get results properly recorded #5004
    Jeremy
    Keymaster

    Hi,

    Yes, by default the log command on the Key element will only record keypresses corresponding to a wait command, in an effort to reduce the number of lines it adds to the results file (participants can technically press the same key many times in the course of one trial). In your case you probably want to use .log("first") to record the first keypress that happens. More info on the documentation.

    Jeremy

    in reply to: Using Group for two different blocks #4998
    Jeremy
    Keymaster

    I just tested your table (replacing filenames to point to files in my project) and your code, and it seems to be working fine

    I added labels to the trials though, ie. I added "training", after newTrial( in the first Template command and "test", after in the second one, and I referenced them in the Sequence command: Sequence( "intro" , "training" , "test" ) (I had to add an intro trial first to set the ID Var element)

    What is the unwanted behavior that you observe?

    EDIT: oh, did you add .log( "Group" , row.Group ) to the closing parenthesis of each newTrial command? Ibex also adds another Group column to the results file, which has a slightly different meaning, so you want to make sure you’re logging the value directly from your table

    • This reply was modified 5 years, 3 months ago by Jeremy. Reason: note about Ibex adding a group column to the results file
Viewing 15 posts - 1,366 through 1,380 (of 1,522 total)