Node-RED パズルで遊んでフローに親しんでもらいたい

Node-RED の最大の特徴は、**フロー** だと思います。画面上でノードを線(フロー)で繋ぐだけで、理解し易い表現を維持しつつ、いろいろなロジックを実現できる素晴らしい機能ですよね。

今回は Node-RED の編集画面をうまく使って、初心者に **パズルゲーム** を解く感覚で、フローに親しんでもらおう！という試みです。具体的には、以下のようなパズルを作成し、線を繋ぐことでゴールまで進んでもらいます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/bc443483-3174-df90-d408-80a97c20a665.png)

# まずは問題の準備

まずは簡単な問題を試してみましょう！以下の JSON データをクリップボードにコピーして、

```json
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 4","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています！\"};\n\nif (msg.payload === 4) {\n    status.text = \"正解です！\";\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その1","info":"","x":100,"y":40,"wires":[]},{"id":"20d6a96f.a026d6","type":"function","z":"5bd94f8d.06751","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 1;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"53adc9a9.1c8598","type":"function","z":"5bd94f8d.06751","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload *= 2;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":180,"wires":[[]]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"スタートからゴールまで線を繋ぎましょう！","info":"","x":530,"y":40,"wires":[]}]
```

メニューから「読み込み」にある「クリップボード」を選択し、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/af37f3b0-9b6d-dd2e-7bce-bd2a58e554ec.png)
表示された読み込みダイアログのテキストエリアに JSON データをペーストし、「新規のタブ」に「読み込み」を実施します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/b1931045-8a6c-9a17-243f-9f395cc37471.png)
読み込まれたフローを配置すると以下のようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/bc443483-3174-df90-d408-80a97c20a665.png)
これで問題の準備は完了です！

左にある「スタート」の数値が **1** で、右にあるゴールの数値が **4** です。スタートからゴールまで正しく線(フロー)を繋ぐことで、ゴールに届く数値を **4** にするのがこのパズルの目的です。

# 問題を解いてみましょう

対象は初心者ですから、間違うこともあります。まずは以下のように線(フロー)を繋いだとします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/e4db618d-82b9-8255-4830-15e85ba163a7.png)
フローを「デプロイ」した後、「スタート」と記載されている Inject ノードの左にあるボタンをクリックしましょう。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/0f4d751a-620f-c7f9-8a03-0818c1aa0b20.png)
すると「スタート」から数値 **1** が送信され、フローが動作します。

各計算ノードの下のステータス領域には計算の結果が表示され、最後の「ゴール」ノードの下にはパズルの結果が表示されます。今回は赤のアイコンと共に「間違っています！」と表示されていますので、繋ぎ方が正しくなかったようですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/b675f94e-1f72-f1be-4d6a-b66f1124a2c9.png)
では、線(フロー)を繋ぎなおして、もう一度「デプロイ」してみましょう。今回は以下のように繋いでみました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/bd749109-ae43-bfa6-2fd8-e0b0305015c3.png)
「スタート」と記載されている Inject ノードの左にあるボタンをクリックして、再びフローを実行してみましょう！

今回は「ゴール」に緑のアイコンが表示され「正解です！」と表示されました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/321cf529-e07f-76f4-aa88-54d3d1a2371c.png)
正しいフローを実行できたので、最初のパズルは無事にクリアです！おめでとうございます。

# 今回のパズルの仕組み

仕組み、と言ってもたいしたことはありません。Node-RED の基本機能のうち、ノードエディタで表示できる「ステータス」機能を利用してフローの様子を視覚的に見せているのがポイントです。

## スタート用ノード

スタート用ノードは普通に inject ノードを使っているだけです。ペイロードで指定した値を、ちゃんと名前のところにも設定してあげる、ぐらいしか注意点はありません。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/0c5dead6-e124-5cad-4ccc-a8c2d27f60ca.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/061b9bea-0183-a526-7570-ed551cd170b5.png)

## 計算用ノード

計算用ノードは function ノードで、こちらは名前と、コードの赤枠の部分の処理をあわせているのがポイントです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/993dffe3-5f4b-8e68-3389-b08ea3cda728.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/62471c99-e91b-4065-f7b4-816d03ccc6fb.png)
コードは以下のように記載しています。

```javascript
var status = {fill:"red",shape:"dot",text:""};

if (typeof msg.payload === "number") {
    status.text = msg.payload + " -> ";
    // ----------
    msg.payload += 1;
    // ----------
    status.text += msg.payload;
    status.fill = "green";
}

node.status(status);
return msg;
```

