0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

IchigoJamAdvent Calendar 2024

Day 7

【IchigoJam】学ランか セーラー服か ルーレット【川柳】

Posted at

はじめに

みなさんは、学ランを着ようかセーラー服を着ようか迷ったことはあるだろうか?
きっとある人も、ない人も居るだろう。
そんな状況でも、それ以外の状況でも、学ランかセーラー服かをランダムに決めてくれるルーレットを 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 を足した文字コードの文字を置いて表す

以下の Python プログラムは、画像ファイルとエンコードする部分を受け取り、この形式でエンコードした文字列を出力する。

img_assyuku.py
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 版デコーダの実行結果

なお、画像中の「学ラン」「セーラー服」の文字は美咲ゴシックを使用している。

画像の展開をマシン語に移植する

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

まとめ

画像を独自の形式でエンコードして文字列としてプログラムに埋め込み、それをマシン語で素早くデコードして表示することで、2種類の画像の中からランダムで1種類を選ぶルーレットを作成できた。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?