Need help with designing an experiment!

Hello guys!

I’m a neuroscience student and currently working on coding my first experimental paradigm. I am nearly done and only one last step blocks me. This is the problem: I need to make experimental blocks, which are then composed by trials. Each trial is done by the subject. I have a total of 168 scenes which make up these blocks. Of these 168 scenes, 160 make up one condition (which I will call “new”) and 8 another (which I will call “old”).
Now, the experiment consists in preseting to the subjects 20 blocks, composed of 8 OLD conditions and 8 NEW conditions in each block. Here’s the difficult part (for me, of course :D): the OLD conditions cannot repeat within a block but can through blocks, while the NEW conditions can only be presented ONCE in the whole experiment. They should of course all be randomized.

OLD:
1
.
.
.
8

NEW:
1
.
.
.
160

1 block = 8 old (randomized, not repeated), 8 new (randomized, not repeated through trials)

I have no idea how to program this in C#. I’ve tried to put the OLD conditions in an array, randomize from that one and then do the same thing for the new condition, but I get stuck. I have just started programming and this is where my skills end and where I hope your help comes in :slight_smile:

Thank you!

One quick note: if your experiment requires precise timing (e.g., accuracy on the order of milliseconds when presenting stimuli or recording reaction time), implementing it in Unity is not a good idea. Psychtoolbox (which works with MATLAB and to a lesser extent its open-source alternative Octave) would be a better choice. Building the experiment program in C++ using a reliable multimedia library such as SFML is probably the best option, but requires more development time.

Regarding your actual question: there are multiple ways to approach random-selection-without-repetition tasks like these. One method is to maintain a 3 of all items (in this case, scene indices) and remove an item from the list if/when it gets selected.

For example, you could have two “master” lists, one containing the A (“old”) conditions and one for the B (“new”) conditions. When configuring an individual block, you’d want to make a distinct copy of the A conditions list (since the A conditions are only forbidden from repeating within a given block, not across the experiment as a whole). The procedure for choosing scenes for a block is then:

  • Make a distinct copy of the master list of A scenes.
  • For each scene within the current block, decide whether to choose an A scene or a B scene (since you have 8 of each per block, you could simply keep track of how many A and B scenes you’ve used for the block so far and randomly choose to select an A scene or a B scene 50/50 until one of the two types runs out).
  • When choosing an A scene, randomly select (e.g., using Random.Range) an A scene from the local copy of the A list and then remove that scene from the list so it won’t be selected again (of course, add it to another list containing the scenes to use for the current block). Since this A list is local to the current block, the master A list is not altered and thus all A scenes are available during the next block when a new distinct copy of the master A list is made.
  • When choosing a B scene, randomly select a B scene from the master B list and then remove that scene from the list to prevent it being selected again. Since the selected scene is removed from the master B list, it won’t be available to the any subsequent blocks.

Hopefully that helps. If you’re quite new to programming it might be a bit high-level, so feel free to ask if you need clarification.

What I think you are asking for is “How to get a random index from a set of indexes without repeated indexes?”

I am going to assume you can refer to your scenes with indexes, so this is what you want to do.

  1. Make a array of bools(array size 8 for old)
  2. Get a random index from within that array(int index = Random.Range(0, array.length))
  3. Check is array[index] == false
  4. If true: set array[index] = true and return your index.
  5. If false: repeat from 2.

I wrote a script that might help you. This is not tested but you get the overall idea. I have also included a way to save these arrays so that you can save progress between experiments.

using UnityEngine;
using System.Collections;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO;

public class RandomIndexSelector : MonoBehaviour
{
		public bool[] usedIndexPool;
		
		/// <summary>
		/// The name of the file. Leave Empty if you do not want to save
		/// </summary>
		public string fileName;
		
		
		
		/// <summary>
		/// Awake this instance.
		/// </summary>
		private void Awake()
		{	
			if(fileName != "")
			{
				Load();
			}
			else
			{
				Reset();
			}
		}
		
#region Operations
		/// <summary>
		/// Reset usedIndexPool.
		/// </summary>
		public void Reset ()
		{
				for (int i = 0; i < usedIndexPool.Length; i++) {
						usedIndexPool  *= false;*
  •  		}*
    
  •  }*
    
  •  /// <summary>*
    
  •  /// Gets a new index from the index pool.*
    
  •  /// </summary>*
    
  •  /// <returns>The index or -1 if index has not found</returns>*
    
  •  public int GetIndex ()*
    
  •  {		*
    
  •  		bool hasActiveIndex = false;*
    
  •  		for (int i = 0; i < usedIndexPool.Length; i++) {*
    

_ if (usedIndexPool == false) {_
* hasActiveIndex = true;*
* break;*
* }*
* }*

* if (hasActiveIndex == false) {*
* return -1;*
* }*

* while (true) {*
* int index = Random.Range (0, usedIndexPool.Length);*
* if (usedIndexPool [index] == false) {*
* usedIndexPool [index] = true;*

* //if you want to save every time a new index is found*
_ /_
_
if(fileName != “”)_
_
{_
_
Save();_
_
}*_
_ */_

* return index;*
* }*
* }*
* }*
#endregion

#region Save and Load data
* //saves data to disk*
* public void Save ()*
* {*
* BinaryFormatter bf = new BinaryFormatter ();*
* FileStream file = File.Create (Application.persistentDataPath + “/” + fileName + “.dat”);*

* DataContainer dc = new DataContainer(usedIndexPool);*

* bf.Serialize (file, dc);*
* file.Close ();*
* }*

* //loads data from disk*
* public void Load ()*
* {*
* if (File.Exists (Application.persistentDataPath + “/” + fileName + “.dat”)) {*
* BinaryFormatter bf = new BinaryFormatter ();*
* FileStream file = File.Open (Application.persistentDataPath + “/” + fileName + “.dat”, FileMode.Open);*

* DataContainer dc = (DataContainer)bf.Deserialize (file);*
* file.Close ();*

* //Loads the saved data to the active instance*
* usedIndexPool = new bool[dc.usedIndexPool.Length];*
* for (int i = 0; i < usedIndexPool.Length; i++) {*
usedIndexPool = dc.usedIndexPool ;
* }*
* } else {*
* Debug.Log (“No save data found”);*
* Reset ();*
* }*
* }*

* public class DataContainer*
* {*
* public bool[] usedIndexPool;*
* public DataContainer(bool[] uip)*
* {*
* usedIndexPool = new bool[uip.Length];*
* for(int i = 0; i < uip.Length)*
* {*
usedIndexPool = uip*;*
* }*
* }*
* }*
#endregion
}

And this is how you use it.
using UnityEngine;
using System.Collections;

public class UseCase : MonoBehaviour
{
* //set newPool and oldPool from the inspector*
* //newPool size = 168, newPool filename and oldPool size = 8*
* public RandomIndexSelector newPool;*
* public RandomIndexSelector oldPool;*

* // Update is called once per frame*
* void Update ()*
* {*
* if(Input.GetKeyDown(KeyCode.N))*
* {*
* int newIndex = newPool.GetIndex();*
* Debug.Log("NewIndex: " + newIndex);*

* if(newIndex == -1)*
* {*
* Debug.Log(“No more active indexes in new pool”);*
* }*
* }*

* if(Input.GetKeyDown(KeyCode.O))*
* {*
* int oldIndex = oldPool.GetIndex();*
* Debug.Log("OldIndex: " + oldIndex);*

* //check is oldIndex is valid*
* if(oldIndex == -1)*
* {*
* Debug.Log(“No more active indexes in old pool”);*
* //oldPool.Reset*
* //or*
* //end of test*
* }*
* }*
* }*
}