西條和スロット

# 作ったもの

「西」「條」「和」の3文字からスロットのように重複を許してランダムに3個選びます。
「西條和」の順で揃えばラッキー！

(「西條和」は「さいじょうなごみ」と読みます)

# 実装

## 文字画像の用意

まず、表示するための「西」「條」「和」の3文字の画像を用意しました。
さらに、スロットがぐるぐる回っている様子を表現する画像も用意しました。
各文字は16×16ピクセルの大きさにしました。

[![「西」「條」「和」「ぐるぐる」](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/89340ad6-0347-af9f-abff-86cc350432ce.png)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99656/782a594b-36d1-890c-f0d2-4bf7967e87d7.png)
(8倍に拡大して掲載、リンク先は原寸大)

## 画像データの変換

この文字の画像をIchigoJamで扱うため、数値データに変換しました。
IchigoJamには、2×2ピクセルを1文字で表せる文字`#80`～`#8F`があります。
下から1ビット目(1)が立っていると左上、2ビット目(2)が立っていると右上、
3ビット目(4)が立っていると左下、4ビット目(8)が立っていると右下が白になります。

画像データからこの文字を表す数値データに変換するため、以下の処理をしました。

1. 画像のグレーのピクセルを取り除き、白を0、黒を1で表現する。
2. 0/1が2個ずつのペアについて、それぞれ順番を入れ替え、1バイトに変換する。
3. 2行分のデータを1行にマージする。(1行のサイズは64ピクセル = 32バイトに決め打ち)
4. データをコンマ区切りの10進数に変換する。