`// ----------` のコメントで挟まれた部分が、実際の計算部分で、計算内容によって変わります。

`status` オブジェクトが処理の中心で、ステータス表示の内容をこれで管理しています。処理の最後で `node.status(status)` と画面に表示させているのがわかります。

今回は「計算」ノードなので、`if` 文の中身は `typeof msg.payload === "number"` としています。フローで渡された値が数値でなければ、赤いエラー表示になるわけです。

例えば `+ " !"` という処理をする「文字列処理」ノードであれば、`if` 文の中身は `typeof msg.payload === "string"` にすべきですね。

## 分岐ノード

分岐ノードは switch ノード、ほぼそのままです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/c44e3059-eb4c-fd85-2ed5-6468954c91b9.png)
名前をパズルっぽく表現した、のがポイントでしょうか。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/ade5dd3a-c8ab-27f8-8411-089879ece791.png)
地味ですが、一応は出力にもラベルを設定しています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/9a9fd9eb-1288-7903-1567-4b6f28bcc0cb.png)
ステータスを出したいので同種の機能を function ノードで作成しようか、とも思ったのですが。Node-RED のフローに慣れてもらうのが本来の目的なので、あえて switch ノードそのままで利用してみました。

なお以下の奇数/偶数の実装方法に少し困りまして、
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/fb5dd3c1-6651-3acf-8a21-2e6da1cbec7f.png)
以下のように [JSONata](https://qiita.com/yamachan360/items/91805334890d18ef6e23) で条件を書いてみました。やっぱり便利ですね。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/ba79f69f-74ab-dc20-d025-3d960dd68da5.png)

## ゴール用ノード

ゴール用ノードも計算用ノードと似ており、やはり Function ノードを使用しています。アイコンは変えています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/de108ddb-098c-ef83-026a-35ff17375f4c.png)
指定したコードも、計算ノードを少しシンプルにしたようなコードになっています。`if` 文の中にこのパズルの答え合わせがあることに注意してください。

```javascript
var status = {fill:"red",shape:"dot",text:"間違っています！"};

if (msg.payload === 4) {
    status.text = "正解です！";
    status.fill = "green";
}

node.status(status);
return msg;
```

# 追加の問題

## フロー課題 その2

では、計算ノードを増やした第2問にもチャレンジしてみてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/105dbe46-5b54-a5e5-2495-c4cd9185aa4f.png)
問題の JSON データは以下になります。

```json
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 5","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています！\"};\n\nif (msg.payload === 5) {\n    status.text = \"正解です！\";\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その2","info":"","x":100,"y":40,"wires":[]},{"id":"20d6a96f.a026d6","type":"function","z":"5bd94f8d.06751","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 1;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"53adc9a9.1c8598","type":"function","z":"5bd94f8d.06751","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload *= 2;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":180,"wires":[[]]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"スタートからゴールまで線を繋ぎましょう！","info":"","x":530,"y":40,"wires":[]},{"id":"ada6b121.9d565","type":"function","z":"5bd94f8d.06751","name":"+ 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 2;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":260,"wires":[[]]}]
```
## フロー課題 その3

では、分岐ノードが追加された第3問にもチャレンジしてみてください。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/9935a035-2f88-aacb-c29a-aad4fbe88505.png)

```json
[{"id":"230becfa.e30cb4","type":"inject","z":"8d4f0d2b.2e2fa","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":150,"y":140,"wires":[[]]},{"id":"12b0e3dc.6bd48c","type":"function","z":"8d4f0d2b.2e2fa","name":"ゴール: 10","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています！\"};\n\nif (msg.payload === 10) {\n    status.text = \"正解です！\";\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":690,"y":140,"wires":[[]],"icon":"node-red/alert.png"},{"id":"d23c40d5.6bf8a","type":"comment","z":"8d4f0d2b.2e2fa","name":"フロー課題 その3","info":"","x":140,"y":80,"wires":[]},{"id":"e3990a0.94dbbf8","type":"function","z":"8d4f0d2b.2e2fa","name":"+ 1","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 1;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":390,"y":140,"wires":[[]]},{"id":"f8325a77.8a6808","type":"comment","z":"8d4f0d2b.2e2fa","name":"数の大きさによって出口が違うノードが登場！","info":"","x":580,"y":80,"wires":[]},{"id":"707287ad.b54308","type":"switch","z":"8d4f0d2b.2e2fa","name":"9以下↓ / 10以上↑","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"10","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":410,"y":240,"wires":[[],[]],"outputLabels":["10以上","9以下"]}]
```

## フロー課題 その4

