search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

IchigoJamでPanCakeに三毛猫を表示する

お詫び

私の能力不足のため、記事を書くために連日ひどい夜ふかしを発生させてしまいました。
さらに、今日(2019/12/25 AM0時~3時頃)は別件で時間を奪われ、さらにひどい事態になることが予想されます。
そのため、申し訳ありませんが、今夜(2019/12/25 AM0時~5時頃)は記事の執筆を見送らせていただきます。した。
年内には記事を書いて差し替えます。差し替えました。

何をするか

IchigoJamを用いてPanCakeを制御し、三毛猫の画像を出力させます。
ついでにその画像でスライドパズルができるようにします。

使用ツール

IchigoJam

2,000円前後で購入することができ、BASICによるプログラミングが可能なパソコンです。
こどもパソコン IchigoJam - はじめてのプログラミングパソコン(1500円)

また、プログラムをWebブラウザ上で実行できるサービスもあります。
IchigoJam web by WebAssembly
※マシン語を用いている部分は、今のところこのサービスでは動きません

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

PanCake

シリアル通信でコマンドを送り、画像や音声を出力させることができる「こどもサウンドグラボ」です。
PanCake
GitHub - fu-sen/PanCake-COMMAND: PanCake コマンド一覧 command reference (Japanese)

IchigoJamとPanCakeの接続

PanCakeから出ているピンヘッダ・ピンソケットを用いて、
VCC・GND・TXDの3本の端子をIchigoJamの同じ名前の端子に接続します。
VCCやGNDは複数ありますが、それぞれ1組ずつだけで良いようです。
また、RXDを接続してしまうとなぜかIchigoJamへのPCからのシリアル通信が効かなくなってしまうようなので、
RXDは接続しないようにします。

三毛猫の表示 (第1世代)

ソースコード

1024byte

1 ' ネコ
10 LET[0],63736,4200,5960,22776,18464,63527,4152,3,1864,5891,6392,4880,18432,4871,63511,24584,63607,24584,18551,22560,4175,8474,5914,6223,30768,16704,18503,14400,4175,801,5921,10319,26672,12576,12547
20 LET[35],22567,22592,17217,26689,26672,30913,30768,16674,34850,34865,16913,4113,16760,16744,8469,22576,26689,5441,20513,12616,41304,14432,22577,24753,12584,49496,6240,18481,20721,12552,61768,16401
30 LET[67],14385,61751,14129,26424,497,14391,61815,14391,53639,18503,14144,24896,18503,16640,5888,16640,16640,22599,16640,5888,16640,12544,30775,256,256,256,6144,256,256,256,63488,16392,16440,63736
40 [101]=216:A=#900:S=0:FORI=0TO202:D=PEEK(#800+I):C=D%16:FORJ=0TOD/16:IFSPOKEA,S-1+C:A=A+1:S=0ELSES=C*16+1
50 NEXT:NEXT:LET[0],41279,46560,2306,8975,16387,9504,20240,8320,28728,8230,28728,8194,28728,1944,3776,28728,2200,192,28728,2192,448,6153,1936,3840,6153,8720,28730,8964,26632,32062,16949,53500,28728
60 LET[33],2560,7771,53752,12560,7826,53747,48608,32768,16384:POKE#700,#7E,#E0:FORI=0TO15:X=USR(#700,I*17):NEXT

実行結果

YouTube動画 (0分29秒)
実行結果(YouTube)

解説

画像の作成

まずは表示する三毛猫の画像を用意します。今回は32×32の画像を用います。
また、変換処理をしやすいように8ビットビットマップ(圧縮なし)で保存し、
パレットの番号をPanCakeの色番号と合わせておきます。
画像の作成には、パレットの編集と保存が可能なPictBearを用いました。
PictBear - 多機能ペイントアプリ | フェンリル

今回はこの画像を用います。ただし、表示の都合上8倍に拡大し、PNGに変換してあります。
三毛猫

画像の圧縮

32×32の画像は、1ピクセルを1バイトで表すと1024バイトになってしまいます。
IchigoJamのプログラムの1ブロックは1024バイトなので、これだけで埋まってしまいます。
(実際は、行番号なども入れるので、書けるサイズはもっと小さくなります)
16色なので2ピクセルを1バイトで表すことができ、これなら512バイトになりますが、
この情報をそのままプログラムに書こうとしても、普通の文字にならずに書きにくい場所が多くなります。