[Comment, 21 more - CyberChef](https://gchq.github.io/CyberChef/#recipe=Comment%28%27Convert%20white-%3E0,%20black-%3E1%27%29Extract_RGBA%28%27,%27,false%29Find_/_Replace%28%7B%27option%27:%27Regex%27,%27string%27:%27%28%5B0-9%5D%2B%29,%5B0-9%5D%2B,%5B0-9%5D%2B%27%7D,%27$1%27,true,false,true,false%29Find_/_Replace%28%7B%27option%27:%27Simple%20string%27,%27string%27:%27127,%27%7D,%27%27,true,false,true,false%29Find_/_Replace%28%7B%27option%27:%27Simple%20string%27,%27string%27:%27255%27%7D,%271%27,true,false,true,false%29Find_/_Replace%28%7B%27option%27:%27Simple%20string%27,%27string%27:%27,%27%7D,%27%27,true,false,true,false%29Substitute%28%2701%27,%2710%27%29Comment%28%27swap%20bits%27%29Find_/_Replace%28%7B%27option%27:%27Regex%27,%27string%27:%27%28%5B01%5D%29%28%5B01%5D%29%27%7D,%27$2$1%27,true,false,true,false%29Comment%28%27convert%20to%20bytes%27%29Find_/_Replace%28%7B%27option%27:%27Regex%27,%27string%27:%27%28%5B01%5D%7B2%7D%29%27%7D,%27000000$1%27,true,false,true,false%29From_Binary%28%27None%27,8%29Comment%28%27merge%202%20lines%20to%201%27%29Subsection%28%27.%7B64%7D%27,true,true,false%29Register%28%27%28.%7B32%7D%29%27,true,false,true%29Drop_bytes%280,32,false%29Bit_shift_left%282%29OR%28%7B%27option%27:%27Latin1%27,%27string%27:%27$R0%27%7D%29Merge%28%29To_Decimal%28%27Comma%27,false%29Find_/_Replace%28%7B%27option%27:%27Regex%27,%27string%27:%27%28%28%5B0-9%5D%2B,%29%7B64%7D%29%27%7D,%27$1%5C%5Cn%27,true,false,true,false%29Find_/_Replace%28%7B%27option%27:%27Regex%27,%27string%27:%27,$%27%7D,%27%27,true,false,true,false%29)

## スロット部分の実装

タイトルを書き、変換した画像データをメモリに置きます。

```
10 ' ｻｲｼﾞｮｳ ﾅｺﾞﾐ ｽﾛｯﾄ (ﾑｱｯｼｭｸ)
20 POKE#700,0,0,0,0,0,0,0,0,0,0,0,0,8,0,0,0,0,0,0,8,0,0,0,0,0,4,0,0,0,0,0,0,0,12,12,12,12,12,12,0,0,8,1,8,11,3,11,1,0,8,6,1,8,12,12,4,0,5,0,0,0,10,0,0
30 POKE#740,0,0,0,5,10,0,0,0,8,11,8,0,0,9,1,0,2,1,5,0,10,0,0,5,0,5,10,0,0,10,0,4,0,7,3,7,11,3,11,0,0,10,10,0,12,1,9,4,2,11,15,3,10,0,0,5,0,5,10,0,4,10,0,5
40 POKE#780,0,13,12,1,2,12,14,0,0,10,10,0,0,10,0,0,0,5,5,9,10,0,0,5,0,5,10,0,5,10,0,5,0,5,0,0,0,0,10,0,0,10,10,0,3,15,7,1,10,0,5,0,10,0,0,5,0,5,10,0,5,2,0,5
50 POKE#7C0,0,13,12,12,12,12,14,0,0,10,0,0,6,10,2,4,0,0,5,0,10,12,12,5,0,1,0,0,5,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0
```

変数`X`(0～2)で指定した場所に画像を描画します。
画像の大きさは16×16ピクセルで、1文字で2×2ピクセルを表すので、8×8文字となります。
変数`M`が1の時はぐるぐるを描画し、0のときは「西」「條」「和」のどれかをランダムで描画します。

```
120 IFMC=3ELSEC=RND(3)
130 FORI=0TO7:LOCATE3+9*X,8+I:FORJ=0TO7:?CHR$(PEEK(#700+32*I+8*C+J)+#80);:NEXT:NEXT:LOCATE0,17:RETURN
```

画面をクリアし、リールの初期値としてランダムな文字を描画(`M=0`)します。
さらに、スロットを止めるためのキー(Z、X、C)の番号を配列に置きます。

```
60 CLS
70 M=0:FORX=0TO2:GOSUB120:NEXT:LET[0],90,88,67
```

Enterキーが押されるまで待機します。
Enterキーが押されたら、止めたリールの情報(変数`S`)を初期化し、ぐるぐるを描画(`M=1`)します。

```
80 IFINKEY()<>10GOTO 80
90 M=1:S=0:FORX=0TO2:GOSUB120:NEXT:M=0
```

まだ止めていないリールに割り当てられたキーが押されたら、リールを止め、ランダムな文字を描画します。
3個のリール全てを止めたら、Enterキーの待機に戻ります。

```
100 K=INKEY()&#DF:FORX=0TO2:V=1<<X:IFK=[X]AND!(S&V)GOSUB120:S=S|V
110 NEXT:IFS=7GOTO80ELSEGOTO100
```

## 画像データの圧縮

リールの枠をつけようとしたところ、プログラムの容量が足りなくなってしまいました。
そこで、画像データを圧縮することにしました。
今回は、deflateの簡易版を用いることにしました。
deflateは[RFC1951](https://datatracker.ietf.org/doc/html/rfc1951)で定義されており、

* 参照する位置と長さを指定してのデータの再利用
* ハフマン符号化

を用いてデータの圧縮を行う方法です。
今回は、このうち「データの再利用」のみを活用しました。

まず、画像データに対し通常のdeflateを行いました。

[From Decimal, Raw Deflate, Escape string - CyberChef](https://gchq.github.io/CyberChef/#recipe=From_Decimal%28%27Comma%27,false%29Raw_Deflate%28%27Fixed%20Huffman%20Coding%27%29Escape_string%28%27Special%20chars%27,%27Single%27,false,true,false%29&input=MCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsOCwwLDAsMCwwLDAsMCw4LDAsMCwwLDAsMCw0LDAsMCwwLDAsMCwwLDAsMTIsMTIsMTIsMTIsMTIsMTIsMCwwLDgsMSw4LDExLDMsMTEsMSwwLDgsNiwxLDgsMTIsMTIsNCwwLDUsMCwwLDAsMTAsMCwwLAowLDAsMCw1LDEwLDAsMCwwLDgsMTEsOCwwLDAsOSwxLDAsMiwxLDUsMCwxMCwwLDAsNSwwLDUsMTAsMCwwLDEwLDAsNCwwLDcsMyw3LDExLDMsMTEsMCwwLDEwLDEwLDAsMTIsMSw5LDQsMiwxMSwxNSwzLDEwLDAsMCw1LDAsNSwxMCwwLDQsMTAsMCw1LAowLDEzLDEyLDEsMiwxMiwxNCwwLDAsMTAsMTAsMCwwLDEwLDAsMCwwLDUsNSw5LDEwLDAsMCw1LDAsNSwxMCwwLDUsMTAsMCw1LDAsNSwwLDAsMCwwLDEwLDAsMCwxMCwxMCwwLDMsMTUsNywxLDEwLDAsNSwwLDEwLDAsMCw1LDAsNSwxMCwwLDUsMiwwLDUsCjAsMTMsMTIsMTIsMTIsMTIsMTQsMCwwLDEwLDAsMCw2LDEwLDIsNCwwLDAsNSwwLDEwLDEyLDEyLDUsMCwxLDAsMCw1LDAsMCwxLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMiwwLDAsMCwwLDEsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMAo)

この結果のハフマン符号を自作プログラム(未公開)でデコードし、データを取り出しました。
取り出したデータは、単独の数値で表したデータ(literal)を結果に加える指示と、
(長さ,位置)の数値の組で表したデータを再利用する指示からなります。

これを、以下のプログラム(Python)で変換し、IchigoJamのプログラムに埋め込める形にしました。
今回扱うデータは0～15の数値なので、16以上の数値に役割をもたせることができます。
16は圧縮データの終了を、17以上はデータを再利用する指示を表すことにしました。

```py
deflated = [0, (11, 1), 8, (12, 7), 4, (7, 21), 12, (5, 1), (3, 22), 1, 8, 11, 3, 11, 1, 0, 8, 6, 1, 8, 12, 12, 4, 0, 5, (3, 28), 10, (5, 34), 5, (4, 7), 8, 11, (3, 55), 9, 1, 0, 2, 1, 5, (4, 23), 5, (5, 22), 10, (3, 70), 7, 3, 7, (3, 56), (3, 12), 10, 0, 12, 1, 9, 4, 2, 11, 15, 3, (8, 32), 4, 10, (3, 8), 13, 12, 1, 2, 12, 14, (5, 32), (5, 80), 5, 5, 9, (8, 32), (4, 3), (5, 104), (4, 25), (3, 32), 3, 15, 7, 1, (4, 19), (9, 32), 2, (5, 64), (3, 1), (4, 64), 0, 0, 6, 10, 2, (3, 182), (3, 32), 12, 12, 5, 0, 1, (4, 10), (4, 6), (11, 1), 2, (4, 5), (14, 19)]

result = []

for d in deflated:
	if type(d) == tuple:
		result.append(d[0] + 16)
		result.append(d[1])
	else:
		result.append(d)

print("len(result) = " + str(len(result)))
print(",".join(map(str, result)))
```

圧縮したデータは配列の領域に埋め込むことにしたので、
IchigoJamのプログラムで埋め込んだ配列の値(1個の数値が2バイトを表す)を出力させ、それを埋め込みに利用することで、
1バイトずつ`POKE`するよりもさらに短いプログラムで埋め込むことができました。

## 圧縮した画像データの展開

圧縮データを配列に置き、圧縮データを読み込む位置`I`と展開したデータを書き込む位置`O`を初期化します。

```
20 LET[0],6912,2049,1820,5892,3093,277,5651,2049,779,267,2048,262,3080,1036,1280,7187,5386,1314,1812,2824,14099,265,512,1281,5908,5381,2582,17939,775,4871,4920,2572,3072,2305,516,3851,6147,1056,4874
30 LET[39],3336,268,3074,5390,5408,1360,2309,8216,788,26645,6420,8211,3843,263,4884,8217,5378,4928,5121,64,1536,522,#B613,8211,3084,5,5121,5130,6918,513,1300,4894,16:I=#800:O=#700
```

圧縮データから1バイト読み込みます。
読み込んだのが16だった場合、展開処理を終了します。
読み込んだのが16未満の数だった場合、その値を展開したデータとして書き込みます。

```
40 C=PEEK(I):I=I+1:IFC=16GOTO60ELSEIFC<16:POKEO,C:O=O+1:GOTO40
```

読み込んだのが16を超える数だった場合、その数から16を引いた値を再利用する長さとします。
そして、圧縮データからもう1バイト読み込み、展開したデータのうちその数だけ前のデータを展開したデータに加えます。

```
50 D=PEEK(I):I=I+1:FORT=17TOC:POKEO,PEEK(O-D):O=O+1:NEXT:GOTO40
```

# 完成

```
10 ' ｻｲｼﾞｮｳ ﾅｺﾞﾐ ｽﾛｯﾄ
20 LET[0],6912,2049,1820,5892,3093,277,5651,2049,779,267,2048,262,3080,1036,1280,7187,5386,1314,1812,2824,14099,265,512,1281,5908,5381,2582,17939,775,4871,4920,2572,3072,2305,516,3851,6147,1056,4874
30 LET[39],3336,268,3074,5390,5408,1360,2309,8216,788,26645,6420,8211,3843,263,4884,8217,5378,4928,5121,64,1536,522,#B613,8211,3084,5,5121,5130,6918,513,1300,4894,16:I=#800:O=#700
40 C=PEEK(I):I=I+1:IFC=16GOTO60ELSEIFC<16:POKEO,C:O=O+1:GOTO40
50 D=PEEK(I):I=I+1:FORT=17TOC:POKEO,PEEK(O-D):O=O+1:NEXT:GOTO40
60 CLS:LOCATE2,7:FORI=2TO29:?CHR$(#8F);:NEXT:LOCATE2,16:FORI=2TO29:?CHR$(#8F);:NEXT:FORI=8TO15:FORJ=0TO3:LOCATE2+9*J,I:?CHR$(#8F);:NEXT:NEXT
70 M=0:FORX=0TO2:GOSUB120:NEXT:LET[0],90,88,67
80 IFINKEY()<>10GOTO80
90 M=1:S=0:FORX=0TO2:GOSUB120:NEXT:M=0
100 K=INKEY()&#DF:FORX=0TO2:V=1<<X:IFK=[X]AND!(S&V)GOSUB120:S=S|V
110 NEXT:IFS=7GOTO80ELSEGOTO100
120 IFMC=3ELSEC=RND(3)
130 FORI=0TO7:LOCATE3+9*X,8+I:FORJ=0TO7:?CHR$(PEEK(#700+32*I+8*C+J)+#80);:NEXT:NEXT:LOCATE0,18:RETURN
```

3個のリールが止まっている状態のとき、Enterキーを押すとリールの回転を開始します。
リールが回転している時、左のリールはZキー、中央のリールはXキー、右のリールはCキーで止めることができます。

<iframe width="560" height="315" src="https://www.youtube.com/embed/yoQeeKjPUGU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

# ところで西條和って？

デジタル声優アイドル [22/7(ナナブンノニジュウニ)](https://nanabunnonijyuuni.com/) のメンバーです。

↓ ラジオ番組をやっています。第1・第3日曜日更新、最新回は無料(有料会員限定パートあり)。
[西條和のねむねむたいむ | アニたまどっとコム](http://anitama.com/snzzz/)

↓ アニメ化作品です。西條和さんは滝川みう役です。
[22/7　#01　さよなら、私のささやかな世界 - ニコニコ動画](https://www.nicovideo.jp/watch/1578630243)

↓ ライブ配信をするかも。
[西條和　22/7(ナナブンノニジュウニ) - SHOWROOM(ショールーム)](https://www.showroom-live.com/digital_idol_22)

# ちなみに

今日は12/7…22/7と1文字違いです！

# おわりに

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