File: app/randomizers/permute.js
/**
* @module exp-player
* @submodule randomizers
*/
/**
* Randomizer to allow random ordering of a list of frames. Intended to be
* useful for e.g. randomly permuting the order of particular stimuli used during
* a set of trials (although frames need not be of the same kind to permute).
*
* To use, define a frame with "kind": "choice" and "sampler": "permute",
* as shown below, in addition to the parameters described under 'properties'.
*
```json
"frames": {
"test-trials": {
"sampler": "permute",
"kind": "choice",
"commonFrameProperties": {
"showPreviousButton": false
},
"frameOptions": [
{
"blocks": [
{
"emph": true,
"text": "Let's think about hippos!",
"title": "hippos!"
},
{
"text": "Some more about hippos..."
}
],
"kind": "exp-lookit-text"
},
{
"blocks": [
{
"emph": false,
"text": "Let's think about dolphins!",
"title": "dolphins!"
}
],
"kind": "exp-lookit-text"
}
]
}
}
*
* ```
* @class Permute
*/
var randomizer = function(frameId, frameConfig, pastSessions, resolveFrame) {
// Data provided to randomizer (properties of frameConfig):
/**
* List of frames to be created by this randomizer. Each frame is an
* object with any necessary frame-specific properties specified. The
* 'kind' of frame can be specified either here (per frame) or in
* commonFrameProperties. If a property is defined for a given frame both
* in this frame list and in commonFrameProperties, the value in the frame
* list will take precedence.
*
* (E.g., you could include 'kind': 'normal-frame' in
* commmonFrameProperties, but for a single frame in frameOptions, include
* 'kind': 'special-frame'.)
*
* @property {Object[]} frameOptions
*/
/**
* List of sets of frame properties of the same length as frameOptions. The order
* of this list will be preserved; the properties in orderedFrameOptions[0] will be added to the
* frame shown first, the properties in orderedFrameOptions[1] will be added to the
* frame shown second, etc. Properties are applied in this order:
* commonFrameProperties, frameOptions, orderedFrameOptions
* so orderedFrameOptions properties will take priority over regular frameOptions.
* This allows you to, for instance, do something different during the first or last
* trial (e.g., a practice/training trial or a debriefing trial).
* If `parameterSets` is included as one of the properties in orderedFrameOptions[n],
* the values will be added to any parameterSets property on the existing frame
* (value-by-value, iterating through corresponding parameterSets)
* rather than overwriting the whole property.
*
* @property {Object[]} orderedFrameOptions
*/
/**
* Object describing common parameters to use in EVERY frame created
* by this randomizer. Parameter names and values are as described in
* the documentation for the frameType used.
*
* @property {Object} commonFrameProperties
*/
// TODO: input checking. Make sure all parameters are given or impute empty vals if
// not; make sure orderedFrameOptions.length == frameOptions.length if both are given
// TODO: allow optional specification of how many frames to create!
/*
* Randomize array element order in-place.
* Using Durstenfeld shuffle algorithm.
*/
var array = frameConfig.frameOptions.slice();
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
var thisFrame = {};
var frames = [];
for (var iFrame = 0; iFrame < array.length; iFrame++) {
// Assign parameters common to all frames made by this randomizer
thisFrame = {};
Object.assign(thisFrame, frameConfig.commonFrameProperties);
// Assign parameters specific to this frame (allow to override
// common parameters assigned above)
Object.assign(thisFrame, array[iFrame]);
// Assign parameters specific to the frame occupying this position
// in the ordered list. These override everything else. If `parameterSets` are
// included, they are *added* to any parameterSets, rather than overwriting.
if (frameConfig.hasOwnProperty('orderedFrameOptions') && frameConfig.orderedFrameOptions.length > iFrame) {
if (frameConfig.orderedFrameOptions[iFrame].hasOwnProperty('parameterSets') && thisFrame.hasOwnProperty('parameterSets')) {
for (var iPS = 0; iPS < thisFrame.parameterSets.length; iPS++) {
Object.assign(thisFrame.parameterSets[iPS], frameConfig.orderedFrameOptions[iFrame].parameterSets[iPS]);
}
delete frameConfig.orderedFrameOptions[iFrame].parameterSets;
}
Object.assign(thisFrame, frameConfig.orderedFrameOptions[iFrame]);
}
thisFrame = resolveFrame(frameId, thisFrame)[0];
frames.push(...thisFrame);
}
/**
* Parameters captured and sent to the server
*
* @attribute conditions
* @param {Object[]} frameList the list of frames used, in the final shuffled order
*/
return [frames, {'frameList': array}];
};
export default randomizer;