「璃奈ちゃんボード」をつくろう！プログラム解説編

# 前回までのあらすじ

[「璃奈ちゃんボード」をつくろう！要件定義編](https://qiita.com/hamahamabe/items/9216df345bb908eb5199)
[「璃奈ちゃんボード」をつくろう！設計編](https://qiita.com/hamahamabe/items/f2a5497aea395eb9699d)

ここまで**要件定義編**では「どんな機能を実装するか」を、**設計編**では「どうやって実装するか」を検討してきました。
今回は、実際に作成したプログラムを見ながら実装についての詳しい解説を行っていきます。

おおまかな製作工程・完成品の様子はこちらです。併せてご覧ください。
[璃奈ちゃんボードの作り方【ラブライブ！虹ヶ咲学園スクールアイドル同好会】](https://youtu.be/0yT_qYHqlOU)
<img width="400" src="http://img.youtube.com/vi/0yT_qYHqlOU/0.jpg">

また、今回の記事をもって璃奈ちゃんボード解説シリーズは終了となります。最後まで楽しんで（？）いただければ幸いです。

# ソフトウェア解説
詳しいことは後述しますがArduino用の**制御用プログラム**の他に、`GoogleAppsScript` を用いて**表情設定ファイル自動生成スクリプト**も作成しました。
今回はこれら２つのプログラムについての解説を行っていきます。

## Arduino
Arduinoに書き込むプログラムは**スケッチ**と呼ばれます。スケッチには`C/C++`をベースにした**Arduino言語**という専用の言語が用いられ、この**Arduino言語**はC言語のすべての構造といくつかのC++の機能をサポートしています。僕は**C言語は基礎を少々触れたことがありC++は全くの未経験**という状況だったのでほぼC言語の知識でスケッチを作成しましたが、概ね問題なくコーディングを行うことができました。C言語の基礎を知っていれば違和感なく扱えるのではないかと思います。

### Arduinoの基礎
プログラム解説を行う前に、Arduinoの動作について簡単に説明をします。
Arduinoは先述したように、**Arduino言語**で書かれた**スケッチ**を書き込むことで動作します。
また、Arduinoは多数の**I/Oポート**（ピン）を備えており、そこに各種センサーやスイッチなどを接続することでハードウェアとソフトウェアを連携させることが可能です。

スケッチは基本的に以下の形式で記述されます。

```C
void setup() 
{
  // 電源が入った時最初に一回だけ実行される処理
}

void loop() 
{
  // 電源が入っている間ずっと繰り返される処理
}
```

`setup()`関数は起動時に一度だけ行われる処理で、その後起動している間ずっと`loop()`関数が繰り返し実行され続けます。

簡単な具体例で説明します。
例えばArduinoの10番目のピンに普通のLEDを接続し、以下のスケッチを書き込んだとします。

```C
void setup() 
{
  pinMode(10, OUTPUT);      //「10番のピンを出力に使います」という宣言
}

void loop() 
{
  digitalWrite(10, HIGH);   //「10番のピンを"HIGH"にする（=10番につないだLEDをオンにする）」という命令
  delay(1000);              //1000ミリ秒（1秒）待機
  digitalWrite(10, LOW);    //「10番のピンを"LOW"にする（=10番につないだLEDをオフにする）」という命令
  delay(1000);              //1000ミリ秒（1秒）待機
}
```

電源が入るとまず`setup()`が実行され10番のピンを出力に使う準備が行われ、その後`loop()`に書かれた「LEDが光る→1秒待機→LEDが消える→1秒待機」という処理がずっと繰り返し実行されます。つまり、このスケッチが書き込まれたArduinoは **「電源を切るまで1秒間隔でLEDが点滅し続ける装置」** ということになります。

### 「璃奈ちゃんボード」のスケッチ
Arduinoの動作の基礎について説明が終わったところで、ついに璃奈ちゃんボードのスケッチがどんな動作をしているのか見ていきたいと思います。細かい実装については説明を端折るので、興味のある方は以下からソースを覗いてみてください。
https://github.com/hmskdagoyabai/rinachan-board

#### ヘッダインクルード&各種宣言部分

まずはヘッダインクルード&各種宣言部分を見ていきます。

```C
#include <FastLED.h>
#include "faces.h"
```

今回は`NeoPixel`の制御に`FastLED`というライブラリを使用するため`FastLED.h`をインクルードします。(事前にArduino IDEでライブラリのインストールを行う必要あり)
また、`faces.h`はGASで生成した表情設定ファイルです。これについては後ほど詳しく解説します。

```C
/* PIN */
#define SW1_PIN 5
#define SW2_PIN 4
#define SW3_PIN 3
#define SW4_PIN 2
#define DATA_PIN 10
```

使用するピンについての宣言は**設計編**で決めたとおり、`SW1~4`をつなぐピンとNeoPixel制御用のデータピンだけです。


```C
/* FastLED */
#define RINA_PINK 0xff1493
#define NUM_LEDS 154
CRGB leds[NUM_LEDS];
```

`FastLED`で使うピンク色のカラーコードを`RINA_PINK`として定義しています。ちなみに`RINA_PINK`は<font color="ff1493">こんな色</font>です。
また、制御するLEDの数(今回は**設計編**で決めた通り154個)を`NUM_LEDS`と定義しています。`FastLED`では繋いだ`NeoPixelLED`を配列として扱うことができるので、その為の配列として`CRGB leds[NUM_LEDS]`を宣言しています。`CRGB`は`FastLED`で定義されている構造体ですが、その内容についてはとりあえず意識しなくても大丈夫です。


#### 各種操作用フラグなど

今回のプログラムでは各種モードや表情・プリセットなどをフラグで管理しており、それらのフラグはグローバル変数としてプログラムの最初に宣言しています。

```C
/* 各種操作用フラグ・パラメータ */
int brightness = 30;            //default 30
int mode = 1;                   //1:random 2:manual 0:dokipipo
int random_timemode = 1;        //0:random 1:constant
int now_preset = 0;             //selected preset number
int now_preset_size = FACES;    //selected preset size
int now_manualmode_emote = 0;   //selected emotion in manualmode
int automode_preset = 0;        //selected preset in automode (only 0:dokipipo)
int automode_playing = 0;       //playing flag
int automode_waiting_emote = 0; //selected emotion in automode
```

それぞれ簡単に説明します。

* `brightness`
明るさを指定。SW4で明るさの調整が可能。

* `mode`
使用中のモード番号を保持するフラグ。（1:ランダムモード　2:マニュアルモード　0:プリセット再生モード）

* `random_timemode`
ランダムモード時の表情切り替え時間設定を保持するフラグ。（0:等間隔モード　1:ランダム間隔モード）
オート感が減るかな？と思ってランダムな間隔で表情が切り替わるモードを実装したけど、いまいち。

* `now_preset`
現在使用中の表情プリセット番号を保持するフラグ。

* `now_preset_size`
使用中の表情プリセットの要素数を保持するフラグ。

* `now_manualmode_emote`
マニュアルモードで表示中の表情番号を保持するフラグ。

* `automode_preset`
再生モードで選択している再生プリセットを保持するフラグ。死に機能気味。

* `automode_playing`
再生モードの再生開始フラグ。死に機能気味。

* `automode_waiting_emote`
再生モードの再生待ち中に表示しておく表情番号を保持するフラグ。死に機能気味。




#### メインの処理

ようやくメインの処理についてです。
以下が今回のスケッチの`setup`関数と`loop`関数です。

```C
void setup()
{
  FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS);
  FastLED.setBrightness(brightness);
  pinMode(SW1_PIN, INPUT_PULLUP);
  pinMode(SW2_PIN, INPUT_PULLUP);
  pinMode(SW3_PIN, INPUT_PULLUP);
  pinMode(SW4_PIN, INPUT_PULLUP);
  randomSeed(analogRead(0)); //乱数初期化
}

void loop()
{
  operate_flags_by_switch();
  delay(50);
  operate_emotion_by_flag();
  delay(50);
}
```

##### setup()

まず`setup`関数で初期処理を行います。
最初の2行では、`FastLED.addLeds`で使用するNeoPixelLEDを定義し`FastLED.setBrightness`でLEDの明るさを指定しています。
その次の4行は`SW1~4`をインプットに使用するという宣言で（プルアップを有効）、最後の行はランダム関数の初期化を行っています。ここでは未使用の0番ピンのアナログ入力（=ノイズ）をseed値として利用しています。（参考：http://www.musashinodenpa.com/arduino/ref/index.php?f=0&pos=2867 ）

##### loop()

そしてメインの処理である`loop`関数の内容です。
`operate_flags_by_switch`は「スイッチを読み取りフラグを操作する関数」で、  `operate_emotion_by_flag`は「フラグを読み取り表情（画面）を操作する関数」となっています。
一見かなりシンプルですが、ここの処理は少し複雑なのでフローチャートで示します。各処理の内容は詳しく説明しないので、興味のある方はコードを参照してみてください。


###### operate_flags_by_switch

`operate_flags_by_switch`では4つのボタンの入力を読み取り、入力に応じて`sw1()`～`sw4()`という関数を呼び出しています。
![flag.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547023/2c741a0d-8f9b-2e65-d157-81bd7c631a75.png)

`SW3`と`SW4`は長押しに対応するためカウンタを用いて長押ししている時間を計測し、長押し/短押しによって異なる引数を与えて`sw3`、`sw4`関数を呼び出しています。最終的に呼び出された`sw1()`～`sw4()`関数がフラグの操作を行います。
例として以下に`sw3`関数の内容を示します。

```C
/* SW3の挙動 */
void sw3(bool is_long)
{
  //randmode時
  if (mode == 1)
  {
    //間隔モードを切り替える
    random_timemode = !random_timemode;
  }
  //manualmode時
  else if (mode == 2)
  {
    //短押し時はプリセットをひとつ進める
    if (!is_long)
    {
      now_preset = (now_preset + 1) % PRESETS;
      now_preset_size = presets_sizes[now_preset];
      now_manualmode_emote = 0;
    }
    //長押し時はプリセットをallに
    else if (is_long)
    {
      now_preset = 0;
      now_preset_size = presets_sizes[now_preset];
      now_manualmode_emote = 0;
    }
  }
  //automode時
  else if (mode == 0)
  {
    //誤操作防止のため長押し時のみ反応
    if (is_long)
    {
      //再生フラグオン
      automode_playing = 1;
    }
  }
}
```

`mode`の値と引数で与えられた`is_long`フラグの値によって行う操作を決定する動作です。**要件定義編**で示した操作方法の表と見比べてみると何をしているのかがわかりやすいかもしれません。興味のある方は他の`sw`関数も操作方法の表と見比べてみてください。

| モード  |  スイッチ  |  動作  |
| :---: | :---: | ---- |
|  全モード共通  |  SW4  |  短押し : 明るさを１段階上げる <br> 長押し : モードを１つ進める  |
|  ランダムモード  |  SW1  |  プリセットを１つ進める  |
|  ランダムモード  |  SW2  |  プリセットを１つ戻す  |
|  ランダムモード  |  SW3  |  表情が切り替わる間隔を変更する（等間隔 or ランダム間隔）  |
|  マニュアルモード  |  SW1  |  表情を１つ進める  |
|  マニュアルモード  |  SW2  |  表情を１つ戻す  |
|  マニュアルモード  |  SW3  |  短押し : プリセットを１つ進める <br> 長押し : プリセットを初期値（全表情）に変更する  |
|  再生モード  |  SW1  |  再生待機の表情を１つ進める  |
|  再生モード  |  SW2  |  再生待機の表情をランダムに変更する  |
|  再生モード  |  SW3  |  短押し : 何もしない（誤操作防止） <br> 長押し : パターン再生開始  |


###### operate_emotion_by_flag

`operate_emotion_by_flag`では現在のモードや各種フラグを読み取り、最終的に`set_face`関数によって状態に応じた表情を出力します。
![emotion.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547023/ebc91c88-335a-b6f5-bf76-aca54aac7fd7.png)

この`set_face`関数に **選択中のプリセット番号** と **選択中の表情番号** を引数として渡すことで表情が切り替わる仕組みになっています。こちらも操作方法の表を見比べながらソースを見てみると動作がわかりやすいと思います。また、`set_face`関数で用いられている`presets_sizelist`や`presets`などの定義は**表情定義ファイル**`faces.h`に記載されています。これについては詳しく後述します。

```C
/* 選んだ表情に光らせる */
void set_face(int preset_no, int face_no)
{
  //対象の表情の要素数と表情配列を取得
  int size = presets_sizelist[preset_no][face_no];
  int *emotion = presets[preset_no][face_no];

  //一旦全部OFF
  set_black();
  //その後表情配列を読み該当箇所のみON
  for (int i = 0; i < size; i++)
  {
    leds[emotion[i]] = RINA_PINK;
  }
  FastLED.show();
}
```

#### 表情設定ファイル

**表情設定ファイル**`faces.h`の内容についても説明していきます。この内容は、おそらく**ポインタ**についてある程度理解していないと何を言ってるか全くわからないと思うのでな～んとなくで聞き流してください。

ファイルの内容はこんな感じになっています。

```C
#define FACES 14
#define PRESETS 4

/* 表情配列定義 */
int smile[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int evil[] = {119, 120, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 38, 31, 22, 23, 24, 25, 26, 27};
int cry1[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 95, 96, 97, 102, 103, 93, 91, 71, 37, 36, 35, 34, 33, 32};
int me[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 43, 44, 45, 46, 37, 32, 22, 27, 17, 12, 3, 4, 5, 6};
int patiri[] = {140, 134, 120, 121, 126, 127, 115, 114, 109, 108, 96, 97, 102, 103, 91, 90, 85, 84, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 21, 28, 17, 12, 3, 4, 5, 6};
int suya[] = {116, 107, 96, 97, 98, 99, 100, 101, 102, 103, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int mu[] = {140, 139, 132, 131, 121, 122, 125, 126, 112, 111, 97, 98, 101, 102, 92, 91, 84, 83, 68, 66, 53, 51, 38, 31, 22, 27, 16, 15, 14, 13};
int nikori[] = {139, 138, 133, 132, 119, 120, 121, 126, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 21, 28, 17, 12, 3, 4, 5, 6};
int cry2[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 104, 84, 82, 80, 43, 44, 45, 46, 37, 32, 22, 27, 17, 12, 3, 4, 5, 6};
int u[] = {140, 139, 133, 132, 121, 122, 126, 127, 112, 109, 108, 97, 98, 102, 103, 92, 91, 68, 66, 53, 51, 44, 45, 36, 33, 23, 26, 15, 14};
int tere[] = {140, 134, 120, 121, 126, 127, 115, 114, 109, 108, 96, 97, 102, 103, 91, 90, 85, 84, 68, 66, 53, 51, 21, 28, 17, 12, 3, 4, 5, 6};
int upset[] = {144, 145, 146, 150, 140, 136, 133, 119, 123, 126, 113, 109, 97, 102, 90, 85, 66, 63, 62, 61, 58, 57, 56, 53, 40, 44, 45, 49, 39, 30, 20, 29, 19, 15, 14, 10, 1, 2, 3, 6, 7, 8};
int wink[] = {132, 131, 121, 122, 125, 126, 114, 113, 111, 97, 98, 101, 102, 90, 89, 84, 83, 68, 66, 53, 51, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 15, 14, 13};
int pero[] = {140, 139, 132, 131, 121, 126, 113, 112, 111, 110, 93, 91, 84, 82, 63, 62, 40, 43, 39, 38, 36, 31, 30, 22, 23, 26, 27, 15, 14};

/* プリセット定義 */
int *all[] = {smile, evil, cry1, me, patiri, suya, mu, nikori, cry2, u, tere, upset, wink, pero};
int *happy[] = {smile, patiri, suya, mu, nikori, u, tere, wink};
int *clever[] = {evil, pero};
int *sad[] = {cry1, me, cry2, upset};

/* プリセット要素数定義 */
int all_sizes[] = {38, 28, 32, 36, 36, 30, 30, 44, 40, 29, 30, 42, 37, 29};
int happy_sizes[] = {38, 36, 30, 30, 44, 29, 30, 37};
int clever_sizes[] = {28, 29};
int sad_sizes[] = {32, 36, 40, 42};

/* プリセット配列定義 */
int **presets[PRESETS] = {all, happy, clever, sad};
int *presets_sizelist[PRESETS] = {all_sizes, happy_sizes, clever_sizes, sad_sizes};
int presets_sizes[PRESETS] = {FACES, 8, 2, 4};

```


##### マクロ定義

```C
#define FACES 14
#define PRESETS 4
```
まず最初にマクロで宣言している`FACES`と`PRESETS`は、登録されている**表情の総数**と**プリセットの総数**です。上記のファイルだと、14個の表情と4個のプリセットが登録されているということですね。

##### 表情配列定義

```C
/* 表情配列定義 */
int smile[] = {143, 144, 151, 152, 138, 137, 134, 133, 123, 124, 114, 113, 110, 109, 95, 96, 103, 104, 71, 73, 78, 80, 41, 42, 43, 44, 45, 46, 47, 48, 38, 31, 22, 27, 16, 13, 4, 5};
int evil[] = {119, 120, 127, 128, 116, 115, 114, 113, 110, 109, 108, 107, 96, 97, 102, 103, 68, 66, 53, 51, 38, 31, 22, 23, 24, 25, 26, 27};
int cry1[] = {139, 138, 133, 132, 119, 120, 122, 125, 126, 128, 116, 115, 114, 113, 110, 109, 108, 107, 95, 96, 97, 102, 103, 93, 91, 71, 37, 36, 35, 34, 33, 32};
以下略
```
次に**表情配列**です。これは**直列に繋げたNeoPixelLEDの何番目を光らせるか**を定義した配列です。例えば`int smile[]`は下の画像でピンク色になっている数字の配列です。
![smile.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547023/b1e41ed2-7976-e307-5599-f80e3cbaa629.png)


##### プリセット定義

```C
/* プリセット定義 */
int *all[] = {smile, evil, cry1, me, patiri, suya, mu, nikori, cry2, u, tere, upset, wink, pero};
int *happy[] = {smile, patiri, suya, mu, nikori, u, tere, wink};
int *clever[] = {evil, pero};
int *sad[] = {cry1, me, cry2, upset};
```
次に**プリセット定義**です。プリセット定義は**表情配列のポインタの配列**になっています。（出たな、ポインタ）
例えば`int *clever[]`というプリセットは、`int evil[]`と`int pero[]`という2つの表情のポインタを格納した配列、ということになります。

##### プリセット要素数定義
```C
/* プリセット要素数定義 */
int all_sizes[] = {38, 28, 32, 36, 36, 30, 30, 44, 40, 29, 30, 42, 37, 29};
int happy_sizes[] = {38, 36, 30, 30, 44, 29, 30, 37};
int clever_sizes[] = {28, 29};
int sad_sizes[] = {32, 36, 40, 42};
```
さて次は**プリセット要素数定義**です。これはプリセット定義と対応する形で、各表情配列の要素数（=光るLEDの数）を格納している配列です。
どうしてこれが必要になるのイメージがつきにくいと思いますが、これは表情配列を読み出してLEDを光らせる際、表情配列の要素数分ループを回すために用いられます。C言語では**ポインタから配列の要素数を取得することができない**ため、別途要素数の配列を保持しています。


##### プリセット配列定義
```C
/* プリセット配列定義 */
int **presets[PRESETS] = {all, happy, clever, sad};
int *presets_sizelist[PRESETS] = {all_sizes, happy_sizes, clever_sizes, sad_sizes};
int presets_sizes[PRESETS] = {FACES, 8, 2, 4};
```
次に**プリセット配列定義**です。ここでは以下の3つの配列を定義しています。
- `int **presets[PRESETS]`
これはプリセット定義配列をまとめた配列、つまり**表情配列のポインタの配列のポインタの配列**となります。ついてこれてますか？
この配列を **選択中のプリセット番号** と **選択中の表情番号** を用いて参照することで、**選択中の表情配列のポインタ**を得ることができます。

- `int *presets_sizelist[PRESETS]`
これは**プリセット要素数定義のポインタの配列**です。この配列を **選択中のプリセット番号** と **選択中の表情番号** を用いて参照することで、**選択中の表情配列の要素数**を得ることができます。

- `int presets_sizes[PRESETS]`
これは**各プリセットの表情数の配列**です。これはプリセット切り替え時にオーバーフロー/アンダーフローさせるために用います。


さて、ガーっと説明しましたが良くわからないと思います。ポインタといえばC言語初学者がぶつかる壁として有名であり、かくいう僕もなんとなくでしかポインタを理解していませんでした。今回の制作を通してあれこれ仕様を検討していくなかで **「もしかしてこれがポインタの出番か！？」** となり、初めてちゃんとポインタについて知れたような気がします。

最終的にこの構造で実装しましたがかなり複雑になってしまったので、よりよい実装方法の提案などがあればぜひお聞かせ願いたいところです。

## GoogleAppsScript

表情設定ファイルのついての説明を行いましたが、見てのとおりかなりややこしい構造です。
というわけで、`GoogleAppsScript`通称`GAS`を使って簡単に表情設定ファイルを生成できるようにします。

### 表情設定ファイル生成の流れ

表情の設定はGoogleスプレッドシートで**1シート1表情**で行います。セルの色で表情を作成し、表情名と所属プリセット名（任意）を入力します。
![faces.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547023/173b2410-a2b8-9c5b-525e-d457363afc6a.png)
表情設定の例

入力が完了したら **「生成開始」** ボタンを入力するとGAS関数が実行され、各シートの入力を集計し同シート内に結果を出力します。あとはこれをArduinoに書き込めば表情設定の更新完了です。簡単！
![gen.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547023/82e93488-5a14-ace6-8e5e-70f597c86c8d.png)


### コード

こちらはあまり具体的な説明はしません。ハードコーディングの箇所も多くあまりキレイなコードではないですがコードを貼っておくので、興味のある方は読んでみてください。
「生成開始」ボタンに割り当てているのは`gen_facesfile`関数です。

```JavaScript
function gen_facesfile() {
  var sheets = get_sheets();
  var faces = get_faces(sheets);
  var presets = get_presets(sheets, faces);

  var today = new Date();

  var date =
    Utilities.formatDate(today, "Asia/Tokyo", "yyyy/MM/dd HH:mm") + " 生成";

  var result =
    "/* " +
    date +
    " */\n\n#define FACES " +
    String(faces.name.length) +
    "\n#define PRESETS " +
    String(presets.list.length) +
    "\n\n/* 表情配列定義 */\n" +
    faces.list.join("\n") +
    "\n" +
    "\n/* プリセット定義 */\n" +
    presets.list.join("\n") +
    "\n\n/* プリセット要素数定義 */\n" +
    presets.sizes.join("\n") +
    "\n" +
    "\n/* プリセット配列定義 */\n" +
    presets.other.join("\n") +
    "\n";

  write_result(result, date);
}

function get_sheets() {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = spreadsheet.getSheets();

  return sheets;
}

function get_faces(sheets) {

  var faces_str = [];
  var faces_num = [];
  var faces_name = [];
  for (var p = 3; p < sheets.length - 1; p++) {
    var range = sheets[p].getRange("D2:W14");
    var vals = range.getValues();
    var colors = range.getBackgrounds();
    var name = sheets[p].getRange("I18").getValue();
    var face_val = [];
    for (var i = 0; i < 13; i++) {
      for (var j = 0; j < 20; j++) {
        if (colors[i][j] == "#ff00ff") {
          face_val.push(vals[i][j]);
        }
      }
    }
    var str = "int " + name + "[] = {" + face_val.join(", ") + "};";
    faces_str.push(str);
    faces_num.push(face_val.length);
    faces_name.push(name);
  }
  var faces = {};
  faces.num = faces_num;
  faces.list = faces_str;
  faces.name = faces_name;
  return faces;
}

function get_presets(sheets, faces) {

  var presets_names = [];
  var presets_num = [];

  for (var p = 3; p < sheets.length - 1; p++) {
    var range = sheets[p].getRange("I19:Q19");
    var preset = range.getValues()[0];

    for (var i = 0; i < preset.length; i++) {
      //プリセット名が初登場の場合インデックス追加
      if (preset[i]) {
        if (presets_names.indexOf(preset[i]) == -1) {
          presets_names.push(preset[i]);
          presets_num.push(1);
          //初登場でなければカウント追加
        } else {
          presets_num[presets_names.indexOf(preset[i])]++;
        }
      }
    }
  }

  var presets_list = [];
  var str = "int *all[] = {" + faces.name.join(", ") + "};";
  presets_list.push(str);

  var presets_sizes = [];
  str = "int all_sizes[] = {" + faces.num.join(", ") + "};";
  presets_sizes.push(str);

  for (var i = 0; i < presets_names.length; i++) {
    var names = [];
    var nums = [];

    for (var j = 0; j < sheets.length; j++) {
      var range = sheets[j].getRange("I19:Q19");
      var preset = range.getValues()[0];
      var name = sheets[j].getRange("I18").getValue();
      if (preset.indexOf(presets_names[i]) != -1) {
        names.push(name);
        nums.push(faces.num[faces.name.indexOf(name)]);
      }
    }

    str = "int *" + presets_names[i] + "[] = {" + names.join(", ") + "};";
    presets_list.push(str);

    str = "int " + presets_names[i] + "_sizes[] = {" + nums.join(", ") + "};";
    presets_sizes.push(str);
  }

  var presets_other = [];
  str = "int **presets[PRESETS] = {all, " + presets_names.join(", ") + "};";
  presets_other.push(str);
  var sizesstr = [];
  for (var i = 0; i < presets_names.length; i++) {
    sizesstr.push(presets_names[i] + "_sizes");
  }
  str =
    "int *presets_sizelist[PRESETS] = {all_sizes, " +
    sizesstr.join(", ") +
    "};";
  presets_other.push(str);

  str = "int presets_sizes[PRESETS] = {FACES, " + presets_num.join(", ") + "};";
  presets_other.push(str);

  var presets = {};
  presets.list = presets_list;
  presets.sizes = presets_sizes;
  presets.other = presets_other;

  return presets;
}

function write_result(str, date) {
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var sheet = spreadsheet.getSheets()[2];
  sheet.getRange("B10").setValue(str);

  sheet.getRange("C9").setValue(date);
  sheet.getRange("C9").setFontColor("red");

```

# 最後に

長々とお読みいただきありがとうございました。
というわけで、無事に璃奈ちゃんボードを完成させることができました。
出発点はキャラ愛でしたが、結果としてかなり多くのことを学ぶことができてとてもいい経験になりました。ものづくりは楽しいですね。
もしも本記事や動画が誰かの手助けになれたのであれば幸いです。
ついでに動画の方も再掲しておきます。ぜひご覧ください！
[璃奈ちゃんボードの作り方【ラブライブ！虹ヶ咲学園スクールアイドル同好会】](https://youtu.be/0yT_qYHqlOU)
<img width="400" src="http://img.youtube.com/vi/0yT_qYHqlOU/0.jpg">

かなりガーっと書いたので分かりにくい点も多々あると思いますので、ご指摘や質問などお待ちしております。それではここまでお読みいただきありがとうございました！