Assistance with Updating a Drawing Task Code for Randomized Trials and Rewards

Hello Chris,

My name is Shirin Taghian, and I am a research assistant in Dr. Kohitij Kar’s lab. We previously contacted you for help in designing a drawing task where monkeys would connect two dots by drawing a line between them. Your assistance was invaluable, and we have been successfully using that task.

Currently, we are looking to modify the task, and I would greatly appreciate your guidance. Specifically, I Preformatted text aim to make the following updates:

  1. Introduce multiple trials in the task.
  2. Randomize the positions of the dots in each trial.
  3. Provide a reward after each line drawn.

I have written some code that successfully randomizes the dot positions and incorporates the reward system. However, when I attempt to implement a protocol for handling multiple trials, the program gets stuck and does not work as expected.

Could you please help me debug this issue or advise how I can modify the code to make it work? I am happy to share the current version of the code for your review.

Thank you very much for your time and assistance!

//
// Mouse input device.  This enables testing on a Mac using the mouse pointer as
// a substitute for touch input.
//
var pointer_x = 0  // Pointer X coordinate
var pointer_y = 0  // Pointer Y coordinate

mouse_input (
    mouse_position_x = pointer_x
    mouse_position_y = pointer_y
    autostart = true
    )
//
// Variables
//

%define dot_count = 5  // Number of dots
var dot_size = 2.0  // Size of dots
var dot_positions_x = [0] * dot_count  // X positions of dots
var dot_positions_y = [0] * dot_count  // Y positions of dots
var pointer_on_dot = [false] * dot_count  // Tracks whether pointer is on a dot
var current_dot_index = -1  // Index of current dot
var previous_dot_index = -1  // Index of previous dot
var next_line_index = 0  // Tracks the next line to draw
var line_begin = [[0, 0]] * (dot_count - 1)  // Start points of lines
var line_end = [[0, 0]] * (dot_count - 1)  // End points of lines
var reward_duration_ms = 50  // Reward duration
var pump_control_line = false  // Reward pump control
var loop_index = 0  // Helper variable for loops
var min_distance = 5.0  // Minimum distance between dots
var is_valid_line = false  // Flag for valid line drawing

// Helper variables
var unique = 0
var check_index = 0
var distance = 0.0
var retry_count = 0  // Declare retry_count globally
var too_close = 0  // Declare too_close globally


//
// Stimuli: Dots
//

stimulus_group dots {
    range_replicator (
        variable = rr_index
        from = 0
        to = dot_count - 1
        step = 1
    ) {
        circular_fixation_point dot$rr_index (
            trigger_width = dot_size
            trigger_watch_x = pointer_x
            trigger_watch_y = pointer_y
            trigger_flag = pointer_on_dot[$rr_index]
            x_size = dot_size
            x_position = dot_positions_x[$rr_index]
            y_position = dot_positions_y[$rr_index]
        )
    }
}

//
// Stimuli: Lines
//

stimulus_group lines {
    range_replicator (
        variable = rr_index
        from = 0
        to = dot_count - 2
        step = 1
    ) {
        rectangle line$rr_index (
            color = 0, 0, 0
            x_size = max(0.1, sqrt(pow(line_end[$rr_index][0] - line_begin[$rr_index][0], 2) +
                                   pow(line_end[$rr_index][1] - line_begin[$rr_index][1], 2)))
            y_size = 0.2
            x_position = (line_begin[$rr_index][0] +
                          line_end[$rr_index][0]) / 2
            y_position = (line_begin[$rr_index][1] +
                          line_end[$rr_index][1]) / 2
            rotation = atan2(line_end[$rr_index][1] - line_begin[$rr_index][1],
                             line_end[$rr_index][0] - line_begin[$rr_index][0]) * (180 / pi())
        )
    }
}

//
// Protocol: Line Drawing Task
//