そこで、画像データを圧縮します。
今回は、このPerlプログラムを用います。

image_compress.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 2) {
    die "Usage: perl image_compress.pl input output\n";
}

my $inName = $ARGV[0];
my $outName = $ARGV[1];

open(INFILE, "< $inName") or die "$inName open failed\n";
binmode(INFILE);
my $inData = "";
while (<INFILE>) { $inData .= $_; }
close(INFILE);

if (length($inData) != 1142) {
    die "unsupported input\n";
}
if (substr($inData, 0, 6) ne "\x42\x4d\x76\x04\x00\x00") {
    die "unsupported input\n";
}
if (substr($inData, 10, 28) ne ("\x76\x00\x00\x00" .
"\x28\x00\x00\x00\x20\x00\x00\x00\x20\x00\x00\x00" .
"\x01\x00\x08\x00\x00\x00\x00\x00\x00\x04\x00\x00")) {
    die "unsupported input\n";
}

my $rawImageData = substr($inData, 0x76, 0x400);
my $imageDataStr = "";
for (my $i = 31; $i >= 0; $i--) {
    $imageDataStr .= substr($rawImageData, 32 * $i, 32);
}

my @imageData = unpack("C*", $imageDataStr);

open(OUTFILE, "> $outName") or die "$outName open failed\n";
binmode(OUTFILE);
my $prev = 0;
my $count = 0;
for (my $i = 0; $i < @imageData; $i++) {
    if ($imageData[$i] != $prev || $count >= 16) {
        if ($count > 0) {
            printf OUTFILE "%c", ($count - 1) * 16 + $prev;
        }
        $prev = $imageData[$i];
        $count = 0;
    }
    $count++;
}
if ($count > 0) {
    printf OUTFILE "%c", ($count - 1) * 16 + $prev;
}
close(OUTFILE);

データを読み込んだら、まず8ビット、32×32、無圧縮のBMPファイルかをチェックします。
次に、ピクセルデータが下から上に格納されているBMPデータから、上から下になるようにデータを取得します。
そして、圧縮をかけてその結果を出力します。
今回はランレングス圧縮を行います。
これは連続して同じデータが出てくることが多いデータに有効で、
連続する同じデータの並びを「データ+並ぶ個数」に置き換えていきます。
今回は、「データ+並ぶ個数」を1バイトで表現し、上位4ビットを個数、下位4ビットをデータとしています。
この圧縮をかけた結果、203バイトのデータが得られました。
IchigoJamの配列領域は2バイト×102要素=204バイトなので、ギリギリ収まる計算です。

画像の埋め込み

上記プログラムで得られた圧縮済みバイナリデータを、
以下のPerlプログラムを用いて配列領域に書き込むIchigoJamプログラムに変換します。

bin2text.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 1) {
    die "Usage: perl bin2text.pl input\n";
}

my $inputName = $ARGV[0];

open(INFILE, "< $inputName") or die "$inputName open failed\n";
binmode(INFILE);
my $inData = "";
while (<INFILE>) { $inData .= $_; }
close(INFILE);

if (length($inData) % 2 != 0) { $inData .= "\x00"; }

my @data = unpack("C*", $inData);
my $addr = 0;
my $line = sprintf("LET[%d]", $addr);
for (my $i = 0; $i < @data; $i += 2) {
    my $value = $data[$i] + $data[$i + 1] * 256;
    #if ($value >= 32768 && $value - 65536 > -10000) { $value -= 65536; }
    my $delta = sprintf(",%d", $value);
    if (length($line) + length($delta) > 200-3) {
        print "$line\n";
        $line = sprintf("LET[%d]", $addr);
    }
    $line .= $delta;
    $addr++;
}
print "$line\n";

POKE命令はRAMの広い範囲に書き込めるかわりに1バイトずつしか指定できない一方、
配列への書き込みは配列領域にしか書き込めないかわりに2バイトずつ指定できるため、文字数の削減につながります。
例えば、0xFFのバイトを配列の先頭に2個書き込む時、

POKE#800,#FF,#FF
LET[0]  ,65535

と、データ部分だけで2文字、全体では4文字、配列のほうが短くなります。
データ部分が短くなると、1行の文字数制限の中でより多くのデータを書き込めるようになり、
行番号および書き込み命令の分のオーバーヘッドの削減にもつながります。

画像の展開

