-
Notifications
You must be signed in to change notification settings - Fork 18
Matching Roles Partners v7
- status: complete
- version: 7.x
- follows from: Data Exchange Examples
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.
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
}
});
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...
}
}
}
});
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...
}
}
}
});
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>);
Go back to the wiki Home.
Copyright (C) 2021 Stefano Balietti
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.