第4問は第3問の発展形です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/828667a1-c5f8-dd9e-4521-0f94ad31ac6b.png)

```json
[{"id":"75687ea7.6aaf7","type":"inject","z":"8d4f0d2b.2e2fa","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":460,"wires":[[]]},{"id":"cd891ad4.6c6138","type":"function","z":"8d4f0d2b.2e2fa","name":"ゴール: 16","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています！\"};\n\nif (msg.payload === 16) {\n    status.text = \"正解です！\";\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":710,"y":460,"wires":[[]],"icon":"node-red/alert.png"},{"id":"d21de8fe.44ce78","type":"comment","z":"8d4f0d2b.2e2fa","name":"フロー課題 その4","info":"","x":160,"y":400,"wires":[]},{"id":"877ce50a.1cf9f8","type":"function","z":"8d4f0d2b.2e2fa","name":"x 2","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload *= 2;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":460,"wires":[[]]},{"id":"cb5bb9fd.6ef7d8","type":"comment","z":"8d4f0d2b.2e2fa","name":"順番に注意して！","info":"","x":690,"y":400,"wires":[]},{"id":"b31e1ac2.a0a5d8","type":"function","z":"8d4f0d2b.2e2fa","name":"+ 3","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 3;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":410,"y":540,"wires":[[]]},{"id":"d9c784b1.02e448","type":"switch","z":"8d4f0d2b.2e2fa","name":"9以下↓ / 10以上↑","property":"payload","propertyType":"msg","rules":[{"t":"gte","v":"10","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":430,"y":620,"wires":[[],[]],"outputLabels":["10以上","9以下"]}]
```

## フロー課題 その5

第5問は奇数/偶数の分岐ノードを使用してみました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/9722f90b-eca8-83d5-96ab-6ea1a500d045.png)

```json
[{"id":"f30474b6.061cb8","type":"inject","z":"5bd94f8d.06751","name":"スタート: 1","topic":"","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":100,"wires":[[]]},{"id":"6ae4cfc9.5241f","type":"function","z":"5bd94f8d.06751","name":"ゴール: 7","func":"var status = {fill:\"red\",shape:\"dot\",text:\"間違っています！\"};\n\nif (msg.payload === 7) {\n    status.text = \"正解です！\";\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":640,"y":100,"wires":[[]],"icon":"node-red/alert.png"},{"id":"2acb2b87.1fd974","type":"comment","z":"5bd94f8d.06751","name":"フロー課題 その5","info":"","x":100,"y":40,"wires":[]},{"id":"341d3ff3.a56ba","type":"comment","z":"5bd94f8d.06751","name":"今度は奇数と分数で分岐します！","info":"","x":590,"y":40,"wires":[]},{"id":"35511ba2.8b9ac4","type":"function","z":"5bd94f8d.06751","name":"+ 3","func":"var status = {fill:\"red\",shape:\"dot\",text:\"\"};\n\nif (typeof msg.payload === \"number\") {\n    status.text = msg.payload + \" -> \";\n    // ----------\n    msg.payload += 3;\n    // ----------\n    status.text += msg.payload;\n    status.fill = \"green\";\n}\n\nnode.status(status);\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":100,"wires":[[]]},{"id":"cd3e7fc8.a39dc","type":"switch","z":"5bd94f8d.06751","name":"奇数↑ / 偶数↓","property":"$number(payload) % 2","propertyType":"jsonata","rules":[{"t":"eq","v":"1","vt":"num"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":350,"y":180,"wires":[[],[]],"outputLabels":["奇数","偶数"]}]
```

# 追加の問題の回答集

このページを読んでいる皆さんには簡単すぎる問題だとは思いますが、一応、それぞれの回答例も記載しておきます。

## フロー課題 その2 (回答例)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/34a9b679-6b06-9e74-7bc7-01e89bdcf496.png)

## フロー課題 その3 (回答例)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/8dae9186-d43e-1e61-59d4-7ad204fd07e4.png)


## フロー課題 その4 (回答例)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/34974905-444e-35e8-b868-3755d1f47135.png)

## フロー課題 その5 (回答例)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/193519/8c7d4518-03db-2e49-d4df-0dabfb949465.png)


# というわけで

Node-RED 編集画面を使ったフローのパズル問題を幾つか作成して、ご紹介しました。Node-RED の初心者向け講習会などで、最初に頭の体操として解いてもらう、プログラミング初心者の方にまずはパズル感覚で親しんでもらう、などいくつか活用方法が考えられます。

皆さんもこのような仕組みを使って、何か面白いパズルを作ってみませんか？

それではまた！