2022年中旬にスプラトゥーン2のチーム分けを自動化したいと思って書きました。数時間で書いたわりには想像以上に動いてくれました。
4人チームのゲームですが、端数がいても交代要員でどこかのチームに割り振ります。
/***********************************
■ グローバル変数定義
***********************************/
//シート
var ss = SpreadsheetApp.getActiveSpreadsheet();
var ssMembers = ss.getSheetByName("参加者入力");
var ssResult = ss.getSheetByName("チーム分け結果");
//チーム数
var teamCount = 0;
//配列
var headerArray = [];
var dataArray = [];
//要素チーム閾値とカウンタ
//①前衛ローラー
var frontRollerThreshold = 0;
var frontRollerCount = 0;
//②チャージャー
var chargerThreshold = 0;
var chargerCount = 0;
//③ハイプレ
var presserThreshold = 0;
var presserCount = 0;
//④後衛
var rearThreshold = 0;
var rearCount = 0;
//やり直し判定
var thresholdOK = false;
/***********************************
■ メイン処理
***********************************/
function myFunction1() {
//確認メッセージ
var UserChoice = Browser.msgBox("チーム分けを行います。\\n" +
"腕前が近い参加者は毎回ランダムで割り振られるます。\\n" +
"前回のチーム分けが上書きされてしまいますが続行しますか?",
Browser.Buttons.OK_CANCEL);
if(UserChoice=="ok"){
//チーム分け
while (thresholdOK == false) {
shuffleTeam();
};
//1列目削除
headerArray[0].shift();
for (let i = 0; i <= dataArray.length-1; i++) {
dataArray[i].shift();
};
//最終列を1列目
headerArray[0].unshift(headerArray[0][headerArray[0].length-1]);
for (let i = 0; i <= dataArray.length-1; i++) {
dataArray[i].unshift(dataArray[i][dataArray[i].length-1]);
};
headerArray[0].pop();
for (let i = 0; i <= dataArray.length-1; i++) {
dataArray[i].pop();
};
//最終列削除
headerArray[0].pop();
for (let i = 0; i <= dataArray.length-1; i++) {
dataArray[i].pop();
};
//チームでソートする
let sortIndex = headerArray[0].indexOf("チーム");
dataArray.sort((a, b) => {return a[sortIndex] -b[sortIndex];} );
//チーム0を"余り"にする
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("チーム")] == 0) {
dataArray[i][headerArray[0].indexOf("チーム")] = "余り";
};
};
//チームごとの合計点計算
let teamScores = [["チーム", "合計ランク点数(補正後)"]];
for (team = 1; team <= teamCount; team++) {
teamScores.push([team, 0]);
};
for (team = 1; team <= teamCount; team++) {
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("チーム")] == team) {
teamScores[team][1] += dataArray[i][headerArray[0].indexOf("ランク点数(補正後)")];
};
};
};
//結果シートに張り付ける
ssResult.clearContents()
ssResult.getRange(1,1,1,headerArray[0].length).setValues(headerArray);
ssResult.getRange(2,1,dataArray.length,dataArray[0].length).setValues(dataArray);
ssResult.getRange(dataArray.length+3,1,teamScores.length,2).setValues(teamScores);
//見た目きれいにする
ssResult.getRange(1,1,50,12).setBorder(null, null, null, null, false, false);
ssResult.getRange(1,1,1,headerArray[0].length).setBorder(true, true, true, true, false, false);
ssResult.getRange(2,1,dataArray.length,dataArray[0].length).setBorder(true, true, true, true, false, false);
for (let i = 1; i <= dataArray.length-1; i++) {
if (dataArray[i-1][headerArray[0].indexOf("チーム")] != dataArray[i][headerArray[0].indexOf("チーム")]) {
ssResult.getRange(i+2,1,1,dataArray[0].length).setBorder(true, null, null, null, false, false);
};
};
ssResult.getRange(dataArray.length+3,1,teamScores.length,2).setBorder(true, true, true, true, false, false);
//結果シートを表示
ssResult.activate();
};
};
/***********************************
■ チーム分け
***********************************/
function shuffleTeam() {
//下準備******************************************************************************************************
//最終行、最終列、総人数 取得
let maxRow = ssMembers.getMaxRows();
let lastRow = ssMembers.getRange(maxRow, 2).getNextDataCell(SpreadsheetApp.Direction.UP).getRowIndex();
let memberCount = lastRow - 1;
let lastColumn = ssMembers.getLastColumn();
//総チーム数 計算
teamCount = memberCount / 4;
teamCount = Math.floor(teamCount);
//チーム平均点 計算
let teamScoreAverage = 0;
for (let r = 2; r <= lastRow; r++) {
teamScoreAverage += ssMembers.getRange(r, lastColumn).getValue();
};
teamScoreAverage = teamScoreAverage / memberCount * 4
//配列処理**************************************************************************************************
//データを配列に格納する
let copyRange = ssMembers.getRange(1,1,1,lastColumn);
headerArray = copyRange.getValues();
copyRange = ssMembers.getRange(2,1,memberCount,lastColumn);
dataArray = copyRange.getValues();
//ランダム列設定
headerArray[0].push("ランダム値")
for (let i = 0; i <= memberCount-1; i++) {
dataArray[i].push(Math.random());
};
//チーム列設定
headerArray[0].push("チーム")
for (let i = 0; i <= memberCount-1; i++) {
dataArray[i].push(0);
};
//ランク点数 > ランダム値の優先度でソート
let sortIndex = headerArray[0].indexOf("ランダム値")
dataArray.sort((a, b) => {return b[sortIndex] - a[sortIndex];} )
sortIndex = headerArray[0].indexOf("ランク点数(補正後)")
dataArray.sort((a, b) => {return b[sortIndex] - a[sortIndex];} )
//要素チーム閾値把握*****************************************************************************************
//①前衛ローラー
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("武器種")] == "ローラー") {
if (dataArray[i][headerArray[0].indexOf("前後衛")] == "前衛") {
frontRollerThreshold += 1;
};
};
};
frontRollerThreshold = frontRollerThreshold / teamCount;
frontRollerThreshold = Math.ceil(frontRollerThreshold);
//②チャージャー
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("武器種")] == "チャージャー") {
chargerThreshold += 1;
};
};
chargerThreshold = chargerThreshold / teamCount;
chargerThreshold = Math.ceil(chargerThreshold);
//③ハイプレ
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("スペシャル")] == "ハイパープレッサー") {
presserThreshold += 1;
};
};
presserThreshold = presserThreshold / teamCount;
presserThreshold = Math.ceil(presserThreshold);
//④後衛
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("前後衛")] == "後衛") {
rearThreshold += 1;
};
};
rearThreshold = rearThreshold / teamCount;
rearThreshold = Math.ceil(rearThreshold);
//チーム作成*************************************************************************************************
let checkIndex = -999;
let bottomUser = -999;
for (team = 1; team <= teamCount; team++) {
//チーム決まってないランク低い人
bottomUser = -999;
for (i = dataArray.length-1; i > 0; i--) {
if (dataArray[i][headerArray[0].indexOf("チーム")] == 0) {
bottomUser = i;
break;
};
};
//要素かぶりのない上位者とマッチング
checkIndex = -999;
for (i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("チーム")] == 0) {
if (checkThreshold(team, i, bottomUser) == true) {
checkIndex = i;
break;
};
};
};
if (checkIndex == -999) {return false};
dataArray[checkIndex][headerArray[0].indexOf("チーム")] = team;
dataArray[bottomUser][headerArray[0].indexOf("チーム")] = team;
let teamScore = 0;
teamScore += dataArray[checkIndex][headerArray[0].indexOf("ランク点数(補正後)")];
teamScore += dataArray[bottomUser][headerArray[0].indexOf("ランク点数(補正後)")];
//選出されていない人の組み合わせと合算点数を配列に入れ、点数で降順にソートする [index1, index2, 合算点数]
let pairArray = getCombinationArray();
pairArray.sort((a, b) => {return b[2] - a[2];} );
//残りの2人を選出する
checkIndex = -999;
let teamScoreGap = teamScoreAverage - teamScore;
for (i = 0; i <= pairArray.length-1; i++) {
if (pairArray[i][2] - teamScoreGap <= 0.25) {
if (checkThreshold(team, pairArray[i][0], pairArray[i][1]) == true) {
checkIndex = i;
break;
};
};
};
if (checkIndex == -999) {return false};
dataArray[pairArray[checkIndex][0]][headerArray[0].indexOf("チーム")] = team;
dataArray[pairArray[checkIndex][1]][headerArray[0].indexOf("チーム")] = team;
teamScore += dataArray[pairArray[checkIndex][0]][headerArray[0].indexOf("ランク点数(補正後)")];
teamScore += dataArray[pairArray[checkIndex][1]][headerArray[0].indexOf("ランク点数(補正後)")];
};
//かぶり要素最終確認*************************************************************************************************
thresholdOK = true;
for (team = 1; team <= teamCount; team++) {
if (checkThreshold(team) == false) {
thresholdOK = false;
};
};
return thresholdOK;
};
/***********************************
■ その他、関数
***********************************/
//かぶり要素判定
function checkThreshold(team, userIndex1=-999, userIndex2=-999) {
let checkStatus = true;
//人数カウンタの初期化
frontRollerCount = 0;
chargerCount = 0;
presserCount = 0;
rearCount = 0;
//既存チームメンバーの把握
for (let i = 0; i <= dataArray.length-1; i++) {
if (dataArray[i][headerArray[0].indexOf("チーム")] == team) {
thresholdCount(i)
};
};
//候補者の考慮
if (userIndex1 != -999) {
thresholdCount(userIndex1);
};
if (userIndex2 != -999) {
thresholdCount(userIndex2);
};
//判定
if (frontRollerCount > frontRollerThreshold ||
chargerCount > chargerThreshold ||
presserCount > presserThreshold ||
rearCount > rearThreshold) {
checkStatus = false;
};
return checkStatus;
};
//かぶり要素カウント
function thresholdCount(index) {
//①前衛ローラー
if (dataArray[index][headerArray[0].indexOf("武器種")] == "ローラー" &&
dataArray[index][headerArray[0].indexOf("前後衛")] == "前衛") {
frontRollerCount += 1;
};
//②チャージャー
if (dataArray[index][headerArray[0].indexOf("武器種")] == "チャージャー") {
chargerCount += 1;
};
//③ハイプレ
if (dataArray[index][headerArray[0].indexOf("スペシャル")] == "ハイパープレッサー") {
presserCount += 1;
};
//④後衛
if (dataArray[index][headerArray[0].indexOf("前後衛")] == "後衛") {
rearCount += 1;
};
};
//選出されていない人のペア組み合わせと合算点数を配列に入れる [index1, index2, 合算点数]
function getCombinationArray() {
let userIndex1 = 0;
let userIndex2 = 1;
let pairArray = [];
while (userIndex1 <= dataArray.length-2) {
if (dataArray[userIndex1][headerArray[0].indexOf("チーム")] == 0) {
userIndex2 = userIndex1 + 1;
while (userIndex2 <= dataArray.length-1) {
if (dataArray[userIndex2][headerArray[0].indexOf("チーム")] == 0) {
pairArray.push (
[userIndex1,
userIndex2,
dataArray[userIndex1][headerArray[0].indexOf("ランク点数(補正後)")] + dataArray[userIndex2][headerArray[0].indexOf("ランク点数(補正後)")],
]
);
};
userIndex2 += 1
};
};
userIndex1 += 1;
};
return pairArray;
};