Matching Roles Partners v7 - nodeGame/nodegame GitHub Wiki

Overview

Sometimes the same client type in the same step shall behave differently, according to a predefined set of roles.

For example, in an ultimatum game, players are paired together, and in the same step one player is making an offer (role = 'BIDDER') and the other one is waiting to receive it (role = 'RESPONDENT').

It is possible to define roles and to automatically assign them by adding properties to game steps using the stager API, as explained in the code examples below.

Note: only matching in pairs is currently supported. For Group Matching see here.

Logic code

The logic must pick a matching algorithm and specify the roles inside the matcher step property. Here, we match the players in pairs so that each player meets his or her partner once in the role of 'BIDDER' and once in the role of 'RESPONDENT.' If many rounds are played, the matching is repeated in cycle.

stager.extendStep('step1_bidder', {
    matcher: {

        // Roles are specified as array of two or three elements. They
        // are automatically assigned according to the matching algorithm.
        // If the number of players is odd, in turns, each player receives
        // a _bye_, that is third role in the array, the 'SOLO' role here.
        // If roles are not specified, only partner matching takes place.
        roles: [ 'BIDDER', 'RESPONDENT', 'SOLO' ],

        // The available matching algorithms: 'random_pairs' or 'roundrobin'.
        // Each player is matched with a partner and is assigned a role.
        match: 'roundrobin',

        // If there are more rounds in the stage than possible
        // matches between players, you must specify what to do after
        // all matches have been used. Available cycle options:
        //   - 'repeat': repeats exactly same partner and role matching
        //   - 'repeat_invert': repeats the same order of matching, but
        //         inverts the roles.
        //   - 'mirror': inverts the order of matching, but keeps
        //         the same roles.
        //   - 'mirror_invert': inverts both the order of matching
        //         and the roles.
        cycle: 'repeat_invert',

        // Additional options:

        // If the number of players is odd, it is possible to exclude
        // from the matches those who received the _bye_. Default: false
        skipBye: false,

        // Normally, a player receives the role and the id of the
        // partner. However, it is possible to avoid sending the id
        // of the partner. Default: false.
        sayPartner: false,

        // If TRUE, a player keeps the same role for all the matches.
        // Default: false
        fixedRoles: true,

        // If TRUE, a player can be matched with others of the same role.
        // Default: false
        canMatchSameRole: false,

        // Advanced options.
        ////////////////////

        // Callback that assigns ids to positions
        // 
        // An assigner callback must take as input an array of ids,
        // reorder them according to some criteria, and return it.
        // The order of the items in the returned array will be used to
        // match the numbers in the `matches` array.
        assignerCb: function(arrayIds) {
            // Switches around roles for a specific ID.
            if (arrayIds[0] === 'X') {
                arrayIds[0] = arrayIds[1];
                arrayIds[1] = 'X';
            }
            return arrayIds;
        },

        // Overwrites the number of requested matches. Default:
        // one for every possible combination, but no more
        // than the total number of rounds in the game stage.
        // Useful if you plan to change/remove the matching
        // after a few rounds within the same stage.
        rounds: 2,

        // Forces calling the matching procedure every round,
        // otherwise matches are re-used.
        reInit: true,

        // Default id assigned to complete matches in case partner is missing.
        bye: -1
    }
});

Player code

By default, the role and the partner are automatically sent to each client, and stored under the following locations:

node.game.role = 'ROLE_NAME';
node.game.partner = 'PARTNER_ID';

The player client type must implement the roles in the corresponding game step.

Roles are nested inside the roles object, inside which the names of the properties must match the names of the roles. All the properties inside a role overwrite other step properties defined outside a role.

stager.extendStep('step1_bidder', {
    roles: {
        BIDDER: {
            frame: 'bidder.html',
            cb: function() {
                // Make an offer of 50 to the RESPONDENT.
                node.say('OFFER', this.partner, 50);
            }
        },
        RESPONDENT: {
            frame: 'resp.html',
            cb: function() {
               // Waits for the answer to arrive, then go to the next step.
            }
        },
        SOLO: {
            frame: 'solo.html',
            cb: function() {
                // This player is not matched with anyone, he or she
                // just waits...
            }
        }
    }
});

Validity of role and partner

By default, the role and partner properties are valid only within the same step. To re-use them across steps they either need to be sent again by the server, or being "copied over" to the next step explicitly, like in the example below:

stager.extendStep('step2_respondent', {
    // Role and partner meaning:
    //   - falsy      -> delete (default),    
    //   - string     -> as is (must exist),
    //   - function   -> must return null or a valid role name
    //   - true       -> keep the role reference from previous step, but does not load new role step properties.
    role: function() { return this.role; },
    partner: function() { return this.partner; },
    roles: {
        RESPONDENT: {
            timeup: function() {
                // Take actions to resolve the timeup.
            },
            cb: function() {
                // Say to BIDDER the offer was rejected.
                node.say('REJECT', this.partner);
            }
        },
        BIDDER: {
            cb: function() {
                // Do stuff.
            }
        },
        SOLO: {
            cb: function() {
                // Keep doing stuff alone...
            }
        }
    }
});

Accessing the Matches

The logic can access all the matches and roles in the step callback via the method:

matcher.getMatches(<mod>, <round>);

For example:

stager.extendStep('step1_bidder', {
    matcher: {
        roles: [ 'BIDDER', 'RESPONDENT', 'SOLO' ],
        match: 'roundrobin',
        cycle: 'repeat_invert'
    },
    cb: function() {
        var matches, i;

        // Get matches for current round.
        matches = this.matcher.getMatches('ARRAY_ROLES_ID');

        // Inform each player of its own role and partner id.
        // (note: informing players of role and partner is done
        //  automatically by nodeGame, this is just an example).
        for (i = 0 ; i < matches.length ; i++) {
            node.say('YOU_ARE_BIDDER', matches[i].BIDDER, {
                partner: matches[i].RESPONDENT
            });
            node.say('YOU_ARE_RESPONDENT', matches[i].RESPONDENT, {
                partner: matches[i].BIDDER
            });
        }
    }
});

The method matcher.getMatches(<mod>, <round>) can automatically format its return values depending on the value of mod:

  • 'ARRAY' (default):

[ [ 'id1', 'id2' ], [ 'id3', 'id4' ], ... ]

  • 'ARRAY_ROLES':

[ [ 'ROLE1', 'ROLE2' ], [ 'ROLE1', 'ROLE2' ] ]

  • 'ARRAY_ROLES_ID':

[ { ROLE1: 'id1', ROLE2: 'id2' }, { ROLE1: 'id3', ROLE2: 'id4' }, ... ]

  • 'ARRAY_ID_ROLES':

[ { id1: 'ROLE1', id2: 'ROLE2' }, { id3: 'ROLE1', id4: 'ROLE4' }, ... ]

  • 'OBJ':

{ id1: 'id2', id2: 'id1', id3: 'id4', id4: 'id3' }

  • 'OBJ_ROLES_ID':

{ ROLE1: [ 'id1', 'id3' ], ROLE2: [ 'id2', 'id4' ] }

  • 'OBJ_ID_ROLES':

{ id1: 'ROLE1', id2: 'ROLE2', id3: 'ROLE1', id4: 'ROLE2' }

The second optional input parameter select a round different than current to retrieve the matches.

Other useful API methods include:

  • matcher.getRoleFor(id, <round>);
  • matcher.getMatchFor(id, <round>);
  • matcher.getIdForRole(role, <round>);

Sources

Next Topics

See Also

⚠️ **GitHub.com Fallback** ⚠️