圧縮して埋め込んだ画像データをIchigoJamで展開し、表示のためにPanCakeに送る準備をします。
以下のアルゴリズムで2ピクセルを1バイトで表すデータに展開できます。
展開すると512バイトのデータになるので、VRAMに置けます。

上位データ = 0
for バイト in 圧縮済み画像データのバイト列:
    データ = 「バイト」の下位4ビット
    長さ = 「バイト」の上位4ビット
    (「長さ」+1)回繰り返し:
        if 「上位データ」が0:
            上位データ = 「データ」×16+1
        else:
            (「上位データ」-1 + 「データ」)を出力する
            上位データ = 0

画像の出力

VRAMに置いたデータを、シリアル通信でPanCakeのコマンドに埋め込んで出力します。
今回は、8×8ピクセルのカラーの絵を描画するSTAMPコマンドを用います。
そして、以下のマシン語を用い、指定位置のデータの読み取りと出力を行います。
横2ピクセルを1バイトで表すので、絵については4バイト×8行のデータを出力します。
上から$y$番目、左から$x$番目(0-origin)の8×8のブロックのデータ開始位置のオフセットは、$128y + 4x$となります。

    ' place this code to #800
    ' param : image_index * 16 + position_index
    R1 = PC + #40
    PUSH {R5,R6,R7,LR}
    R2 = R0 >> 4
    R3 = #F
    R3 &= R0
    R5 = #20
    R7 = [@uart_address]L
    R0 = #80
    [R7 + 0] = R0
    R0 = #26
    [R7 + 0] = R0
    R0 = #02
    [R7 + 0] = R0
    ' x coord
    'R0 = #3
    'R0 &= R3
    'R0 = R0 << 3
    R0 = R3 << 30
    R0 = R0 >> 27
    [R7 + 0] = R0
    ' y coord
    R0 = R3 >> 2
    R0 = R0 << 3
    [R7 + 0] = R0
    ' calculate image data address
    R0 = R2 >> 2
    R0 = R0 << 7
    R1 = R1 + R0
    'R0 = #3
    'R0 &= R2
    'R0 = R0 << 2
    R0 = R2 << 30
    R0 = R0 >> 28
    R1 = R1 + R0
    ' image data
    R2 = 16
    [R7 + 0] = R2 ' transparent color
@loop1
    R3 = 4
    R0 = [R1 + 0]L
@loop2
@send_wait
    R6 = [R7 + #14]
    R5 & R6
    IF 0 GOTO @send_wait
    [R7 + 0] = R0
    R0 = R0 >> 8
    R3 = R3 - 1
    IF !0 GOTO @loop2
    R1 += 16
    R2 = R2 - 2 ' hack for transparent color
    IF !0 GOTO @loop1
    POP {R5,R6,R7,PC}
@uart_address
    DATAL #40008000

後のことを考え、データの位置と出力する位置を別に変えられるようにしています。
コマンドに出力する座標は、x・yともに単純に指定の8倍(3ビット左シフト)です。
位置を切り出す計算において、AND演算を使おうとするとANDする値を設定するのに1命令余計に食ってしまうので、
左シフトで余計な上位のデータを追い出してから右シフトで戻す、という計算をしています。
プログラム短縮のため、画像データより前は送信できる状態かのチェックを省略し、
データ量が比較的多い画像データのみ状態をチェックしてから送信するようにしています。
また、1行が4バイトなので、4バイトずつメモリから読み込んでいます。

三毛猫の表示 (第2世代)

ソースコード

758byte

1 REM ミケネコ ヲ ヒョウジ 2
10 'ママニ1トaマナ2トbマテ1P0ト`Paマチ1Q0ト`Qaマタ6gマタ6gト2ナヤ1アBアaヤチ3ヌ4Ddト4テヤ1BPBaヤツ3ニ2CPCbナ4ナDTDニ3ニLヌ3ヌ"D"ネCネA$A1ヌDニDqB3ナDニDqB5トCナJ6テCナK6ツCナL6チCトO5タCトOA4CテcOCcテfO@cテgOcテhMdト4c4Fdト0D0a0D0Ddナ0D0a0D0Ccヌ0@0@0@0チ0@0@0@0マ
20 'タ4テ4ママヘ
39 LET[2],2,0,1,3,7,5,0,0,0,10,8,15:A=#900:P=#C00:B=0
40 IFPEEK(P)<>39P=P+1:GOTO40
50 P=P+1:IFPEEK(P)=0GOTO90
60 D=PEEK(P):IFD=0GOTO40
70 C=[D/16]:FORJ=0TOD%16:IFBPOKEA,B-1+C:A=A+1:B=0ELSEB=C*16+1
80 NEXT:P=P+1:GOTO60
90 LET[0],41279,46560,2306,8975,16387,9504,20240,8320,28728,8230,28728,8194,28728,1944,3776,28728,2200,192,28728,2192,448,6153,1936,3840,6153,8720,28730,8964,26632,32062,16949,53500,28728,2560,7771
100 LET[35],53752,12560,7826,53747,48608,32768,16384:POKE#700,#7E,#E0:FORI=0TO15:X=USR(#700,I*17):NEXT

実行結果

YouTube動画 (0分29秒)
実行結果(YouTube)

解説

圧縮データを効率よく埋め込む

配列を使うことでPOKEを使うよりは効率よくデータをIchigoJamプログラムに埋め込めますが、
それでも1バイトあたり埋め込むのに約3バイトを使ってしまい、効率が悪いです。
そこで、データの性質を利用し、1バイトあたり約1バイトで埋め込めるようにします。

まず、以下のPerlプログラムで圧縮データ中のバイトの分布を調べました。

hist.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 1) {
    die "Usage: perl hist.pl input\n";
}

