コンテンツへスキップ

チーム分けコード

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;
};