/**
* This function will return a function where it will randomize a string (in this case a member name) and pair it up with an object's property name (in this case the role name) if that object's member property array.length is not the same as its quota property (i.e. the object's quota is not full yet).
* There are 6 stages of randomization in this function, basically: STAGE 1 - randomize the roles order, STAGE 2 - randomize the members order, STAGE 3 - randomize which member to pick first, STAGE 4 - randomize which assignment to pick for that member, STAGE 5 - if a member is already paired before or if the assignment's quota is already full, randomize the member and assignment again (back to stage 3); and finally STAGE 6 - if an assignment's quota is full, go back to stage 3.
* Even though the result should be random, there is still a possibility of the results being similar or the same to the previous results.
* To use this function, just call this function inside of a variabel or something that can contain the returned function and pass this function the corresponding arguments. After that, just call the returned function without passing any arguments to see the results. Since this function returns a function with its own closure, you can make as many randomizer as you want with different arguments and results and call them seperately.
* @param {{}} rolesAndQuota - an object where each property's property name would have a name that would be paired up with a string from membersList parameter (in this case, the role name will be paired up with a member name which will then be stored in the resultAnnouncement variabel below); the property name can be spaced out, e.g.: 'make powerpoint'. The property value of each object's property would be the name's (role's) quota, this will determine how many strings from membersList (how many members) can be paired up with the same property name (same role), e.g.: makePPT: 2, would mean there will be 2 members that could be assigned with makePPT, so like: ['Chris gets to makePPT', 'Adam gets to makePPT']. The syntax of the object for the argument should something like this: {makePPT: 2, makePaper: 1, role: quota, etc.}
* @param {string[]} membersList - a string array where each item is the string that will be paired up with the property name from rolesAndQuota object (in this case: each item is the name of the member that will be paired up with a role based on the rolesAndQuota object's property names that is passed as the argument). Example of this array: ["Adam", "Eve", "memberName", etc.].
* IMPORTANT!!! the total number of quota in rolesAndQuota should match the total number of members (i.e. the membersList.length). For example: {makePPT: 1, makePaper: 3} in total, the quota is 4 (1 + 3). So the membersList.length should also be 4, so: ["Adam", "Eve", "Chris", "John"].length = 4. IT IS NOT RECOMMENDED FOR THE membersList.length > total quota because this will result in an infinite loop. IT IS ALSO NOT RECOMMENDED FOR THE membersList.length < total quota because even though it won't cause an inifnite loop, it's not going to be a fair assignment.
* @param {boolean} wantToBeConsoleLogged - true if the results will be console logged, false to return an object containing properties about the randomization results (default is true)
* @param {string} announcementMessage - sets the announcement message for the results, e.g.: announcementMessage = " gets to " -> "Chris gets to makePPT", "Adam gets to makePaper", etc. (default is " dapet bagian ")
* @param {number} resultConsoleLogInterval - sets the interval for the console log if wantToBeConsoleLogged is true (default value is 1000, set to 0 if you want to instantly console log everything)
* @returns function
*/
function randomizeRolesMaker(rolesAndQuota, membersList, wantToBeConsoleLogged = true, announcementMessage = " dapet bagian ", resultConsoleLogInterval = 1000) {
if ((function () {
let totalQuota = 0;
for (let eachQuota in rolesAndQuota) {
totalQuota += rolesAndQuota[eachQuota];
}
return totalQuota;
})() != membersList.length) {
let errorMessage = "Oops! It seems like the total number of quota doesn't match the total number of members, please adjust them and try again.";
console.error(errorMessage);
return errorMessage;
} else {
/**
* @param {string} roleName
* @param {number} quota
*/
const AssignmentObjects = function (roleName, quota) {
this.roleName = roleName;
this.quota = quota;
this.members = [];
}
return function () {
/**
* @type {object[]} - this variable will first call an IIFE where the IIFE will return an array containing objects made from AssingmentObjects to this variable.
*/
const assignmentObjectsArray = (function () {
const createdAssignmentObjects = [];
for (let roleNameOfRolesAndQuota in rolesAndQuota) {
createdAssignmentObjects.push(new AssignmentObjects(roleNameOfRolesAndQuota, rolesAndQuota[roleNameOfRolesAndQuota])); // This will make an AssignmentObjects object for each property in rolesAndQuota by looping through each property. For each loop it will take the role name of each property in the rolesAndQuota object that was passed as an argument to the first function. It will then use that name and pass it for the roleName argument and it will also be used to get the role's quota from rolesAndQuota and pass it as an argument.
}
return randomizeArray(createdAssignmentObjects, false); // STAGE 1 - randomize the roles order of the assignment objects inside of the createdAssignmentObjects array so that the order of the item could be different from the rolesAndQuota property order.
})();
const members = randomizeArray(membersList, false); // STAGE 2 - randomize the members order; make an array containing the randomized item order of membersList
/**@type {string[]}*/ const alreadyPushedMembers = []; // This array will be used to check for members that already has a role
const fullAssignmentQuotaObjectIndex = [] // This array is like alreadyPushedMembers but to to store the index assignments whose quota is already full (i.e.: AssignmentObjects.members.length = AssignmentObjects.quota) that is stored in the assignmentObjectsArray
let memberIterated = 0;
let whichMember;
let whichAssignment;
const resultAnnouncement = [];
while (memberIterated < members.length) {
while (true) {
whichMember = Math.floor(Math.random() * members.length); // STAGE 3 - randomize which member to pick first
whichAssignment = Math.floor(Math.random() * assignmentObjectsArray.length); // STAGE 4 - randomize which assignment to pick for that member
if (!alreadyPushedMembers.includes(members[whichMember]) && !fullAssignmentQuotaObjectIndex.includes(whichAssignment)) { // STAGE 5 - if a member is already paired before or if the assignment's quota is already full, randomize the member and assignment again (back to stage 3). If a member hasn't been paired before AND the assignment's quota isn't full yet based on the current fullAssignmentQuotaObjectIndex, then stop this while loop.
break;
}
}
if (assignmentObjectsArray[whichAssignment].members.length < assignmentObjectsArray[whichAssignment].quota) { // If the assignment's quota isn't full yet = true based on the current fullAssignmentQuotaObjectIndex
assignmentObjectsArray[whichAssignment].members.push(members[whichMember]); // Pair the member with that assignment by pushing the member's string to the assignment's AssignmentObjects object's members property.
alreadyPushedMembers.push(members[whichMember]);
resultAnnouncement.push(members[whichMember] + announcementMessage + assignmentObjectsArray[whichAssignment].roleName);
memberIterated++; // Make the announcement and store it in resultAnnouncement
} else { // STAGE 6 - if an assignment's quota is full, go back to stage 3.
fullAssignmentQuotaObjectIndex.push(whichAssignment); // So checking if the quota of that assignment is full or not happens in the if's conditional, not after pushing the member inside of the if's code block. The reason being is so that there is this 6th stage of randomization where if the quota is full, go back to stage 3. But the next loop if this else statement activates would make stage 5 and this else's if statement checking for assignments with full quota stricter.
}
}
if (wantToBeConsoleLogged) {
let resultAnnouncementIndex = 0;
let resultAnnouncementInterval = setInterval(function () {
if (resultAnnouncementIndex < resultAnnouncement.length) {
console.log(resultAnnouncement[resultAnnouncementIndex]);
resultAnnouncementIndex++;
} else {
clearInterval(resultAnnouncementInterval);
}
}, resultConsoleLogInterval);
} else {
const randomizedInfo = {
resultAnnouncement: resultAnnouncement,
assignmentObjectsArray: assignmentObjectsArray,
alreadyPushedMembers: alreadyPushedMembers,
originalRolesAndQuotaArgument: rolesAndQuota,
originalMembersList: membersList,
randomizedMembersList: members
}
console.table(randomizedInfo);
return randomizedInfo;
}
}
}
}