my $inName = $ARGV[0];

open(IN, "< $inName") or die "$inName open failed\n";
binmode(IN);
my $inData = "";
while (<IN>) { $inData .= $_; }
close(IN);

my @data = unpack("C*", $inData);

my @count = ();
for (my $i = 0; $i < 256; $i++) { push(@count, 0); }

for (my $i = 0; $i < @data; $i++) {
    $count[$data[$i]]++;
}

my @sum = ();
for (my $i = 0; $i < 16; $i++) { push(@sum, 0); }

print "    ";
for (my $i = 0; $i < 16; $i++) {
    printf "   %X", $i;
}
print " sum\n";
for (my $i = 0; $i < 16; $i++) {
    printf "  %X ", $i;
    my $csum = 0;
    for (my $j = 0; $j < 16; $j++) {
        printf " %3d", $count[$i * 16 + $j];
        $csum += $count[$i * 16 + $j];
        $sum[$j] += $count[$i * 16 + $j];
    }
    printf " %3d\n", $csum;
}
print  "sum ";
my $csumsum = 0;
for (my $j = 0; $j < 16; $j++) {
    printf " %3d", $sum[$j];
    $csumsum += $sum[$j];
}
printf " %3d\n", $csumsum;

このプログラムは、バイトの数の分布を上位4ビットと下位4ビットに分けて調べてくれます。
見出しが縦に出ているのが上位4ビット、横に出ているのが下位4ビットです。
今回のデータでは、このような分布になりました。

       0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F sum
  0   18   7   0   4   0   0   0   2   4   0   0   0   0   0   0   0  35
  1    6   3   0   2   0   2   0   7   4   0   2   0   0   0   0   0  26
  2    3   5   2   0   0   0   0   2   2   0   0   0   0   0   0   0  14
  3    5  11   0   0   0   0   0   6   8   0   0   0   0   0   0   0  30
  4    8  13   1   1   0   0   0   4  11   0   0   0   0   0   0   4  42
  5    2   0   0   0   0   0   0   0   9   0   0   0   0   0   0   0  11
  6    5   1   0   0   0   0   0   1   6   0   0   0   0   0   0   0  13
  7    0   0   0   0   0   0   0   3   5   0   0   0   0   0   0   0   8
  8    0   0   0   0   0   0   0   1   2   0   0   0   0   0   0   0   3
  9    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  A    0   1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1
  B    0   1   0   0   0   0   0   0   0   0   0   0   0   0   0   0   1
  C    0   2   0   0   0   0   0   0   0   0   0   0   0   0   0   0   2
  D    0   1   0   0   0   0   0   0   1   0   0   0   0   0   0   0   2
  E    0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0   0
  F    0   5   0   0   0   0   0   0  10   0   0   0   0   0   0   0  15
sum   47  50   3   7   0   2   0  26  62   0   2   0   0   0   0   4 203

上位4ビット(長さ)はほとんど全ての種類が使われているのに対し、下位4ビット(色)は9種類しか使われていません。
そこで、上位4ビットと下位4ビットを入れ替え、さらに上位4ビットをテーブルを用いて変換することで、
普通の文字で表せるようにします。
この変換は、以下のPerlプログラムを用い、paletteにはxx201375xxxA8Fxxを指定して行いました。
このpaletteは、n番目(0-origin)の文字が表すデータをnで表す、という意味です。

