IWDD Hackathon FES.ことIWDD#25に参加して「美術館」の解法支援プログラムをMSXで作ってきた。

まあふだんは自分はperlな人なので、最初はperlでなにか作ろうかと思ってたのですが。
このあいだ、思わず1-Chip MSXを購入してしまいまして。
別に昔MSXを持っていたわけでもなく、ということはROMカセットもほとんど持っておらず、そんなわけでこのままでは「懐かしいMSXをちょっと起動してみました」以上にならないので、今回の好機を使って何かBASICプログラミングでもしようと思ってみたわけですよ。

つまり、私がMSXでプログラミングしてた人です。

それにしても、こんなにMSX2ってスペック低かったっけ?

MSXでプログラミングはしなかったわけではありません。中学校のクラブ活動で使えるMSX2マシンが1台あったし。
でも、自分の家にあったのは別の8ビットマシンだったし、高校でさわっていたのは主に富士通のマシンだったし、MSX-BASICって考えてみたら移植のネタ元にしか使ってなかったんです。
そんなわけで、実質文字色が1色のみ、とか、グラフィック文字にベタ塗りつぶしな■がないとか、あれれ?と思うことしきり。
ある程度のコマンドリファレンスはネット上に転がっているのですが、この辺のデータがちょっと探しきれなかったのが残念です。

今回の実質作業時間は4時間。

BASICそのものは昔さわっていたので、行番号プログラミングとかそんなことはやっているあいだに思い出しましたが、いや思いもしなかったことに戸惑うばかり。MID$関数(文字列から一部の文字を取り出す)が0オリジンでなく1オリジンだったり。whileループがなかったり。
あと、MSXの画面上でプログラミングしていると、やっぱり大文字で書く方が読みやすいですね。あの頃全て大文字だったのはこのせいか。

何を作るかは一応方向としては決めてありました。実質4時間で何か形になる、と言うことを考えると、基本はテキスト表示主体。データ処理。でも多量のデータはテスト時間がかかる。ならばパズル解法支援か、と。

今回ネタにしたパズルは「美術館」

ペンシルパズル、まあクロスワードパズルとか数独とか、あの類の紙と鉛筆で解くパズルの中に「美術館」というのがあります。発祥のニコリには解説ページがありますので、そちらを見てもらった方がいいのですが、

  • 10x10*1のマス目に、空白と壁が配置してある。
  • 壁の一部には、数字が書いてあり、これがその壁の上下左右のマスに配置される明かり(○)の総数となる。
  • 明かり(○)は、1つ配置されたら、その上下左右、壁にぶつかるまでの各1直線のマス(明かりに照らされたマス)には他の明かりは配置されない。
  • 全てのマスは、明かり(○)によって照らされなければならない。

というルールでして。
これを解くのを支援しよう、というのが今回の目的です。

コンピュータだって作るパズルを、コンピュータで解くんじゃ、人がいらなくなっちゃうよね。

ここがミソなのですが、今回作るプログラムはあくまで「解法支援」であって、「自動解析」ではありません。
いや、だって金払ってパズル買うんですよ。自分で解かなきゃもったいないじゃないですか。
今回作るのは、あくまで、「特に考えなくても自明で解ける部分は解いてしまう」というところまで。人間はそこから先の試行錯誤だけを楽しめばいい、という、そのためのプログラムです。

BASICがスパゲティプログラムの温床になっていた理由も思い出した。

それにしても、久しぶりのBASICは大変でした。
慣れないキーボードでラインエディタ。行番号の上でReturnキー押すの忘れたり、間違えて上書きしたり。
機能追加も行番号単位ですから、プログラムもかなり見苦しいことになっています。
そうか、こうやって作ってたらそりゃスパゲティになるよな。

