【IchigoJam】小型I2C LCDに表示する ロト6ランダム選択機 (ポータブル)

昨年、[IchigoJam](https://ichigojam.net/) BASIC でポータブルを目指した角度計を作成した。

[IchigoDake + DakeCap で角度計を作ろうとしたが、惜しくも失敗した #BASIC - Qiita](https://qiita.com/mikecat_mixc/items/0f6bf7f9f008da319f6a)

今回は、このときのハードウェアを流用し、ポータブルになるはずのロト6ランダム選択機を作成した。

:::note info
今回のプログラムは、単に数字を**ランダムに選択**するだけである。
**予想はしない**ので、「予想機」ではない。
:::

~~というか、あの後 IchigoDake で使えるようにするためにロープロファイルのピンヘッダで接続できるプリント基板を設計したはずなんだ。この愚か者、まだ組み立ててないんだ。愚かだね。~~

※IchigoJamはjig.jpの登録商標です。

# ロト6

[ロト6（LOTO6）のご案内｜宝くじ商品のご案内【宝くじ公式サイト】](https://www.takarakuji-official.jp/kuji/loto/loto6/)

ロト6は、1～43の数字のうち6個の**組み合わせ** (すなわち、順序は気にしない**集合**であり、同じ数字を複数選んではいけない) を選んで購入し、その組み合わせが抽せんされる当たりの数字の組み合わせと一致すれば当たりとなる宝くじである。

2桁までの数字を6個選ぶので、数字の間に空白を1個ずつ入れると、8×2文字を表示できる**液晶ディスプレイにちょうど収まる。**

# 数字の組み合わせをランダムに選択する方法

今回のプログラムでは、以下の方法で数字の組み合わせをランダムに選択する。

まず、配列に選ぶ数字の候補となる1～43の数字を1個ずつ格納する。
最初は、配列の中の全ての要素が、まだ選んでいない候補である。
この候補の中からランダムに1個要素を選び、先頭の要素と入れ替える。
(先頭の要素を選んだ場合は、何もしない)

![全体からランダムに要素を選んで入れ替える](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/a5c48f76-7282-fb01-d23e-afb384a4b436.png)

すると、先頭の要素は「選んだ要素」になる。
残りの要素からまたランダムに選び、2番目の要素と入れ替える。

![2番目以降からランダムに要素を選んで入れ替える](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/0ae4f739-8463-f619-c6eb-516b7d55e14b.png)

2番目までの要素が「選んだ要素」になる。

![2番目まで選んだ状態](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/39a56913-7517-e70d-6bfa-4b9c2079e328.png)

これを6番目まで繰り返すことで、候補の中から6個の数字の組み合わせをランダムに選択できる。

# プログラム

このプログラムを本体の `0` に保存しておくことで、スリープからの復帰時に自動で実行してもらうことができ、スリープ機能を有効活用することができる。

```text
10 ' ﾛﾄ6 ﾗﾝﾀﾞﾑ ﾎﾟｰﾀﾌﾞﾙ
20 VIDEO0,1:P=90:A=#3E:C=32
30 LET[80],#8080,#40,#C080,#40,#39,#14,#70|(C&#F),#54|((C>>4)&3),#6C,#38,#01,#0C,#08,#39,#64,#50,#39:FORI=84TO91:X=I2CW(A,#803,1,#800+I*2,1):WAIT1+14*(I=5):NEXT
40 POKE#878,#2D,#2D,#20,#2D,#2D,#20,#2D,#2D:X=I2CW(A,#8A0,3,#878,8):X=I2CW(A,#8A4,3,#878,8):VIDEO0,P
50 IF BTN() GOTO 50
60 IF !BTN() GOTO 60
70 VIDEO0,1:CLT:FORI=0TO42:[I]=I+1:NEXT:FORI=0TO5:S=RND(43-I):T=[I]:[I]=[I+S]:[I+S]=T:NEXT
80 FORI=4TO0STEP-1:FORJ=0TOI:IF[J]>[J+1]T=[J]:[J]=[J+1]:[J+1]=T
90 NEXT:NEXT
100 FORI=1TO15
110 FORJ=0TO5:IFI>J+9 [50+J]=[J] ELSE [50+J]=RND(100)
120 NEXT:POKE#878,[50]/10+#30,[50]%10+#30,#20,[51]/10+#30,[51]%10+#30,#20,[52]/10+#30,[52]%10+#30,[53]/10+#30,[53]%10+#30,#20,[54]/10+#30,[54]%10+#30,#20,[55]/10+#30,[55]%10+#30
130 X=I2CW(A,#8A0,3,#878,8):X=I2CW(A,#8A4,3,#880,8):WAIT6:NEXT
140 IF !BTN() VIDEO0,P:GOTO 60
150 IF TICK()<300 GOTO 140
160 FORI=92TO96:X=I2CW(A,#803,1,#800+I*2,1):WAIT1+14*(I=5):NEXT:VIDEO0,P
170 IF BTN() GOTO 170
180 VIDEO0,1:SLEEP:GOTO20
```

* 10行目：`FILES` 対応のタイトル
* 20行目：パラメータの設定
  * `P`：節電モード時、クロックの周期を何倍に設定するか (大きくしすぎると効かなくなることがあった)
  * `A`：LCDのI2C 7ビットアドレス
  * `C`：LCDのコントラスト
* 30行目：配列にLCD用のコマンドを置き、LCDに初期化コマンドを送信する
* 40行目：LCDの初期表示内容を設定し、節電モードに切り替える
* 50～60行目：ボタンが離され、また押されるまで待機する
* 70行目：通常モードに切り替え、数字をランダムに選択する
* 80～90行目：選択した数字をソート (バブルソート) する
* 100～130行目：選択・ソートした数字を表示する
  * 最初は、演出のためランダムな数字を表示する
  * 最終的に、前から順番に選択した数字を表示するように切り替えていく
* 140～150行目：ボタンが離されるか5秒経つまで待機する
  * ボタンが離されたら、節電モードに切り替え、次にボタンが押されるまで待機する処理に飛ぶ
  * ボタンが離されずに5秒経ったら、次の処理に行く
* 160行目：LCDに停止コマンドを送信する
* 170行目：ボタンが離されるまで待機する
* 180行目：スリープモードに入る

# 操作方法

* ボタンを押す：数字をランダムに選択する
* ボタンをそのまま5秒押し続ける：スリープモードに入る

# 動作例

<iframe width="560" height="315" src="https://www.youtube.com/embed/t0_7pCBDZtc?si=4Ar8mFNX1iMmZvTA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

一見きれいに動いている。
しかし、よく見ると**致命的な問題**が。
なんと、最初に選んだ5組と、スリープからの復帰後選んだ5組が、**完全に一致している**のである。
これでは役に立たないだろう。

通常は、起動後同じ引数で `RND()` を実行しても、違う値が返ってくる。
しかし、これは**インタラクティブモード時**に何らかの仕組みで乱数の状態を変えているためであると考えられる。
そして、今回のように電源投入・スリープ復帰時からいきなりプログラムを実行してしまうと、この仕組みが発動せず、乱数の状態が同じになってしまうと考えられる。

起動してからの時間・アナログ入力・ガイガーカウンターなど、選ばれる数字を変える方法はいくつか考えられるだろう。
角度計のハードウェアを流用しているので、加速度計を活用する方法もあるかもしれない。
これらを組み込むのは、読者への宿題とする。~~(手抜き)~~

# まとめ

* 1～43の数字からランダムな6個の組み合わせを選んで、ソートして小型I2C LCD AQM0802に表示するプログラムを作成した
* スリープモードにする機能も用意した
* よく見ると、起動してから選ばれる組み合わせが毎回同じ…!?