GASで「情シスビンゴ」を作ってみた！

## Why & What｜はじめに
「作ったきかっけ」と「どんなものができたか」については、下記のnoteにまとめました。
先にご確認いただけると嬉しいです！

https://note.com/tk1253/n/n94c6512d08ed

## How｜スクリプトの内容
### GitHub
https://github.com/tk1253/app-gas-wordBINGO/tree/main

## 特に学びとなった部分の振り返り
### メニューの作成
```javascript:ui.gs
// サンプルコード
// スプレッドシートが開かれたら自動的に実行される関数
function onOpen() {
  let ui = SpreadsheetApp.getUi();
  ui.createMenu('[GAS]')
    .addItem('実行する処理の名前', 'function名')
    .addSeparator()
    .addSubMenu(
      ui.createMenu("設定")
        .addItem('実行する処理の名前', 'function名')
        .addItem('実行する処理の名前', 'function名'))
    .addToUi();
}
```
- 過去、挿入した図形にスクリプトを割り当て「GAS実行ボタン」を作ったりしていましたが、Uiクラスからメニューを作成する方がスッキリしますね。
- 階層構造も作ることができて、とても便利。

### 配列から要素をランダムに選択する（Math.random）
```javascript:sample-getRandomValue.gs
function getRandomValue(array, key) {
  // [0以上 array.length 未満]のランダムな整数を生成
  let randomIndex = Math.floor(Math.random() * array.length);
  // [ランダムに選択されたインデックス]に対応する配列の要素を取得
  let selectedElement = array[randomIndex];
  // インデックスを指定して値を取り出す
  let selectedValue = selectedElement[key];

  // [選択された要素]と[そのプロパティの値]をオブジェクトとして返す
  return { element: selectedElement, value: selectedValue };
}

// サンプル
const sampleArray = [
  { id: 1, value: "A" },
  { id: 2, value: "B" },
  { id: 3, value: "C" },
  { id: 4, value: "D" },
  { id: 5, value: "E" }
];

const result = getRandomValue(sampleArray, "value");

console.log("Selected Element:", result.element);
console.log("Selected Value:", result.value);
```

- Math.floor()
    - 引数として与えられた数値以下で、最大の整数に切り捨てられた値を返す
- Math.random()*array.length
    - Math.random()
        - 「0~1」間の数字をランダムに生成する（「0.37654」などなど）
    - array.length
        - 配列の要素数

こちらの記事が分かりやすかったです。

https://programming-engineer.com/javascript-random/


[ garagarapon.gs ]では、ランダムに取り出した値（ビンゴ文字／' item '）を書き込みなどに利用していますね。

```javascript:garagarapon.gs
  // ランダムに要素を1つ選択
  let randomIndex = Math.floor(Math.random() * remainingItems.length);
  let selectedItem = remainingItems[randomIndex];
  let item = selectedItem[1];
```






### 配列全体をランダムに並べ替える（Fisher–Yates のシャッフル）
```javascript:createBingoCards.gs
function fisherYatesShuffle(array) {
  // 変数を宣言する時に同時に値を代入して初期化（array.length）
  let currentIndex = array.length, temporaryValue, randomIndex;

    // currentIndex : 配列内の現在の要素の位置を示す変数
    // temporaryValue : 要素の交換時に一時的に値を保存するための変数
    // randomIndex : ランダムに選択されたインデックスを保存する変数

  // currentIndex が 0 になるまで繰り返す
  while (currentIndex !== 0) {
    // 配列の中からランダムにインデックスを選択
    randomIndex = Math.floor(Math.random() * currentIndex);
    // currentIndex をデクリメント（数値を1ずつ減少させる）
    currentIndex -= 1;

    // 現在の要素をtmp変数に保存
    temporaryValue = array[currentIndex];
    // 現在の要素とランダムに選択した要素を交換
    array[currentIndex] = array[randomIndex];
    array[randomIndex] = temporaryValue;
  }

  // シャッフルされた配列を返す
  return array;
}
// サンプル
let sampleArray = [1, 2, 3, 4, 5];
let shuffledArray = fisherYatesShuffle(sampleArray);

console.log(shuffledArray);
```
- 1つの配列のなかで、「入れ替え前」と「入れ替え後」の領域に分割して考える
    - 1回目：元の配列から1つをランダムに選択し、配列の末尾の要素と入れ替える
    - 2回目：末尾の要素は「入れ替え後」の領域なので、末尾-1の範囲から1つをランダム選択し、「入れ替え前」領域の末尾の要素と入れ替える
    - 3回目：「入れ替え後」領域に2つの要素があるので、「入れ替え前」領域は元の配列の末尾-2の部分
- 「入れ替え前」領域が無くなるまで繰り返す



こちらの記事がとても分かりやすかったです。

https://www.pandanoir.info/entry/2013/03/04/193704



### 抽選された要素を探し「済フラグ」を入れる
```javascript:setDoneStatus.gs
function setDoneStatus(sheet, columnName, targetValue, setStatus) {
  // シート上のすべての値を取得
  let data = sheet.getDataRange().getValues();

  // 列名が格納された最初の行を取得
  let headerRow = data[0];

  // columnNameに指定された列のインデックスを取得
  let columnIndex = headerRow.indexOf(columnName);

  // columnNameが見つからない場合はエラーを出力して終了
  if (columnIndex === -1) {
    console.error(`Column '${columnName}' not found.`);
    return;
  }

  // データの行を走査（ [iの初期値=1]なので2行目から処理を開始）
  for (let i = 1; i < data.length; i++) {
    // 対象の値が見つかったらステータスを設定してループ終了（break）
    if (data[i][columnIndex] === targetValue) {
      sheet.getRange(i + 1, columnIndex + 1).setValue(setStatus);
      break;
    }
  }
}

// サンプル
// function setDoneStatus(sheet, columnName, targetValue, setStatus)
setDoneStatus(all_item_sheet, "name", item, "済");

```

- 要素をランダムに選択する処理と合わせて、事後処理として汎用性が高い。

サンプルコードとほぼ同じですが、[ garagarapon.gs ]では「要素シート」上のすべての値を取得して、各行の2番目の要素が、引数として渡された name と一致するかを確認しています。

```javascript:garagarapon.gs
// 選ばれた要素に済フラグを入れる関数
function set_done(num_item_sheet, name) {
  // num_item_sheet（要素シート）上のすべての値を取得
  let num_item_data = num_item_sheet.getDataRange().getValues();
  for (let i = 1; i < num_item_data.length; i++) {
    // 各行の2番目の要素（num_item_data[i][1]）が、引数として渡された name と一致するか確認
    if (num_item_data[i][1] == name) {
      // 一致した行の3番目の要素（num_item_data[i][2]）に '済' を設定し、 break でループ終了
      num_item_sheet.getRange(i + 1, 3).setValue('済');
      break;
    }
  }
}
```
<br>
余談ですが、要素に数字を入れて実行してみると、シャッフルされているのが分かりやすいですね。
<br>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420464/d911c112-81b5-bdd2-a441-1eb2b29fb06e.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/420464/a3c486e6-f09c-2d4c-70f4-a6ee5c4d310d.png)

<br>

以上です！

## 参考記事
### 「情シスビンゴ」GAS作成の参考
https://qiita.com/ikumi_okura/items/56e7d93d50fc1e12d979

https://myfunc.jp/items/00028/index.html

### スクリプト振り返りの参考
https://qiita.com/howdy39/items/46ca1f2fd9d27eaba0c3

https://qiita.com/shoheiyokoyama/items/4a49674f1e65671ea52b

https://qiita.com/takeharu/items/d75f96f81ff83680013f