10 DEFINT B-Z
20 DEF FNS(S,E)=-(E>S)+(E=<S)
30 DEF FNO(X,Y)=(X<0)+(X>9)+(Y<0)+(Y>9)
40 'Bijutsukan
50 SCREEN 0
60 DIM F(9,9),L(9,9),H(9,9),B(9,9),BU(3)
70 OPEN "data.dat" FOR INPUT AS #1
80 J=0
90 INPUT #1,A$:IF A$="[END]" THEN GOTO 170
100 FOR I=0 TO 9
110 D=VAL(MID$(A$,I+1,1))
120 IF D>0 THEN F(I,J)=1
130 IF D>1 THEN H(I,J)=D-1
140 NEXT I
150 J=J+1
160 GOTO 90
170 CLOSE #1
180 LOCATE 0,0
190 PRINT "+----------+"
200 FOR I=0 TO 9:PRINT "|          |":NEXT
210 PRINT "+----------+"
220 FOR Y=0 TO 9
230 FOR X=0 TO 9
240 LOCATE X+1,Y+1
250 IF F(X,Y)=1 THEN PRINT "#";
260 IF H(X,Y)>0 THEN LOCATE X+1,Y+1:PRINT USING "#";H(X,Y)-1;
270 NEXT X:NEXT Y
280 LOCATE 0,13:AC$=INPUT$(1)
290 IF AC$=" " THEN GOSUB 330
300 IF AC$="f" THEN GOSUB 810
310 IF AC$="q" THEN END
320 GOTO 280
330 'scan sub
340 FOR J=0 TO 9
350 FOR I=0 TO 9
360 IF H(I,J)>0 THEN GOSUB 390
370 NEXT I:NEXT J
380 RETURN
390 'LRUD
400 C=0:CA=0:CF=0
410 FOR J2=J-1 TO J+1
420 IF FNO(I,J2)<0 THEN 510
430 FOR I2=I-1 TO I+1
440 IF FNO(I2,J)<0 THEN CF=CF+1:GOTO 500
450 IF I2<>I AND J2<>J THEN 500
460 IF I2=I AND J2=J THEN 500
470 IF F(I2,J2)=0 AND L(I2,J2)=0 THEN BU(C)=I2*10+J2:C=C+1
480 IF F(I2,J2)=0 AND B(I2,J2)=1 THEN CA=CA+1
490 IF F(I2,J2)=1 THEN CF=CF+1
500 NEXT I2
510 NEXT J2
520 LOCATE 0,14:PRINT I;J;H(I,J)-1;C;CA
530 IF H(I,J)=CA+1 AND C>0 THEN GOSUB 750:RETURN
540 IF H(I,J)=1 OR H(I,J)-1 <> C THEN RETURN
550 'Nuritsubushi
560 'LOCATE 0,13:PRINT I;J;C
570 FOR C2 = 0 TO C-1
580 I2=INT(BU(C2)/10):J2=BU(C2) MOD 10
590 B(I2,J2)=1:L(I2,J2)=1
600 LOCATE I2+1,J2+1:PRINT "o";
610 JS=J2:JE=J2:IS=I2-1:IE=0:GOSUB 660
620 JS=J2:JE=J2:IS=I2+1:IE=9:GOSUB 660
630 JS=J2-1:JE=0:IS=I2:IE=I2:GOSUB 660
640 JS=J2+1:JE=9:IS=I2:IE=I2:GOSUB 660
650 NEXT C2
660 FOR J3=JS TO JE STEP FNS(JS,JE)
670 FOR I3=IS TO IE STEP FNS(IS,IE)
680 IF FNO(I3,J3)<0 THEN I3=IE:J3=JE:GOTO 700
690 IF F(I3,J3)=0 THEN GOSUB 720 ELSE I3=IE:J3=JE
700 NEXT I3:NEXT J3
710 RETURN
720 'print sub
730 L(I3,J3)=1:LOCATE I3+1,J3+1:IF B(I3,J3)=0 THEN PRINT "+";
740 RETURN
750 'Already Put lights
760 FOR C2 = 0 TO C-1
770 I2=INT(BU(C2)/10):J2=BU(C2) MOD 10
780 L(I2,J2)=1:LOCATE I2+1,J2+1:PRINT "+";
790 NEXT C2
800 RETURN
810 'Fill Blank Light
820 FOR J=0 TO 9
830 FOR I=0 TO 9
840 IF F(I,J)=0 AND L(I,J)=0 THEN GOSUB 870
850 NEXT I:NEXT J
860 RETURN
870 'check light
880 CH=0
890 JS=J:JE=J:IS=I-1:IE=0:GOSUB 980
900 JS=J:JE=J:IS=I+1:IE=9:GOSUB 980
910 JS=J-1:JE=0:IS=I:IE=I:GOSUB 980
920 JS=J+1:JE=9:IS=I:IE=I:GOSUB 980
930 LOCATE 0,14:PRINT I;J;L(I,J);CH
940 IF CH=1 THEN RETURN
950 B(I,J)=1:L(I,J)=1
960 LOCATE I+1,J+1:PRINT "o";
970 RETURN
980 FOR J3=JS TO JE STEP FNS(JS,JE)
990 FOR I3=IS TO IE STEP FNS(IS,IE)
1000 IF FNO(I3,J3)<0 THEN I3=IE:J3=JE:GOTO 1030
1010 IF F(I3,J3)=1 THEN I3=IE:J3=JE:GOTO 1030
1020 IF L(I3,J3)=0 THEN CH=1:I3=IE:J3=JE
1030 NEXT I3:NEXT J3
1040 RETURN

せっかく作ったやつですので、この時点での完成品をこちらに置いておきます。さっき行番号を手直しするついでにディスクイメージも作りましたので、paraMSXなどなどのMSXエミュレータでもディスクイメージをマウントして

LOAD "MUSEUM.BAS"
RUN

すれば使えるはずです。
ちなみに、キー入力を促されるところで、スペースを押せば、左上から明かり(○)確定部分を検索、fを押せば明かり(○)補完確定部分を検索、qを押せば終了、となってます。
DATA.DATはパズルのファイル。0が空白,1が壁,2がヒント(0),3がヒント(1),4がヒント(2),5がヒント(3),6がヒント(4)。1マスあたり3bitで表せるわけで、コード圧縮すればもっと小さいファイルになりますが、まあそこまでの必要はないかと。
ちなみにこれ、パズル通信ニコリ117号の「どっさり美術館」特集の3問目を使わせていただいています。
これに添付された難度のパズルだと、解法支援と言いつつ全部解けちゃいますね。

*1:実際には任意だけど