protocol 'Line Drawing Task' {
    block 'Main Block' {
        trial 'New Trial' {
            task 'Line Drawing Task' {
                state 'Initialize Trial' {
                    report('Initializing trial')
                    loop_index = 0
                    next_line_index = 0
                    current_dot_index = -1
                    previous_dot_index = -1
                    goto('Randomize Dot Positions')
                }

                 state 'Randomize Dot Positions' {
                    report('Randomizing dot positions')
                    loop_index = 0
                    retry_count = 0  // Declare outside the while loop
                    too_close = 0  // Declare outside the while loop

                    while (loop_index < dot_count) {
                        unique = 0
                        retry_count = 0  // Reset retry count for each dot
                        while ((unique == 0) && (retry_count < 100)) {
                            dot_positions_x[loop_index] = rand(-10, 10)
                            dot_positions_y[loop_index] = rand(-10, 10)
                            unique = 1
                            check_index = 0
                            too_close = 0  // Reset too_close flag
                            while (check_index < loop_index) {
                                distance = sqrt(pow(dot_positions_x[loop_index] - dot_positions_x[check_index], 2) +
                                                pow(dot_positions_y[loop_index] - dot_positions_y[check_index], 2))
                                if (distance < min_distance) {
                                    too_close = 1
                                    unique = 0
                                    check_index = loop_index  // Exit the inner loop
                                }
                                check_index = check_index + 1
                            }
                            retry_count = retry_count + 1
                        }
                        if (retry_count >= 100) {
                            report('Warning: Could not find unique position for dot $loop_index')
                        }
                        loop_index = loop_index + 1
                    }
                    goto('Queue Dots')
                }

                state 'Queue Dots' {
                    report('Queuing dots for display')
                    loop_index = 0
                    while (loop_index < dot_count) {
                        queue_stimulus(dots[loop_index])
                        loop_index = loop_index + 1
                    }
                    update_display()
                    goto('Interact with Dots')
                }

                state 'Interact with Dots' {
                    report('Waiting for user interaction with dots')
                    start_timer(
                        timer = InteractionTimer
                        duration = 10s
                    )
                    transition (
                        target = 'Draw Line'
                        when = pointer_on_dot[0] or pointer_on_dot[1] or pointer_on_dot[2] or pointer_on_dot[3] or pointer_on_dot[4]
                    )
                    timer_expired(
                        target = 'Dequeue Dots'
                        timer = InteractionTimer
                    )
                }


                state 'Draw Line' {
                    report('Drawing line')
                    previous_dot_index = current_dot_index

                    // Update current dot index based on pointer
                    if (pointer_on_dot[0]) {
			 current_dot_index = 0 
		    }
                    if (pointer_on_dot[1]) {
			 current_dot_index = 1 
		    }
                    if (pointer_on_dot[2]) { 
			current_dot_index = 2 
		    }
                    if (pointer_on_dot[3]) { 
			current_dot_index = 3 
		    }
                    if (pointer_on_dot[4]) { 
			current_dot_index = 4 
		    }

                    is_valid_line = previous_dot_index != -1 and previous_dot_index != current_dot_index and next_line_index < dot_count - 1

                    if (is_valid_line) {
                        line_begin[next_line_index] = [dot_positions_x[previous_dot_index], dot_positions_y[previous_dot_index]]
                        line_end[next_line_index] = [dot_positions_x[current_dot_index], dot_positions_y[current_dot_index]]
                        
                        report('Line Begin: [$line_begin[$next_line_index][0], $line_begin[$next_line_index][1]]')
                        report('Line End: [$line_end[$next_line_index][0], $line_end[$next_line_index][1]]')

                        live_queue_stimulus(lines[next_line_index])
                        send_stimulus_to_back(lines[next_line_index])
                        update_display()

                        pump_control_line = true
                        wait (reward_duration_ms / 1000.0)  // Reward duration
                        pump_control_line = false

                        report('Line drawn and reward given')
                        next_line_index = next_line_index + 1
                    } 
		    else {
                        report('Invalid line - not drawn')
                    }

                    goto('Interact with Dots')
                }

                state 'Dequeue Dots' {
                    report('Removing dots from display')
                    loop_index = 0
                    while (loop_index < dot_count) {
                        dequeue_stimulus(dots[loop_index])
                        loop_index = loop_index + 1
                    }
                    update_display()
                    goto('Initialize Trial')
                }
            }
        }
    }
}

Hi Shirin,

There are a few problems here:

  1. The trigger_flag parameter of a fixation point must be a variable name. It can’t be an index into an array. Changing this is on my to-do list, but at present, you need to define a separate pointer_on_dot variable for each fixation point you use.

  2. A paired if and else need to be placed inside an if_else. Otherwise, the else will always execute.

  3. The duration of the wait in state “Draw Line” needs to be reward_duration_ms * 1000.0. The default duration units are microseconds, not seconds.

  4. The transition from state “Draw Line” back to state “Interact with Dots” needs to be contingent on the pointer not being on any of the dots. Otherwise, as long as the pointer remains on one of the dots, the experiment will bounce back and forth between the two states at maximum speed, and MWServer will be overloaded with all the events.

I’ve attached a version of your experiment that incorporates fixes for these issues, and I believe it now works as you intended.

Cheers,
Chris
with_fixes.mwel (8.8 KB)

Hello Chris,

Happy new year.
Sorry for my delay! Thank you very much for your help, it was really helpful.

Best,
Shirin