はじめに
みなさんは、学ランを着ようかセーラー服を着ようか迷ったことはあるだろうか?
きっとある人も、ない人も居るだろう。
そんな状況でも、それ以外の状況でも、学ランかセーラー服かをランダムに決めてくれるルーレットを IchigoJam で作ってみた。
※IchigoJamはjig.jpの登録商標です。
画像の圧縮
今回は、64×48ピクセルの画像データを2枚用意し、画面上に交互に表示したい。
1ピクセルを1ビットで表すとこれは1枚あたり3072ビットとなり、1文字で6ビットを表すと512文字になる。
これでは、2枚で1024文字となり、これだけで IchigoJam のプログラム領域を埋め尽くしてしまう。
そこで、画像データを圧縮して格納することにした。
今回用意した画像データは背景色の部分が多いため、背景色のみをランレングス圧縮すればよい。
これを踏まえて、今回は以下のような形式のエンコードを採用した。
- 画面上の1文字 (田の形の4ピクセル) を4ビットで表す
- 背景色でないピクセルがある文字は、3文字 (12ビット) を2文字にエンコードする
- 文字コードの下位6ビットを用いて、1文字目の上位ビットから2文字目の下位ビットに向かって4ビット×3を格納する
- 背景色のみのピクセルの2個以下の連続も、この形式で表す
- 背景色のピクセルのみの文字が3個以上連続する場合は、連続する個数をエンコードする
- 15個以下の場合は、連続する個数に
#20
を足した文字コードの文字で表す - 16個以上94個以下の場合は、
!
に続いて連続する個数に#20
を足した文字コードの文字を置いて表す
- 15個以下の場合は、連続する個数に
以下の Python プログラムは、画像ファイルとエンコードする部分を受け取り、この形式でエンコードした文字列を出力する。
img_assyuku.py
# coding: utf-8
import sys
from PIL import Image
if len(sys.argv) != 2 and len(sys.argv) != 6:
sys.stderr.write("Usage: img_assyuku.py input_file [sx sy width height]\n")
sys.exit(1)
img = Image.open(sys.argv[1]).convert("L")
(img_width, img_height) = img.size
if len(sys.argv) == 6:
sx = int(sys.argv[2])
sy = int(sys.argv[3])
width = int(sys.argv[4])
height = int(sys.argv[5])
else:
sx = 0
sy = 0
width = img_width
height = img_height
if width % 2 != 0 or height % 2 != 0:
sys.stderr.write("please use even width and height")
sys.exit(1)
if sx < 0 or sy < 0 or sx + width > img_width or sy + height > img_height:
sys.stderr.write("cropping rectangle out of range")
sys.exit(1)
data = []
for y in range(sy, sy + height, 2):
for x in range(sx, sx + width, 2):
value = 0
if img.getpixel((x, y)) <= 128: value += 1
if img.getpixel((x + 1, y)) <= 128: value += 2
if img.getpixel((x, y + 1)) <= 128: value += 4
if img.getpixel((x + 1, y + 1)) <= 128: value += 8
data.append(value)
char_table = "".join(map(chr, range(0x40, 0x70))) + "".join(map(chr, range(0x30, 0x40)))
result = ""
i = 0
data_len = len(data)
while i < data_len:
if i + 3 > data_len:
sys.stderr.write("Oops! this image is not supported in this version!\n")
sys.stderr.write("partial result:\n")
sys.stderr.write(result)
sys.stderr.write("\n")
sys.exit(1)
if sum(data[i:i+3]) == 0:
zero_end = i + 3
while zero_end < data_len:
if data[zero_end] != 0: break
zero_end += 1
zero_combo = zero_end - i
if zero_combo > 0x5e: zero_combo = 0x5e
if zero_combo > 0xf: result += "!"
result += chr(zero_combo + 0x20)
i += zero_combo
else:
result += char_table[(data[i] << 2) | (data[i + 1] >> 2)]
result += char_table[((data[i + 1] & 3) << 4) | data[i + 2]]
i += 3
print(result)
画像の展開
上で用意したプログラムで画像ファイルをエンコードした文字列を埋め込み、さらにデコーダを実装した。
デコードは、以下のようにエンコードの逆を行い、VRAMにグラフィック文字を書き込む。
- 文字
!
があったら、その次の文字の文字コードから#20
を引いた個数の背景色 (文字コード#80
) を書き込む - それ以外で文字コード
#30
未満の文字があったら、その文字の文字コードから#20
を引いた個数の背景色 (文字コード#80
) を書き込む - それ以外の文字があったら、その次の文字とのペアから4ビットの数値3個を取り出し、対応するグラフィック文字を書き込む
10 ' ガクラン セーラーフク デモ
20 G="!G3D!<i@8@!9cbNl!8`1%NP!5h@@A#h@!4hD@D@Dh@@DQ@cDA@(hE%V`@Jo;CL0BB@&hE@A@Eh@BL;@@F#X@%hE@D@Eh@@HT@a`CF(hE%V`!5hE@A@Eh@!4kE%7`!77L3LT@!8T@AP!:T@AP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:L3LP!T"
30 S="!~'acL<!8`5I@XGd@!4hBRXE`h@!4hDKMXDh@B@@H0@CL0@%hEBM@Eh@Bk`H2@CUH@%hEBo@Eh@Bb@0hCC_l@%hE@A@Eh@@c@BD@b]d@%hM3L3Eh@!4kEhEhM8@!4kEhEhM8@!6V`V`T@!6`ZAZBP!7hJAZB`!7hJAZB`!7H3L3L0!~!~!7"
40 C=0
50 A=#900:IF C P=S ELSE P=G
60 D=PEEK(P):IFD=#22GOTO100
70 IF D=#21:E=PEEK(P+1):FORI=#21TOE:POKEA,#80:A=A+1:NEXT:P=P+2:GOTO60
80 IF D<#30:FORI=#21TOD:POKEA,#80:A=A+1:NEXT:P=P+1:GOTO60
90 E=PEEK(P+1):POKEA,#80+D>>2&#F,D&3<<2|E>>4&3+#80,#80+E&#F:A=A+3:P=P+2:GOTO60
100 IF !INKEY() GOTO100
110 C=!C:GOTO50
IchigoJam Q (IchigoJam BASIC 1.4.3) で実行すると、1枚デコードするのに3~4秒程度かかった。
なお、画像中の「学ラン」「セーラー服」の文字は美咲ゴシックを使用している。
画像の展開をマシン語に移植する
BASIC での実装によりデコーダのアルゴリズムは良さそうであることが確認できたが、このままでは遅すぎてルーレットとして使い物にならなそうである。
そこで、このアルゴリズムをマシン語に移植した。
アセンブリコード
' R0 : 文字のアドレス
' R1 : 画面のアドレス
R0 += R1
R2 = 9
R2 = R2 << 8
R1 += R2
@MAINLOOP
R2 = [R0]
R2 - #22
IF 0 GOTO @END
R2 - #30
IF CS GOTO @DIRECT
R2 - #21
IF !0 GOTO @ONEREPEAT
R0 += 1
R2 = [R0]
@ONEREPEAT
R0 += 1
R2 -= #20
R3 = #80
@REPEATLOOP
[R1] = R3
R1 += 1
R2 -= 1
IF !0 GOTO @REPEATLOOP
GOTO @MAINLOOP
@DIRECT
R2 = R2 << 26
R3 = [R0 + 1]
R3 = R3 << 26
R3 = R3 >> 6
R2 |= R3
R3 = R2 >> 28
R3 += #80
[R1] = R3
R2 = R2 << 4
R3 = R2 >> 28
R3 += #80
[R1 + 1] = R3
R2 = R2 << 4
R3 = R2 >> 28
R3 += #80
[R1 + 2] = R3
R0 += 2
R1 += 3
GOTO @MAINLOOP
@END
RET
10 ' ガクラン セーラーフク デモ 2
20 G="!G3D!<i@8@!9cbNl!8`1%NP!5h@@A#h@!4hD@D@Dh@@DQ@cDA@(hE%V`@Jo;CL0BB@&hE@A@Eh@BL;@@F#X@%hE@D@Eh@@HT@a`CF(hE%V`!5hE@A@Eh@!4kE%7`!77L3LT@!8T@AP!:T@AP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:L3LP!T"
30 S="!~'acL<!8`5I@XGd@!4hBRXE`h@!4hDKMXDh@B@@H0@CL0@%hEBM@Eh@Bk`H2@CUH@%hEBo@Eh@Bb@0hCC_l@%hE@A@Eh@@c@BD@b]d@%hM3L3Eh@!4kEhEhM8@!4kEhEhM8@!6V`V`T@!6`ZAZBP!7hJAZB`!7hJAZB`!7H3L3L0!~!~!7"
40 LET[0],17416,8713,530,17425,30722,10786,#D020,10800,#D20B,10785,#D101,12289,30722,12289,14880,9088,28683,12545,14849,#D1FB,-6162,1682,30787,1691,2459,17178,3859,13184,28683,274,3859,13184,28747
50 LET[33],274,3859,13184,28811,12290,12547,-6181,18288
60 C=0
70 IF C P=S ELSE P=G
80 X=USR(#800,P)
90 IF !INKEY() GOTO90
100 C=!C:GOTO70
IchigoJam Q (IchigoJam BASIC 1.4.3) で実行すると、一瞬で画像を切り替えることができた。
ただし、このプログラムは IchigoJam R には非対応である。
ルーレットの機能を実装する
画像を素早く切り替えることができたので、あとはルーレットの機能を実装すれば完成である。
というわけで、実装した。
10 ' ガクラン or セーラーフク ルーレット
20 G="!G3D!<i@8@!9cbNl!8`1%NP!5h@@A#h@!4hD@D@Dh@@DQ@cDA@(hE%V`@Jo;CL0BB@&hE@A@Eh@BL;@@F#X@%hE@D@Eh@@HT@a`CF(hE%V`!5hE@A@Eh@!4kE%7`!77L3LT@!8T@AP!:T@AP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:TEAP!:L3LP!T"
30 S="!~'acL<!8`5I@XGd@!4hBRXE`h@!4hDKMXDh@B@@H0@CL0@%hEBM@Eh@Bk`H2@CUH@%hEBo@Eh@Bb@0hCC_l@%hE@A@Eh@@c@BD@b]d@%hM3L3Eh@!4kEhEhM8@!4kEhEhM8@!6V`V`T@!6`ZAZBP!7hJAZB`!7hJAZB`!7H3L3L0!~!~!7"
40 LET[0],17416,8713,530,17425,30722,10786,#D020,10800,#D20B,10785,#D101,12289,30722,12289,14880,9088,28683,12545,14849,#D1FB,-6162,1682,30787,1691,2459,17178,3859,13184,28683,274,3859,13184,28747
50 LET[33],274,3859,13184,28811,12290,12547,-6181,18288
60 C=1:D=0:E=0:F=-1
70 C=!C:IF C P=S ELSE P=G
80 X=USR(#800,P)
90 IF D>0 BEEP:D=D-1:WAIT5:GOTO70
100 IF E>0 BEEP:E=E-1:WAIT10:GOTO70
110 IF F>0 BEEP:F=F-1:WAIT20:GOTO70
120 IF F=0 PLAY"T160L16O5CDEFG"
130 IF BTN() GOTO130
140 IF !INKEY() AND !BTN() GOTO140
150 D=RND(13)+36:E=RND(7)+6:F=RND(4)+3:GOTO70
- OneFiveCrowd で実行する (高速、非公式) (推奨)
- IchigoJam web で実行する (公式、低速)
まとめ
画像を独自の形式でエンコードして文字列としてプログラムに埋め込み、それをマシン語で素早くデコードして表示することで、2種類の画像の中からランダムで1種類を選ぶルーレットを作成できた。