tostr.pl
#!/usr/bin/perl

use strict;
use warnings;

if (@ARGV < 3) {
    die "Usage: perl tostr.pl input output palette\n";
}

my $inName = $ARGV[0];
my $outName = $ARGV[1];
my $paletteStr = $ARGV[2];

my @palette = ();
for (my $i = 0; $i < length($paletteStr); $i++) {
    my $c = substr($paletteStr, $i, 1);
    if ($c =~ /[0-9a-fA-F]/) {
        push(@palette, hex($c));
    } else {
        push(@palette, -1);
    }
}
my @invPalette = ();
for (my $i = 0; $i < 16; $i++) { push(@invPalette, 0); }
for (my $i = 0; $i < @palette; $i++) {
    if ($palette[$i] >= 0 && $palette[$i] < 16) {
        $invPalette[$palette[$i]] = $i;
    }
}

open(IN, "< $inName") or die "$inName open failed\n";
binmode(IN);
my $inStr = "";
while (<IN>) { $inStr .= $_; }
close(IN);

my @inData = unpack("C*", $inStr);

open(OUT, "> $outName") or die "$outName open failed\n";
binmode(OUT);
for (my $i = 0; $i < @inData; $i++) {
    my $color = $inData[$i] & 0xf;
    my $num = ($inData[$i] >> 4) & 0xf;
    printf OUT "%c", ($invPalette[$color] << 4) | $num;
}
close(OUT);

さらに、埋め込み時にも工夫をします。
今回は、変換した文字列をプログラムにコメントとして埋め込み、プログラム領域から読み取らせます。
読み取りは、プログラム領域の先頭から以下の方法で行います。

  1. '(文字コード39)まで読み飛ばす。
  2. 'の次のバイトが0x00であれば、終了する。そうでなければ、そこから読み取りと展開を開始する。
  3. 0x00のバイトがあったら、読み取りを停止して読み飛ばしを開始する。(1に戻る)

これに対応するため、プログラムに以下の工夫をしました。

  • 最初の行のプログラム名を展開対象にしないよう、'ではなくREMでコメントを書いている。
  • 画像データの次の行の行番号を39にすることで「39 0」というデータを置き、読み取りを終了させている。

残りの部分は、圧縮データの上位4ビットと下位4ビットの意味を入れ替え、
変数名を変えたくらいで、第1世代のプログラムとだいたい同じです。

三毛猫の表示 (第3世代)

ソースコード

708byte

1 REM ミケネコ ヲ ヒョウジ 3
10 'ママニ1トaマナ2トbマテ1P0ト`Paマチ1Q0ト`Qaマタ6gマタ6gト2ナヤ1アBアaヤチ3ヌ4Ddト4テヤ1BPBaヤツ3ニ2CPCbナ4ナDTDニ3ニLヌ3ヌ"D"ネCネA$A1ヌDニDqB3ナDニDqB5トCナJ6テCナK6ツCナL6チCトO5タCトOA4CテcOCcテfO@cテgOcテhMdト4c4Fdト0D0a0D0Ddナ0D0a0D0Ccヌ0@0@0@0チ0@0@0@0マ
20 'タ4テ4ママヘ
39 LET[2],2,0,1,3,7,5,0,0,0,10,8,15:A=#900:P=#C00:B=0
40 IFPEEK(P)<>39P=P+1:GOTO40
50 P=P+1:IFPEEK(P)=0GOTO90
60 D=PEEK(P):IFD=0GOTO40
70 C=[D/16]:FORJ=0TOD%16:IFBPOKEA,B-1+C:A=A+1:B=0ELSEB=C*16+1
80 NEXT:P=P+1:GOTO60
90 POKE#700,31,161,112,181,32,34,5,75,0,38,141,93,28,125,34,66,252,208,29,112,118,28,134,66,247,211,112,189,0,128,0,64
100 FORI=0TO15:POKE#780,#80,38,2,I%4*8,I/4*8,16:FORJ=0TO31:POKE#786+J,PEEK(#900+I/4*128+I%4*4+J/4*16+J%4):NEXT:X=USR(#700,38):NEXT

実行結果

YouTube動画 (0分32秒)
実行結果(YouTube)

解説

