Generate randomly positioned rectangles with no overlap

Hello PsychoPy Discourse community!

I am trying to create a fully functioning Corsi Block Tapping task. The goal is to have 9 randomly placed, yet not overlapping rectangles appear on the screen. I can manage the random part, but often rectangles overlap.
My idea is to check, whether rectangles overlap, and if they do, to keep on generating new positions until no rectangles overlap, and only if no overlap is detected to actually draw the rectangles.

Here is the code I belive to be relevant:

    # store blocks as a dictionary (to switch between name/object)
    blocks = {}
    blocks['blk1']=blk1
    blocks['blk2']=blk2
    blocks['blk3']=blk3
    blocks['blk4']=blk4
    blocks['blk5']=blk5
    blocks['blk6']=blk6
    blocks['blk7']=blk7
    blocks['blk8']=blk8
    blocks['blk9']=blk9
    
    
    # give blocks a new set of random locations
    for label, block in blocks.items():
        block.pos = random(2)*0.8-0.4
        if block.overlaps(block):  # check for overlapping
            block.pos = random(2)*0.8-0.4
        else:
            block.color = 'white'

Any suggestions would be much appreciated!

@hvongbg, there is a working Corsi block task demo on Pavlovia. You can download the project and adapt it to your needs

https://run.pavlovia.org/demos/corsi/html/

@dvbridges, all versions on Pavlovia have the fault of overlapping rectangles. I currently am adapting a version I found on Pavlovia, trying to add some code to prevent the overlapping… but with no success so far

The way I’ve dealt with this in the past is by using what I call a jittered grid.

Instead of giving the blocks free reign, create an array of locations on a grid and shuffle it. The following code will produce an array of 6 non-overlapping locations from a grid of 24. The amount of jitter can be changed to ensure there is no overlap.

#Variables for user to edit
rows = 4
columns = 6
nchoices = 6

bottom_left = [-.8,-.6]
top_right = [.8,.6]

scale = 0.15
jitter = .1

#Computed variables
nspots = rows * columns
spot_chosen = [0] * nspots

locations=[]
nselected = 0
iselected = []

for Idx in range(rows):
    for Jdx in range(columns):
        dotx = Jdx * (top_right[0]-bottom_left[0])/(columns-1) + bottom_left[0] + random.uniform(-jitter, jitter)
        doty = Idx * (top_right[1]-bottom_left[1])/(rows-1) + bottom_left[1] + random.uniform(-jitter, jitter)
        locations.append([dotx,doty])
        
random.shuffle(locations)
selected = []
for Jdx in range(nchoices+1):
    dotx = Jdx * (top_right[0]-bottom_left[0]+jitter*2)/(nchoices) + bottom_left[0]-jitter
    selected.append([dotx,-.9])

As you can see, this hasn’t been written for online, but replacing random should be fairly easy.

Best wishes,

Wakefield

I’m also interested in doing this. Did you end up finding a solution for this?

I found the following, which worked well. Unfortunately, it doesn’t translate properly to javascript, so I wasn’t able to run it online.

# initial state
blkIndex_3 = 0
nextSwitch_3 = blockDuration
doingResponse_3 = False
currBlock_3 = None
import scipy
import numpy
blockCoords_3 = []
dist_min_3 = 0
actualSequenceLength = min(sequenceLength_3,9)

# store answer blocks as a dictionary (to switch between name/object)
blocks_3 = {}
blocks_3['blk1_3']=blk1_3
blocks_3['blk2_3']=blk2_3
blocks_3['blk3_3']=blk3_3
blocks_3['blk4_3']=blk4_3
blocks_3['blk5_3']=blk5_3
blocks_3['blk6_3']=blk6_3
blocks_3['blk7_3']=blk7_3
blocks_3['blk8_3']=blk8_3
blocks_3['blk9_3']=blk9_3

# give blocks a new set of random locations
while True:
    for label, block in blocks_3.items():
        block.pos = random(2)*0.8-0.4
        blockCoords_3.append(block.pos)
        dist_3 = scipy.spatial.distance.pdist(blockCoords_3)
        block.color = 'white'
    if numpy.min(dist_3)>.11:
        dist_min_3 = numpy.min(dist_3)
        break
    else:
        blockCoords_3 = []
        dist_3 = []
        dist_min_3 = 0

I managed to adapt the task.
It is not very fancy, but it works. I dont know whether it translates well to JS. I defined fixed postions on the screen for every block, and then calculated a random jitter. Then added it to each postion manually, and randomized the postions in the list, to make it look like block were preseneted in a radom order. The rest of the task is as on Pavlovia.
In the —Prepare to start Routine “trial”— part of the code I added the following:
Note that I have 9 blocks.

blocks = {}
    blocks['blk1']=blk1
    blocks['blk2']=blk2
    blocks['blk3']=blk3
    blocks['blk4']=blk4
    blocks['blk5']=blk5
    blocks['blk6']=blk6
    blocks['blk7']=blk7
    blocks['blk8']=blk8
    blocks['blk9']=blk9

locations on the screen

locations = [(-.52, 0.3), (-.52, 0.03), (-.52, -0.25), (0.04, 0.3), (0.04, 0.03),(0.04,-.25), (0.47, 0.3), (0.47, 0.03), (0.47, -0.25)]

create jitter

jitter = [np.random.uniform(-0.1, 0.1) for x in range(18)]

manually ad jitter to lacations

locations_1 = [
[locations[0][0] + jitter[0],
locations[0][1] + jitter[1]],
[locations[1][0] + jitter[2],
locations[1][1] + jitter[3]],
[locations[2][0] + jitter[4],
locations[2][1] + jitter[5]],
[locations[3][0] + jitter[6],
locations[3][1] + jitter[7]],
[locations[4][0] + jitter[8],
locations[4][1] + jitter[9]],
[locations[5][0] + jitter[10],
locations[5][1] + jitter[11]],
[locations[6][0] + jitter[12],
locations[6][1] + jitter[13]],
[locations[7][0] + jitter[14],
locations[7][1] + jitter[15]],
[locations[8][0] + jitter[16],
locations[8][1] + jitter[17]]]

Shuffle locations in list

np.random.shuffle(locations_1)

set counter to 0

i = 0
#give blocks a new set of random locations
for label, block in blocks.items():
    block.pos = locations_1[i]
    i = i + 1 #add to counter to itterate through list
    block.color = 'white'

Also you probably need to add some libraries to the skript. These are the ones I used for the corsi task. some were there by default, others I added but not sure which. I believe to remember that the “numpy.random” was missing and the “numpy as np” to call on the numpy lib.

from psychopy import locale_setup
from psychopy import prefs
from psychopy import sound, gui, visual, core, data, event, logging, clock
from psychopy.constants import (NOT_STARTED, STARTED, PLAYING, PAUSED,
                                STOPPED, FINISHED, PRESSED, RELEASED, FOREVER)

import numpy as np  # whole numpy lib is available, prepend 'np.'
from numpy import (sin, cos, tan, log, log10, pi, average,
                   sqrt, std, deg2rad, rad2deg, linspace, asarray)
from numpy.random import random, randint, normal, shuffle
import os  # handy system and path functions
import sys  # to get file system encoding

from psychopy.hardware import keyboard

Hope this is helpfull enough, otherwise let me know!
Helene

1 Like

This is very helpful. Thank you!