マシン語を使うと、高速かつ自由度の高い処理ができます。
しかし、その代償として、文字数を多く消費してしまいます。
そこで、このバージョンではBASICでバイナリコマンドを生成し、マシン語では送信のみを行います。
今回は、以下のマシン語を用いました。

    ' R0 : # of byte to send
    ' R1 : data start address
    ' R2 : mask for data put check
    ' R3 : UART register address
    ' R4 : buffer for data put check
    ' R5 : data buffer
    ' R6 : sent byte counter
    R1 = PC + #20
    PUSH {R4,R5,R6,LR}
    R2 = #20
    R3 = [@uart_address]L
    R6 = 0
@loop
    R5 = [R1 + R6]
@send_wait
    R4 = [R3 + #14]
    R2 & R4
    IF 0 GOTO @send_wait
    [R3 + 0] = R5
    R6 = R6 + 1
    R6 - R0
    IF CC GOTO @loop
    POP {R4,R5,R6,PC}
@uart_address
    DATAL #40008000

三毛猫の表示 (第4世代)

ソースコード

586byte

1 REM ミケネコ ヲ ヒョウジ 4
10 'ママニ1トaマナ2トbマテ1P0ト`Paマチ1Q0ト`Qaマタ6gマタ6gト2ナヤ1アBアaヤチ3ヌ4Ddト4テヤ1BPBaヤツ3ニ2CPCbナ4ナDTDニ3ニLヌ3ヌ"D"ネCネA$A1ヌDニDqB3ナDニDqB5トCナJ6テCナK6ツCナL6チCトO5タCトOA4CテcOCcテfO@cテgOcテhMdト4c4Fdト0D0a0D0Ddナ0D0a0D0Ccヌ0@0@0@0チ0@0@0@0マ
20 'タ4テ4ママヘ
39 LET[2],2,0,1,3,7,5,0,0,0,10,8,15:A=#980:P=#C00:B=0:CLS
40 IFPEEK(P)<>39P=P+1:GOTO40
50 P=P+1:IFPEEK(P)=0GOTO90
60 D=PEEK(P):IFD=0GOTO40
70 C=[D/16]:FORJ=0TOD%16:IFBPOKEA,B-1+C:A=A+1:B=0ELSEB=C*16+1
80 NEXT:P=P+1:GOTO60
90 LOCATE0,-1:?:FORI=0TO15:?CHR$(#80,38,2,I%4*8,I/4*8,16);:FORJ=0TO31:?CHR$(PEEK(#980+I/4*128+I%4*4+J/4*16+J%4));:NEXT:NEXT

実行結果

YouTube動画 (0分34秒)
実行結果(YouTube)

解説

第3世代ではコマンドの送信のためにマシン語を使っていましたが、
これはPRINT命令で出力するとVRAMの内容の破壊につながると思っていたからでした。
LOCATE 0,-1命令を実行しておくと、VRAMの内容を変えずにシリアル通信への出力ができるため、今回はこれを用います。
なお、最近のバージョンではLOCATE 0,-1命令に対応するデータもシリアル通信に出力されるようなので、
直後に改行を出力して区切り、PanCakeがその後のバイナリコマンドの入力を受け入れられるようにします。

スライドパズル

第4世代で画像とそれを表示するコードが十分短くなったので、空いた領域で15パズルを実装しました。
WASDキーまたは上下左右キーで、「空きに隣接しているパネルを動かす方向」を入力します。
例えば、上キーを押すと、空きの下のパネルが空きの位置に移動します。

ソースコード

990byte

1 REM ミケネコ パズル
10 'ママニ1トaマナ2トbマテ1 0ト` aマチ1!0ト`!aマタ6gマタ6gト2ナヤ1アBアaヤチ3ヌ4Ddト4テヤ1B Baヤツ3ニ2C Cbナ4ナD$Dニ3ニLヌ3ヌRDRネCネATA1ヌDニDqB3ナDニDqB5トCナJ6テCナK6ツCナL6チCトO5タCトOA4CテcOCcテfO@cテgOcテhMdト4c4Fdト0D0a0D0Ddナ0D0a0D0Ccヌ0@0@0@0チ0@0@0@0マ
20 CLS:LOCATE0,-1:?:?"PC CLEAR 08":LET[2],3,0,1,2,7,5,0,0,0,10,8,15:M=#700:A=#980:P=#C00:B=0:'タ4テ4ママヘ
39 IFPEEK(P)<>39P=P+1:GOTO39
40 P=P+1:IFPEEK(P)=0GOTO80
50 D=PEEK(P):IFD=0GOTO39
60 C=[D/16]:FORJ=0TOD%16:IFBPOKEA,B-1+C:A=A+1:B=0ELSEB=C*16+1
70 NEXT:P=P+1:GOTO50
80 B=15:FORI=0TO15:[I]=I:NEXT:[15]=16:FORC=0TO99:I=RND(14)+1:J=RND(I):T=[I]:[I]=[J]:[J]=T:NEXT:FORS=0TO15:I=[S]:GOSUB120:NEXT
90 C=1:FORI=0TO14:C=CAND[I]=I:NEXT:IFCI=15:S=15:GOSUB120:LOCATE0,22:END
100 K=INKEY()&#DF:D=(K=28ORK=65)+4*(K=30ORK=87)-(K=29ORK=68)-4*(K=31ORK=83):N=B+D:IFD=0ORN<0ORN>15OR(ABS(D)<4ANDB/4<>N/4)GOTO100
110 [B]=[N]:[N]=16:I=16:S=N:GOSUB120:I=[B]:S=B:GOSUB120:B=N:GOTO90
120 ?CHR$(#80,38,2,S%4*8+24,S/4*8+6,16);:FORJ=0TO31:?CHR$(PEEK(#980+I/4*128+I%4*4+J/4*16+J%4));:NEXT:RETURN

実行結果

YouTube動画 (3分8秒)
実行結果(YouTube)

解説

1~70行目
第4世代とほぼ同じ、画像データを展開するコードです。
ただし、PanCakeの画面の初期化コマンドおよびCLSコマンドを追加し、
プログラム終了後に扱いやすいよう画像データを展開する位置を調整しました。
'が来るまで読み飛ばす仕様を利用し、画像データの一部の前に処理プログラムを書いています。
CLSコマンドは、空きを表現するために画像データの後の部分を利用するので入れています。
120行目
第4世代とほぼ同じ、表示コマンドを送信するコードです。
ただし、表示画像と表示位置を独立に変えられるようにし、サブルーチン化しました。
また、画像を画面の中央に表示するようにしました。
80行目
スライドパズルの盤面の初期化処理、シャッフル処理、シャッフルした初期配置の描画処理を行っています。
順番にパネルを並べたあと、右下のパネルを空きにし、ランダムなパネルの交換を100回行います。
8パズル,15パズルの不可能な配置と判定法 | 高校数学の美しい物語を参考にすると、
このアルゴリズムでは空きの位置は固定、交換回数も偶数で固定なので、必ず解ける配置になるはずです。
90行目
パズルが解けたかを判定します。
解けている場合は空きを埋めて三毛猫の絵を完成させ、カーソルを次の作業がしやすい位置に戻して終了します。
100行目
キー入力を受け付け、それが有効かを判定します。
動きがない、または欄外に出る動きの場合は無効と判定し、入力の受け付けに戻ります。
上や下の欄外への移動はもちろん、
「上下移動の操作でないにもかかわらず、移動元と移動先の行が違う場合」も、欄外への移動となります。
110行目
有効な操作がされたので、それを盤面に反映し、影響がある部分の描画を行います。

まとめ

IchigoJamのプログラムで画像をランレングス圧縮したデータを展開した後、
PanCakeにコマンドを送り、32×32の三毛猫の画像を表示することができました。
この時、プログラムを改良していくことで、長さを減らしてスライドパズルを実装する余地を作ることができました。

各プログラムのbyte数グラフ

1バイトずつ指定するPOKE命令より、2バイトずつ指定できるLET(配列への代入)命令のほうが、
効率よくデータを埋め込むことができます。
データを1バイト1文字で表現できれば、コメントを用いてさらに効率よくデータを埋め込むことができます。
マシン語は、自由度が高いかわりに文字数を食いがちです。

ライセンス

この記事の中で、以下のものはCreative Commons 表示 4.0 国際ライセンスとします。
(CC BY 4.0 by みけCAT)

  • 「画像の作成」で提示している三毛猫の画像
  • IchigoJam向けのソースコード (第1世代、第2世代、第3世代、第4世代、スライドパズル)
  • IchigoJam向けのソースコードに埋め込んだマシン語のソースコード (第1世代と第3世代の解説の中)

記事のその他の部分(Perlプログラムを含む)については、利用規約に従います。

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
What you can do with signing up
1