果たしてFM77AV用Death Forceはクリアできるのか?

Death ForceはFM77AV専用ソフトとして1987年にリバーヒルソフトから発売された。FM77AVがせっかく4096色同時発色(あるいは8色4画面とか64色2画面とかパレットでなんとでもなった)を実現したのに登場直後専用タイトルがなかなか出なかったそのFM77AV界にあって、最初に多色同時発色を有効利用したタイトルだった。

先日ゲーム保存協会を訪問したとき、「果たしてFM77AV専用ソフト、Death Forceはクリアできるのか?」という問題があることを聞いた。なんでも、Death Forceはコピープロテクトの誤爆によりクリアが不可能である可能性があるということだった。その真相を調べたくなった。

そのDeath Force、自分は当時Death Forceは予算の都合で買わなかったので持っていなかったが、最近ヤフオクに3.5インチディスクが出品されていたので落としてみた。届いたフロッピーディスクは幸いカビなども無く、実機FM77AV40を利用して難なくイメージ化できた。コピープロテクトの内容を解析したところD77ディスクイメージで再現可能なレベルのようだったので、エミュレータXM7を利用してクリアを目指してみた。

使用したディスク

AディスクBディスク共通でディスク裏面に6C184F 2Dと刻印がある

結論

いきなり結論を書いてしまうと、噂はおおむね本当であった可能性が高い。クリアが不可能なバージョンと可能なバージョンが存在することが確認できた。

まず、ヤフオクで今回落としたバージョンではクリアは無理。難しいからではなく、ラスボスに通じる通路への入口に入ることができない。解析した結果、その入り口は、座標 ($19,$0D)にあることになっている。しかし、この位置は壁。入り口座標を上にずらすか、あるいは壁の当たり判定を下にずらさない限り通過できない。

具体的には、この画面。プログラム中ではマップ$79となっている。Death Forceの世界全体は16×16のマップで構成されていてそれぞれのマップに地上・地下がある。左上がマップ$00、右にひとつ移動すると番号は1増え、下に移動すると$10増える。$79は左上から下に7枚、右に9枚移動したマップを意味する。

隠れでもなんでもないただの階段に入らなくてはならない。が、そのままでは入れない。

最終ステージの階段位置テーブル($7C00~)は次の通り

$7C00~
14
75 05 0C 01 
56 1B 09 04 
77 17 1C 01 
78 07 1F 01 
79 19 0D 01 
85 08 10 04 
77 1C 07 04 
87 1E 0F 04 
58 0E 07 04 
69 1E 13 04 
6B 1E 13 04 
48 0F 0B 01 
89 05 16 04 
6A 0F 14 04 
57 19 0F 04 
88 13 0F 04 
8A 07 1D 01 
7A 07 1D 01 
7B 05 01 04 
8B 05 07 01

最初の1バイトが階段個数。16進数なので、10進数だと20個の階段がある。続く4バイト単位の意味はマップID、マップ上のX座標、マップ上のY座標、階段の向き(上下)を表している。最後の1バイトが1のとき地上から地下は下向き、地下から地上は上向き。4の場合はその逆となっている。黄色でハイライトした階段が問題の階段で、オリジナルのディスクイメージではY座標が$0D (10進数で13) になる。

問題の画面、マップ$79の壁判定用のテーブル($4C00~)が次の通り。この値のビット1が0なら通行可能、1なら壁と判定される。Death Forceの画面は40×37個の小ブロックに分かれていて、衝突判定用テーブルは1行あたり縦2ブロック分をカバーする。赤くハイライトしたブロックが問題の壁で、自機は3×3ブロックを占有するので、グリーンにハイライトした場所より下に移動することができない。この座標が($19,$0A)。入り口の判定は壁判定よりも前に実行されるので、入り口座標が($19,$0B)であるならば入り口に入ることができる。しかし、入り口座標は($19,$0D)なので、ちょうど衝突判定テーブル1行分足りない。

$4C00~
00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
01 01 01 01 01 01 01 03 03 03 00 00 00 00 00 00 00 00 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03
01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03
00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03
00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 03 03 03 03 03 00 00 00 00 00 00 00 00 00 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00 01 03 03 03 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03
03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03

この状況を打破するためには、二通りの方法がある。ひとつは、プログラムディスク(ディスクA)を書き換えて階段の座標を($19,$0B)に変更する方法。しかし、この座標はプログラムディスクのトラック15、サイド0、セクタ12に書いてある。この座標を修正するにはプログラムディスクの書き換えが必要なので、望ましくない。原本ディスクの書き換え時にエラーが起きた場合原本ディスクを破壊する可能性がある。また現存しているDeath Forceの原本ディスクは貴重な資料だから、現在の内容を維持するべきだ。

もうひとつの方法がデータディスクを書き換える方法。上の衝突判定用テーブルは、画面切り替えのときいったん$7800からの20×10バイトにコピーされて、その情報を元に$4C00からの40×20のテーブルが展開される。この画面$79の20×10データは以下の通り。

$7800~ 
01 01 01 06 03 5B 5B 5B 5B 5B 02 08 01 01 01 01 01 01 06 03 
09 09 09 0E 0A 01 01 06 03 5B 0F 10 09 09 09 09 09 09 0E 0F 
00 00 00 1B 09 09 09 0E 0F 5B 0F 18 00 00 00 00 00 00 16 0F 
00 00 00 00 00 00 00 16 0A 01 0B 18 2D 2E 2F 00 00 00 16 0F 
00 00 00 00 00 00 00 1B 09 09 09 19 46 47 5A 00 1D 02 1E 1F 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0F 5B 5B 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0A 06 03 
1E 1E 1E 1E 1E 1E 1E 1E 1E 03 1C 00 00 00 00 00 1B 09 0E 0F 
5B 5B 5B 5B 5B 5B 5B 5B 5B 0F 18 00 00 00 00 00 00 00 16 0F 
5B 5B 5B 5B 5B 5B 5B 5B 5B 20 1E 1E 1E 1E 1E 1E 1E 1E 1E 1F

コピー元は$5C80~に格納されている。

このうち黄色でハイライトした部分が問題の階段に対応する。これを一段下に下げてしまえばいい。幸いにして、このデータはデータディスクのトラック11、サイド1、セクタ9に書かれている。(データディスクを生成したときにディスクBからコピーされたまま。) データディスクを生成した後で、以下の書き換えを加えることで、階段を一段(2ブロック)下に下げてラスボス戦に通じる通路に突入が可能になる。

修正前
Disk:0 Track:11 Side:1 Sector:9
0000  18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[.......
0010  09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[...
0020  09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................
0030  5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[...............
0040  00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[
0050  20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./..
0060  0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ ..
0070  1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[
0080  01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[......
0090  01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[..
00a0  09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................
00b0  0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[..............
00c0  00 00 00 16 0a 01 0b 18 2d 2e 2f 00 00 00 16 0f|........-./.....
00d0  00 00 00 00 00 00 00 1b 09 09 09 19 46 47 5a 00|............FGZ.
00e0  1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................
00f0  00 00 00 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|......[[........
修正後
Disk:0 Track:11 Side:1 Sector:9
0000  18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[.......
0010  09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[...
0020  09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................
0030  5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[...............
0040  00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[
0050  20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./..
0060  0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ ..
0070  1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[
0080  01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[......
0090  01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[..
00a0  09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................
00b0  0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[..............
00c0  00 00 00 16 0a 01 0b 18 00 00 00 00 00 00 16 0f|................
00d0  00 00 00 00 00 00 00 1b 09 09 09 19 2d 2e 2f 00|............-./.
00e0  1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................
00f0  46 47 5a 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|FGZ...[[........

この修正を加えたデータディスクで問題の画面、マップ$79 に移動すると、こうなる。

階段が2ブロック下にずれる。これによりプログラムディスクに修正を加えることなくクリアが可能になる。

それだけだと不親切なので、データディスクイメージにパッチを当てるPython Scriptを書いた。

import sys

inFName=sys.argv[1]
outFName=inFName+"_patched"
print("Input="+inFName)
print("Output="+outFName)

ifp=open(inFName,"rb")
bin=ifp.read()
ifp.close()

print("Python inferiority.")
print("Why do I have to convert an array to a list, edit,")
print("and then convert it back to an array?")
print("Surprisingly poor programming language.")
print("Ditch Python.  Use C++.")
lst=[]
for b in bin:
    lst.append(b)

print("Input Length="+str(len(lst))+" bytes")

nFound=0
for i in range(len(lst)-60):
    if lst[i:i+5]==[0x0b,0x18,0x2d,0x2e,0x2f] and lst[i+20:i+25]==[0x09,0x19,0x46,0x47,0x5a]:
        print("Found at 0x"+hex(i))
        lst[i+42]=lst[i+22]
        lst[i+43]=lst[i+23]
        lst[i+44]=lst[i+24]
        lst[i+22]=lst[i+2]
        lst[i+23]=lst[i+3]
        lst[i+24]=lst[i+4]
        lst[i+2]=0
        lst[i+3]=0
        lst[i+4]=0
        nFound+=1

if 1!=nFound:
    print("This should occur only once.\n")
    print("Something is not right.\n")
    quit()

ofp=open(outFName,"wb")
ofp.write(bytearray(lst))
ofp.close()

print("Wrote to "+outFName)
print("Rename the extension before using.")

使い方は、データディスクのイメージが DFORCEDATA.D77、スクリプトがdforcepatch.py だとすると、

python dforcepatch.py DFORCEDATA.D77

DFORCEDATA.D77_patch というファイルができるので、拡張子をD77に変更して使う。

ちなみにPythonは嫌い。みんなC++使おうよ。なんでバイナリの配列をいったんリストに変更してまた配列に戻さなきゃならないんだ。酷い言語だ。なんでこんな欠陥言語が流行するかな。早く消滅してほしいが、消滅されると山のように書いたメンテナンススクリプトが書き直しになるから消滅してもらうと困る。なんで上のをPythonで書いたかというと多分C++コンパイラ入れてる人よりPython入れてる人の方が多いだろうと思ったんで。

それと、下はラスボス直前とエンディング直前のデータファイル。 なお、データは改造してある。最終ステージの猛攻は改造なしでは自分の腕では突破できなかった。なお、ラスボス直前から無敵コマンドを使ってもクリアは可能。

[ラスボス直前] [ラスボス戦後]

なお、このデータディスクは上の方法とは違う方法で問題の階段に突入したので、トラック11、サイド1、セクタ9は元のまま。

バグ修正版ディスクは実在した

Death Forceに関するうわさのひとつに、クリアできないというクレームを入れたところリバーヒルソフトからバグ修正版のディスクが送られてきてそれを使うとクリアできた、というものがある。それは本当なのか?

もしもバグ修正版が存在するとすれば、これまでの解析により修正箇所はディスクAのトラック15、サイド0、セクタ12、またはディスクBのトラック11、サイド1、セクタ9である可能性が高い。しかし、クリアできなかったユーザがいて、リバーヒルソフトから送られてきた修正版ディスクでクリアできたとする噂が本当であるとすれば、ディスクAの修正である可能性が高い。ディスクBは新たにゲームを開始するときしか使用しないので、ディスクBの修正であったとすればそのユーザがそれまでプレイしていたデータを破棄してデータディスク作成からやりなおしたのでなければクリアはできない。

もしもバグ修正版とするディスクの存在が確認できればこの謎は解決する。そして、クリア不能バージョンの問題はただのバグだった可能性が非常に高くなる。

この謎は、ゲーム保存協会メンバーが所有するDeath ForceのディスクA、およびゲーム保存協会が収蔵するDeath ForceのディスクAを確認することで解決した。まず、ゲーム保存協会メンバーが所有するディスクは裏面に 75524X 2D という刻印があった。おそらく、これはフロッピーディスク製造時のバッチを表す刻印であってDeath Forceのシリアルナンバーでは無いかもしれないが、手元のクリア不能バージョンの刻印 6C184F 2D よりと比べると最上位桁が6から7に上がっているのでおそらく後に製造されたディスクと推測できる。

以下はそのディスクのトラック15、サイド0、セクタ12のダンプ。

0000  14 75 05 0C 01 56 1B 09-04 77 17 1C 01 78 07 1F |.u...V...w...x..
0010  01 79 19 0B 01 85 08 10-04 77 1C 07 04 87 1E 0F |.y.......w......
0020  04 58 0E 07 04 69 1E 13-04 6B 1E 13 04 48 0F 0B |.X...i...k...H..
0030  01 89 05 16 04 6A 0F 14-04 57 19 0F 04 88 13 0F |.....j...W......
0040  04 8A 07 1D 01 7A 07 1D-01 7B 05 01 04 8B 05 07 |.....z...{......
0050  01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
0060  00 00 00 00 00 90 00 02-04 00 00 C0 03 90 02 1E |...........タ....
0070  04 93 00 02 04 00 00 C0-03 90 02 28 06 96 00 04 |.......タ...(....
0080  08 82 03 00 01 8C 0B 32-08 AE 00 04 08 82 03 00 |.......2.ョ......
0090  01 0C 0B 32 0A C6 00 04-08 82 03 00 01 0C 0B 46 |...2.ニ.........F
00A0  0F 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00B0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00C0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00D0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................

そして、以下は手元のクリア不能バージョンの同じセクタのダンプ。

0000  14 75 05 0c 01 56 1b 09 04 77 17 1c 01 78 07 1f|.u...V...w...x..
0010  01 79 19 0d 01 85 08 10 04 77 1c 07 04 87 1e 0f|.y.......w......
0020  04 58 0e 07 04 69 1e 13 04 6b 1e 13 04 48 0f 0b|.X...i...k...H..
0030  01 89 05 16 04 6a 0f 14 04 57 19 0f 04 88 13 0f|.....j...W......
0040  04 8a 07 1d 01 7a 07 1d 01 7b 05 01 04 8b 05 07|.....z...{......
0050  01 39 1a 10 cc 0f 2f 8d 41 cc 03 0e f7 fd 16 b7|.9..../.A.......
0060  fd 15 7f fd 15 90 00 02 04 00 00 c0 03 90 02 1e|...............
0070  04 93 00 02 04 00 00 c0 03 90 02 28 06 96 00 04|...........(....
0080  08 82 03 00 01 8c 0b 32 08 ae 00 04 08 82 03 00|.......2........
0090  01 0c 0b 32 0a c6 00 04 08 82 03 00 01 0c 0b 46|...2...........F
00a0  0f 15 7f fd 15 f7 fd 16 4a b7 fd 15 7f fd 15 39|.......J.....9
00b0  34 02 64 e4 24 14 bd 2a cb cc 1d 00 b7 fc 80 f7|4.d.$..*........
00c0  fc a0 96 36 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...6.....d.$..*
00d0  cb cc 1d 01 b7 fc 80 f7 fc a0 96 35 b7 fc a1 7f|...........5...
00e0  fd 05 64 e4 24 14 bd 2a cb cc 1d 02 b7 fc 80 f7|..d.$..*........
00f0  fc a0 96 37 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...7.....d.$..*

クリア可能バージョンではオフセット$0011からの4バイトは 79 19 0B 01 とある。クリア不能バージョンでは 79 19 0D 01。この変更は、まさに上の結論の中で書いたディスクAの修正方法のひとつと一致する。

そして、ゲーム保存協会が収蔵するDeath ForceのディスクAも調べていただいたところ、問題のセクタのオフセット$0013は3枚のうち2枚が$0D (クリア不可能)、1枚が$0B(クリア可能) だった。調べたDeath ForceのディスクA合計6枚のうち2枚がクリア可能、4枚がクリア不可能だった。当時のユーザがなんらかの方法で修正方法を知り、ユーザの手で書き換えが加えられた可能性は排除できないものの、2枚ともユーザの手による改造が加えられていた可能性は非常に低く、リバーヒルソフトが初期バージョンを出荷した後でバグに気づいて修正バージョンを作ったと考えるのが自然だ。

考えようによってはヤフオクで届いたディスクがクリア不能バージョンだったからこの謎が解けたとも言える。クリア可能バージョンだったのであれば多分普通にクリアしてDeath Forceに関する噂はまったく確認できなかった。たまたまクリア不能バージョンが手元に届き、問題箇所を特定でき、ゲーム保存協会メンバーが収蔵するディスクと比較することができたので、クリア可能バージョンとクリア不能バージョンが存在することを確認できた。

コピープロテクトの誤爆なのか?

果たして、クリアできないバージョンの問題がコピープロテクトの誤爆によるものなのか、確定的な判断はできなかった。しかし、コピープロテクトの誤爆である可能性は非常に低いと考えている。そう考える根拠はマップ$79を含むステージ9のデータが読み込まれるタイミングからマップ$79に到達するまでの間でコピープロテクトチェックのためのディスクアクセスが無い点。

Death ForceのコピープロテクトはFM-7シリーズでは一般的なF6, F7セクタの存在と内容確認、読み込み後CRCエラーを確認する方法を使っている。XM7のソースコードを改造してF5, F6, F7をセクタレジスタ$FD1Aに書き込んだらブレークするようにしてテストした結果、最終ステージ突入時からマップ$79到達までの間でF5, F6, F7セクタアクセスは無かった。

従って、Death Forceがクリアできない問題の原因がコピープロテクトの誤爆である可能性は低い。

バグなのか?

自分としてはDeath Forceの初期バージョンがクリアできないのはただのバグによる可能性が高いと思う。そう考える理由もいくつかある。

最大の理由は、クリア可能なバージョンの存在が確認できた点。クリア可能なバージョンでは特別な条件を満たすこともなく問題の階段に入ることができる。

また、もうひとつの大きな理由は最終ステージの完成度が低い点。最終ステージではデータとして入口座標は存在するが壁に阻まれて入れない階段が多い。また同じ理由で入ったら二度と出られない部屋が複数ある。意図的に閉じ込めている可能性もあるが、最終面はデバッグ不足のように思う。

Death Forceは ゲームを通じてある特別な状態にのみ発動する処理が少ない。特別な処理があったのはシールを15個すべて集めた瞬間に自機の外見とBGMが変わった瞬間とラスボス戦に勝ったタイミングのみで、あとはまったく同じ法則でゲームが進む。問題の通路のためだけに特別な処理を書いただろうか?もしもそのような処理があれば他の場所でも使いそうな気がする。

そもそも仕掛けを作るとすれば見えているのに入ることができない階段を作るだろうか?初期状態で何も見えない床にしておいて、フラグが立ったら階段を出すように作るのではないだろうか。フラグによって階段がずれるというのも不自然に思える。

以上の理由により、自分としてはクリアできない問題は単純なバグによるものだと思う。

だが、仮にバグで無かったとすると、なんらかの方法でクリアが可能になるはずだ。初期バージョンには後期バージョンにない仕掛けがあり、それを解明しない限りクリアできなかったが、あまりにも難しいため次のバージョンから除去されたという可能性も完全に排除することはできない。可能性としては、なんらかの条件を満たすことによって、

(1) MAP $79の階段入り口座標($19,$0D)が($19,$0B)に変わる。

(2) MAP $79の階段のグラフィックスと壁の衝突判定が1段(2ブロック分)下に移動する。

(3) 鍵によってMAP $79の階段のグラフィックスと壁の衝突判定が1段(2ブロック分)下に移動する。

(1)の可能性はまず無い。問題の階段の入り口座標はゲームのたびにプログラムディスク(ディスクA)から読み込まれる。この座標が記録されているのはデータディスクではない。もしも(1)だとすると、その状態はデータディスクに保存できない。

(2)の可能性もまず無い。仮にそのような条件が存在するとしたら、状態がセーブできるべきだ。マップデータはデータディスク上に存在するのでセーブできる可能性もあったので、XM7上で実験してみた。まず、最終ステージに移動した状態で、$5C80~のバイトを書き換える。そのうえでマップ$79に移動して変化を確認した。その状態でF5キーを使ってセーブして、ディスクイメージにマップデータが保存されたかを調べた結果、マップデータの変更はセーブされなかった。リセットしてデータを読み込んでみたところマップは元通りだった。

(1)と(2)はセーブできないステートである可能性もゼロとは言い切れないが、他のすべてのステートがセーブできるのに、この一点だけセーブできないのはありえないように思う。

最も可能性が高いのは(3)。実は画面切り替え部分に非常に気になるコードがあって、

   2812 30 88 13       LEAX  $13,X   マップ構造体(問題のMAP $79だと$A6B0)+$13
   2815 86 06          LDA   #$06    6バイトだけ?
   2817 97 13          STA   <$13    ループカウンタ
   2819 E6 80          LDB   ,X+
   281B C1 FF          CMPB  #$FF
   281D 27 1A          BEQ   $2839
   281F CE 78 00       LDU   #$7800
   2822 4F             CLRA
   2823 33 CB          LEAU  D,U
   2825 A6 80          LDA   ,X+
   2827 D6 20          LDB   <$20
   2829 C4 01          ANDB  #$01
   282B 27 02          BEQ   $282F
   282D 9A 14          ORA   <$14
   282F A7 C4          STA   ,U
   2831 0A 13          DEC   <$13
   2833 26 E4          BNE   $2819

あるマップが鍵を消費するように設定してある場合、マップに二通りの変更が加わる。ひとつは上位2ビットが一定の条件を満たすバイトに対して、上位2ビットを違う値に置き換える。もうひとつは6バイトだけ特定の値に書き換える。後者の変更を使えば問題の階段を書き換えることができる。

MAP$79に対応するその情報は、物理アドレス$0B6B0~の48バイトに書いてある。

0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C ................
0B6C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 :02 ................
0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00 ................

このうち、オフセット$1Fのビット7が1のとき、このマップに入ったとき鍵を消費する。オフセット$13~$1Eが6バイトの書き換え(位置・新しい値のペアx6)にあたる。この部分を以下のように変更することで、鍵を持ってマップ$79に入ったとき階段が下に伸びるように

なる。

0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C  ................
0B6C0: 00 00 00 5c 2d 5d 2e 5e 2f 70 46 71 47 72 5a 82 :02  ................
0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00  ................

そして

、重要なのは、この変更はセーブしたときデータディスクに保存される。

この書き換えを実行してマップ$79に入ったときの画面はこうなる。

プレイヤーがなんらかの条件を満たすことでMAP$79のプロパティが上記のように書き換わる仕掛けがあったのかもしれない。 しかし、この方法では階段が不自然に伸びてしまう。グラフィックスの整合性を壊すような仕掛けを作るだろうか?これも可能性は低いような気がする。

いずれにしても、そのコードが見つかっていないからと言っても、存在しないとは言い切れない。だから可能性はゼロとは言い切れない。

ただ、上の可能性を検証してみた結果、自分はどれも可能性が低く、やはりDeath Forceがクリアできなかったのはただのバグの影響だと思う。

隠しコマンド

Death Forceには、起動時に大文字で”SILVER”とタイプしてからスペースを押すと、リセットするまで無敵になるコマンドが知られている。出展: http://fm-7.com/museum/database/urawaza/810101300.html

コードを解析した結果、他に二種類の隠しコマンドを確認した。ちなみに、文字の順序は関係なく、また、文字列のチェックではなく、XORとADDの結果を比べているので、同じ効果を持つ単語は他にも無数に存在する。以下のキーワードはその中からひとつ選んだものだが、意味のある文字列が存在するのかどうかは候補が多すぎて確認できなかった。なお、大文字・小文字は正しくタイプする必要がある。

いずれも、オープニングのBGMが鳴っているところでタイプしてスペースでゲームを開始することで適用できる。

“SILVER” 無敵 (チェックサム 0x17D5)

“1VKJIC” LIFゼロ (チェックサム 0x6CA8)   <- その後確認したところLIFはデータディスクから上書きされるので意味が無かった。

“ARKoxo” 無敵+鍵無限+爆弾連射可能+? (チェックサム 0x2034)

なお、以下はフィーチャー2とフィーチャー3要キーワードを発見するために使ったC++コード。根気のある人は、この中から意味のある単語を見つけ出せるかも。

// Copyright 2019 CaptainYS (soji@andrew.cmu.edu)
// Can redistribue free of charge without permission.

#include <stdio.h>
#include <string.h>

unsigned int Sum(const unsigned char buf[6])
{
    unsigned int sum=0;

    unsigned int A=0,B=0;
    for(int i=0; i<6; ++i)
    {
        A=(A^buf[i])&0xff;  // AND is not really necessary.
        B=(B+buf[i])&0xff;
    }

//   236C A8 84          EORA  ,X
//   236E EB 84          ADDB  ,X
//   2370 6F 80          CLR   ,X+
//   2372 31 3F          LEAY  -1,Y
//   2374 26 F6          BNE   $236C
    return A*256+B;
}

inline unsigned char Increment(unsigned char c)
{
    ++c;
    if('9'+1==c)
    {
        c='A';
    }
    else if('Z'+1==c)
    {
        c='a';
    }
    else if('z'<c)
    {
        c='0';
    }
    return c;
}

int main(void)
{
    printf("%02x\n",Sum((const unsigned char *)"SILVER"));

    // 17D5  "SILVER"  Invincible
    // 6CA8            LIF=0 ??
    // 2034            Invincible, Infinite KEY, Simultaneous bomb shots, +???

    char buf[7]="000000";
    while(0!=strcmp(buf,"zzzzzz"))
    {
        buf[5]=Increment(buf[5]);
        for(int i=5; 0<i; --i)
        {
            if('0'==buf[i])
            {
                buf[i-1]=Increment(buf[i-1]);
            }
            else
            {
                break;
            }
        }

        for(int i=0; i<4; ++i)
        {
            // Doesn't take two same key strokes.
            if(buf[i]==buf[i+1])
            {
                goto NEXT;
            }
        }

        auto s=Sum((const unsigned char *)buf);
        if(0x17D5==s || 0x6CA8==s || 0x2034==s)
        {
            printf("%04x: %s\n",s,buf);
        }
    NEXT:
        ;
    }
    printf("%s\n",buf);
    return 0;
}

ラスボス。両目開いてる点がそれまでのピエロと違う

エンディング。ネタバレごめん。http://fm-7.com/forum/viewtopic.php?f=3&t=71 の情報によりラスボス戦後爆弾で自爆することでエンディング。

DUR Maxがぜんぜん成長しない問題

このゲーム、DURというパラメータ以外はボスが落とすアイテムで成長していくが、DURの最大値だけはなかなか上がらない。一体どういう条件で上がるのか?DURの最大値は<$3C に書いてある。それが上がる条件を調べたところ、ダメージを受けるたびに <$48 が1上昇して、128回に1度DUR Maxが1だけ上昇することがわかった。

以下はダメージルーチン内。

   1BFC 0C 48          INC   <$48
   1BFE 96 48          LDA   <$48
   1C00 84 7F          ANDA  #$7F
   1C02 26 0D          BNE   $1C11    <- <$48をカウントアップして128回に1回しか上がらない?
                                         DURが1減ったタイミングで128回に1度?
   1C04 96 51          LDA   <$51     <- 爆弾による自爆の場合 <$51 は1.通常のダメージだと0。
   1C06 26 09          BNE   $1C11
   1C08 96 3C          LDA   <$3C     <- DUR Max読み込み
   1C0A 81 FF          CMPA  #$FF
   1C0C 27 03          BEQ   $1C11    <- オーバーフロー防止
   1C0E 4C             INCA           <- Increment DUR Max
   1C0F 97 3C          STA   <$3C     <- DUR Max書き込み

ちょっとあまりにも成長が遅い。本来であれば普通にゲームを進めて最終ステージに達するころDUR Maxも上限に達するぐらいに調整するべきだと思うけど、まともにやるとほとんど成長しない。というか、なるべくダメージを受けないように工夫しながら進めるとぜんぜん成長しない。工夫するほど不利になるのはゲームバランス的にどうかと思う。1987年当時だと今のようにゲームバランスに関する考え方が固まってなかった。

なお、DURの現在値はだまっていれば回復するが、ひどく回復が遅い。成長すると回復も速くなるかと思ったらまったくそんなことはなくて、カウンタ <$24 (メインループで毎回1ずつアップ) の値と$3FのANDを取って0のとき回復。だからメインループ64回に1度のペースで回復。大雑把に1秒につき2回復。これもあまりにも遅すぎる。戦闘のあと平気で30秒程度の待ち時間が発生するのはあまりにも苦痛だった。これも1987年のゲームだからこれでいいと思ったんだろうな。

以下はDUR現在値回復部分の逆アセンブル。

   05E5 96 24          LDA   <$24    <- カウンタ
   05E7 84 3F          ANDA  #$3F
   05E9 26 0E          BNE   $05F9   <- 64回に1度通過
   05EB 96 38          LDA   <$38    <- DUR現在値
   05ED 91 3C          CMPA  <$3C
   05EF 24 01          BCC   $05F2   <- まだ回復の余地がある場合通過
   05F1 4C             INCA          <- 1回復
   05F2 97 38          STA   <$38    <- DUR現在値書き込み

全マップ

2D Retro Map Toolを利用して作成した全マップ(クリックで拡大)。なお、Death Forceの地下のマッピングに対応するためにツールに透明色機能を追加した。機能追加版は近日公開の予定。

地上

地下

プレイガイド

序盤

ゲームの大まかな流れとしては、武器と封印を一通り回収してラスボスを倒して最後に自爆するだけ。隠し階段をいくつか見つけなくてはならない以外はとくに謎解きのような要素は無い。隠し階段は上のマップに書いてあるのでそれを参照してもらえればわかると思う。

このゲームはロールプレイングな割には最初いくら敵を倒してもプレイヤーキャラクターが強くならない。このゲームではプレイヤーキャラクターを強化するには、アイテムを取ってLIF, WEP, MOVの上限を上げ、ダメージを何度も受けてDURの上限を上げなくてはならない。LIF, WEP, MOVの上限を上げるアイテムはそれぞれL-MAX, W-MAX, M-MAXで、ボスキャラを倒すとひと手に入る。どの種類が出るのかはランダム。

また、KEYというアイテムがあり、持っていないと通行できない箇所が多数あるが、よりによって最大5個までしか持てない。たくさん備蓄してから前進ということはできなくて、補給可能な地点でマメに補給しなくてはならない。1987年当時のゲームということを考慮しても、相当な忍耐力を要求される。

なお、XM7でプレイする場合はメインシステムの$0000~$007Fのダンプを常に表示しておくと、あと何度ダメージを受ければDUR Maxが1上がるのか、いくつKEYを持っているか、自分がいるマップのIDなどを確認できるのでややストレスが減る。$0000~$007Fのうち、意味を解明したものについては下の解析結果抜粋に書いたので参考になると思う。

ゲーム開始直後は、まずLIF、MOVを温存しながらKEYを貯める。同様の作業はゲーム中何度も必要になる。それには、開始地点から一枚右のマップ(マップ$01)、とそのすぐ下のマップ(マップ$11)を行き来しながらひたすら雑魚(大きな方がアイテムを落とす)を倒してはアイテムを回収する。LIFが減ってきたら、マップ$11は壁に隠れているけれどマップ右上の方にLIFを回復するアイテムL-PACが何度でも復活するので、補充できる。

このゲームのマップには、入るたびに敵・アイテムが回復するマップと、いったん敵を倒してアイテムを回収してしまったら復活しないマップと二種類ある。敵が復活するマップではアイテムも復活する。例えばL-PACが何度でも回復するマップを利用するとLIFを一気に回復することができる。ただし、KEYを消費するマップに入ると自動的にKEYがひとつ使われてしまうので、LIFを復活させようと思って何度も出入りしているうちにKEYがゼロになっていて脱出不可能になるようなパターンがゲーム中かなり多いのでKEYを消費するマップは避けた方がいい。もちろん入るたびにKEYを取ることができるマップもあるので、そういうマップだと仮に入った瞬間ひとつKEYを消費したとしても、その分補給できるので、KEYの消耗を気にせず何度も出入りして補給に使うことができる。なお、敵・アイテムが復活するマップではボスも復活するので弱いボスが復活するマップで何度も倒してLIF, MOV, WEPの上限を鍛えることができる。

KEYがたまったら、マップ$21で8方向ショットを回収する。この武器は以後かなり重宝する。なお、敵を全滅させてしまうと8方向ショットを取った途端にボス戦(青と白のさわやかな色をしているので勝手に「ミント」と命名)に突入する。直接攻撃は避けて少し離れた位置から銃弾を連続で浴びせると多分勝てると思うが、ボス戦を回避したければ雑魚を全滅させずに8方向ショットだけ回収して脱出してくればいい。

8方向ショットを回収したら、地下経由でマップ$03に行く。ここにはアイテムを落とす敵が多い。全滅させてしまうとボス戦に入ってしまうが、全滅させなければマップから出て入りなおしてまた倒すというのを繰り返すことで、LIF, MOV, WEPを補給できる。至近距離から8方向ショットを撃つと割と簡単に倒せる。なお、補給しながらなるべく敵の攻撃を受けるようにしていると非常にゆっくりDURの上限が上がってくる。DURが表示で05ぐらいになったらボス戦にも勝てるようになってくる。マップ$03にはボスが2匹潜んでいて、雑魚を全滅させると2連戦。ただし、どちらも弱いので中央直下から8方向ショットを撃つと弾が8発のうち3発命中する。この手で結構倒せる。倒すとL-MAX, M-MAX, W-MAXのうちどれかが手に入るので、何度も倒すことでLIF、MOV、WEPの上限をほぼ限界まで引き上げることができる。

序盤の修行地点。マップ$03

この作業は序盤でやってしまった方がいい。後半でもできるけど、序盤でやっておけば後半が楽になる。ここで2~3時間ひたすらボスと雑魚を倒しまくって強化したら、次は誘導弾、爆弾、火炎放射器の強奪作戦。

誘導弾・爆弾・火炎放射器強奪作戦

2~3時間ひたすらプレイヤーキャラを強化したら、ぼちぼち誘導弾・爆弾・火炎放射器強奪作戦が決行できるようになる。地下経由でステージ8に向かう。ステージ8ではマップ0x62から地上に出る。出たマップではボス2連戦だがどちらも弱い。なんだったら地上と地下を往復して何度も倒してもいいぐらい。

マップ$62からひとつ上のマップ(マップ$52)でアイテムをすべて回収するとボス戦。このボスが強い。勝手に「水筒」と命名。とにかく、最初は直接攻撃しながら8方向ショットを撃ちまくる作戦でなんとか倒す。倒したら、マップ$52から地下に潜って、誘導弾を回収してくる。誘導弾を回収してしまえば水筒は割と簡単に倒せるようになる。壁の陰に隠れて体半分ぐらい出るようにして誘導弾を撃ちまくる。壁に隠れていれば水筒からの攻撃はほとんど当たらないので誘導弾を撃っていればそのうち勝てる。強いボスは、以後壁に隠れて誘導弾撃ちまくり作戦でほとんど勝てる。実はラスボス戦でも同じ手が通用する。

誘導弾を回収したらマップ$62まで戻って、地下に潜る。そして、適当に途中補給しながら南下してステージ7に向かう。ステージ7ではマップ$A0から地上に出る。そして、なんかして$B0で火炎放射器を回収。なお、ボスが強いので雑魚は必ず一匹残すようにして先に進む。

マップ$C1では、マップ右側から下のマップ$D1に入る。マップ$D1ではボス2連戦を避けられない。最初のボスが「目玉焼き」次のボスが「ピエロ」。どっちも強い。ここは、ホバー移動で素早く階段の壁の背後に隠れて誘導弾を撃ちまくる。

うまいこと倒せたらマップ$C0から地下へ。マップ$C0地下で重要なことがひとつ。ひとつは、必ずKEYをひとつは持って入る。もうひとつは、扉の背後にKEYが2個隠れているので必ず回収すること。地下でマップ$C0と$B0を往復しながらKEYを5個まで回復させることも可能。とりあえずKEYを2個回収したら、マップ$D0で爆弾を回収。この爆弾は重要で、エンディングを見るためにはラスボスを倒した後で爆弾で自爆しなくてはならない。

これで、弱いボス、ミント、銀、緑は至近距離から8方向ショット、それ以外の強いボスは壁の背後から誘導弾でほぼ勝てるようになる。

封印の回収

あとは封印を15個回収してボス戦に向かう。これは、マップを見てもらえればどこに封印があるか全部書いた。ただし、封印は順番通りにしか取れないので、回る順番に注意が必要。

また、武器も一応一通り回収した方が良さそう。武器をすべて持たない状態でラスボスに勝てるかは未確認。これもマップに回収できる場所を書いたので参考になると思う。

DUR Max特訓

封印と武器をすべて回収したら、あとはラスボス戦、と行きたいところだがDUR Maxを鍛えておかないとぜんぜん歯が立たない。このゲームは128回ダメージを受けることでやっとDUR Maxの内部値が1上がるという鬼仕様。なお、画面上の1は内部値の2に相当するから、画面表示のMaxを1上げるためには256回ダメージを受けなくてはならない。

いろいろ試した範囲で最も効率よくDUR Maxを鍛えることができるのはマップ$B5。雑魚(「エビ」と命名)が4匹出る他、L-PACも2個回収できる。LIFが厳しくなったら右の画面にもL-PACが何度でも復活するからLIFの回復には困らない。WEPが減ったら少し下に行ったところの隠し階段から地下に降りると何度でもW-PACを3個回収できる。

この4匹の倒し方は、なるべく横に4匹並んだ状態で8方向ショットを撃ちまくる。うまいこと行くと4匹すべてに弾が命中して少ないWEPの消費で4匹を倒すことができる。攻撃するとエビも反撃してくるのでダメージを受けるのでこれを数時間続けることでDUR Maxを引き上げることができる。

1987年のゲームなので、この作業は数時間繰り返さなくてはならない。まさに忍耐力が鍛えられるゲームと言える。

マップ$B5

ラスボス戦

DUR Maxは画面表示で最低でも50ぐらいは欲しい。そのぐらいまで鍛えたら、いよいよラスボス戦。ラスボス戦に行き方はマップ上に緑色の線で示した。

しかし、最終ステージ、ステージ9の敵の猛攻は突破できてもかなりのダメージを受けてしまう。ラスボス戦前、最後に補給できるのはマップ$7B。ここは、ボス「緑」が入るたびに復活する。しかし、マップ$7Bから唯一出ることができるのはマップ$8B。ピエロ2連戦でかなりダメージを受ける。しかし、L-PACをすべて回収して緑を倒した状態で、F5キーでセーブ。そしてF1キーでロードすると、なんと、同じ部屋から出ることなくL-PACが復活して再度緑を倒すことができる。緑はボスの中でも弱い方だからここまで到達するキャラクタならば簡単に倒すことができる。そして、L-MAX, M-MAX, W-MAXのうちのどれかを落とす。これらのアイテムは、上限を上げる以外にLIF, MOV, WEPの値を回復させる効果もあるので、ここでセーブとロードを繰り返すことで簡単にステートを回復できる。なお、セーブする前にじっとしてDURを回復するのを忘れないこと。

最後の補給地点。MAP$7B

そして、最後のラスボス戦。部屋に入ると、雑魚(「ブラックベリー」と命名)が弾を連射してくる。動きが速くて誘導弾はほとんど当たらないので、爆弾や8方向ショット、直接攻撃を適当に使って全滅させると、まず普通ボス戦(ピエロ)。ピエロを倒すと、ラスボス戦。ただし、両目が開いてるだけであとは見た目はただのピエロ。グラフィックデザインに疲れたんだろうか。

晴れてラスボスを倒したら、爆弾で自爆するとエンディング。

ラスボス戦。この辺りから誘導弾を撃ちまくるとそのうち勝てる

解析結果抜粋

メインループ

この手の解析では、デバッガで止めてコールスタックをたどっていくとそのうちメインループが見つかるので足掛かりにする。

$230~$24E  メインループ前の初期化?
$251~$277  メインループの模様
$279~$28B  PAUSE解除待ちループ

メインループ

   0251 8D 26          BSR   $0279  <- ESCを押すとPAUSE解除までループ
   0253 BD 22 0B       JSR   $220B
   0256 25 D8          BCS   $0230
   0258 8E 00 00       LDX   #$0000
   025B 9F 0E          STX   <$0E
   025D BD 0A 5F       JSR   $0A5F
   0260 BD 02 8E       JSR   $028E
   0263 BD 02 B6       JSR   $02B6  <- 移動。この中でアイテム取得も処理。
   0266 BD 15 E4       JSR   $15E4
   0269 BD 14 D4       JSR   $14D4
   026C BD 0A 28       JSR   $0A28
   026F BD 21 29       JSR   $2129
   0272 BD 2A 4C       JSR   $2A4C
   0275 0C 24          INC   <$24
   0277 20 D8          BRA   $0251

マップIDとステージID対応表

Addr   +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F Sum
7D00 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F  ................
7D10 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F  ................
7D20 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E  ................
7D30 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E  ................
7D40 : 00 00 00 00 00 00 09 09 09 01 01 01 03 03 03 03 :2A  ................
7D50 : 08 08 08 08 08 08 09 09 09 01 01 01 03 03 03 03 :5A  ................
7D60 : 08 08 08 08 08 08 09 09 09 09 09 09 03 03 03 03 :72  ................
7D70 : 08 08 08 08 08 09 09 09 09 09 09 09 03 03 03 03 :73  ................
7D80 : 07 07 07 08 08 09 09 09 09 09 09 09 03 03 03 03 :70  ................
7D90 : 07 07 07 08 08 09 09 09 09 04 04 04 03 03 03 03 :61  ................
7DA0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 03 03 :52  ................
7DB0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 04 04 :54  ................
7DC0 : 07 07 07 07 06 06 06 06 05 05 04 04 04 04 04 04 :56  ................
7DD0 : 07 07 07 06 06 06 06 06 05 05 04 04 04 04 04 04 :55  ................
7DE0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A  ................
7DF0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A  ................
----------------------------------------------------------  ----------------
Sum  : 50 50 50 4F 4C 4F 5E 5C 56 41 3F 41 34 34 33 33 :79

これにより、大雑把に、

   Stage 0           Stage 1         Stage 2
   Stage 8           Stage 9         Stage 3
   Stage 7           Stage 6         Stage 4
                                     Stage 5

であることがわかる。あれ?Stage 5ってあったっけ?

基本的な変数

メインCPUアドレス空間の$0000~$00FF (全RAM空間上の$30000~$300FF)

DP=0x00で、
+00 キーというかジョイスティックコードに対応?
+01 自機の向き (0が上。時計回りに8方向。)
+02 画面内X座標  自機が右端で$25
+03 画面内Y座標  自機が下橋で$22
+04 画面内X座標
+05 画面内Y座標
+06 ジョイスティックコードのコピー
+07 <$06の値のコピーと0と交互
+08
+09 突き状態 3で最も伸びた状態
+0A
+0B 移動のカウンタらしい。0のとき一歩進む。通常歩行では一歩進むと3になる。ホバー時は2。
+0C ボタンを押している間 <$0Dの内容と0と交互に切り替わるようだ。射撃リピート用?
+0D 最後のボタン状態(射撃、突きのイベント検出用)
+0E タイマー上位
+0F タイマー下位
+10
+11
+12
+13 汎用のカウンタ。
+14 
+15 取ったアイテムのコード
+16
+17 $0A92~$0BE1のループのカウンタ。敵キャラのループぽい。
+18
+19
+1A
+1B
+1C
+1D ステージID
+1E [$7E00+MapID]
+1F
+20 ビット0は地上/地下フラグ。ビット1は地上/地下の行き来があった瞬間に1になりマップ切り替えが必要フラグ。
    切り替え後ビット1はすぐ0に戻る。
+21
+22 MAP ID。左上が$00。下位4ビットがX,上位4ビットがY。
+23
+24 メインループで1ずつカウントアップ
+25 マップ切り替え(スクロール)で、$7800[20*(Y/4)+(X/2)]&0xC0をストアしてる。
+26 マップデータ構造体へのポインタ マップ切り替えルーチンでセット
+27
+28
+29
+2A
+2B
+2C
+2D 画面上の残敵数。
+2E これが0じゃないと階段に入れなかったり(0x01?)、ボス倒すまで部屋から出れなくなったり(0x80)。
+2F ステージ切れ目で更新の模様。意味は未確認。
+30 ステージ切れ目で更新の模様。意味は未確認。
+31
+32
+33
+34 最後に突きまたは射撃してからのカウンタ
+35 LIF
+36 MOV
+37 WEP
+38 DUR
+39 LIF Max
+3A MOV Max
+3B WEP Max
+3C DUR Max
+3D Key残量
+3E 武器フラグ
+3F
+40 シール個数
+41
+42 多分最後に取ったアイテムの種類、メッセージ全般?USE KEYもこれ使ってる。
+43 GET **** 表示のカウンタだと思う。
+44
+45
+46
+47
+48 DUR減少カウンタらしい。128回に一度だけDUR Maxが上がるチャンスがある模様。 
    これ注意してるとDUR Maxが上がるタイミングがわかる。
    シールが15個そろったときIncrement。ん?シール15個でひとつだけ?
+49 ステージ切れ目で更新の模様
+4A ステージ切れ目で更新の模様
+4B ステージ切れ目で更新の模様
+4C ステージ切れ目で更新の模様
+4D ステージ切れ目で更新の模様
+4E ステージ切れ目で更新の模様
+4F ステージ切れ目で更新の模様

解明したサブルーチン

$0694         敵キャラ/アイテムとの衝突判定
$0A14         メッセージ表示。<$42がメッセージコード  <$43が多分表示残り時間  Aがアイテム個数?
$0E87         ステージ切り替え処理。$245B(マップ切り替え)から呼び出し
$2414         D=20*(Y/4)+(X/2)  画面上座標から何かのオフセット
$241F         D=$4500+B*#$28+A  そのまま TFR D,X として、$4500からのアイテムシンボルとか敵キャラシンボルの取得用。
$2429         マップ切り替え
$27A0         マップ切り替え後移動可能範囲の更新

テーブル等

特に、$7C00からの入り口座標テーブルは役に立った。それと隠れたアイテム探しには$4500~$4B3Fのマップが役に立った。

$2109~  移動ベクトルのテーブルっぽい。
$2199~  移動ベクトルのテーブルの模様。$2199+(ジョイスティックコード&0x0F)。$FFは多分ありえない方向。
$3200~  なにかのパラメータ。多分敵関係。
$328A~  取ったシール (1バイトずつ。0か1。)
$3299~  多分敵のリストだと思うんだけど。
$3330~  マップっぽいんだけどな。短すぎる。
$38C0~  BGMのワークっぽい。BGMと連動して値が変わってるバイトがいくつかある。
$4500~$4B3F  敵キャラとアイテム当たり判定用のマップ '0' が自機。$FFが何もない空間。
              $00~$2Fは敵キャラ。$30~$7Fはアイテム。
$4C00~$4F1F? 移動判定用のマップ。多分$02が入り口。
$7800~       マップの何からしいんだけど、何であるかが不明。鍵によって変化してる。
$7C00~       最初1バイトが要素数。続く4バイト単位が階段テーブル。MapID, X, Y, 向き
$7D00~$7DFF  マップID→ステージID対応表

階段テーブル

Stage 06 階段

A4 05 0B 01 
A7 1C 07 04 
A7 17 1B 01 
C7 05 17 01 
D6 08 07 01 
D7 05 17 01 
E6 08 07 01 
F6 08 04 01 このY座標バグじゃない?

Stage 07 階段

5F 07 0F 01  バグ。マップ5FはStage 07の範囲外。
82 07 0B 04 
90 0D 15 01 
90 1B 15 01 
92 05 13 04 
A0 05 03 04 
A0 05 16 03 
A3 05 0F 04 
A3 1F 1B 04 
B2 03 0D 01 
C0 05 0F 04 
C1 21 13 04 
C2 17 09 01 
C3 07 08 01 
F0 0F 13 01

Stage 08階段

70 07 0F 04 
71 13 13 04 
71 23 0F 04 
51 0B 13 04 
52 23 17 04 
62 07 13 04 
72 1B 0B 01 
63 0B 13 04 
53 0B 13 04 
64 17 13 04 
54 19 0B 04 
74 1F 0F 04 
84 03 1B 04 
94 17 13 04 
65 21 0B 01

Stage 09階段

48 0F 0B 01 
56 1B 09 04 
57 19 0F 04 バグってて入り口は壁の中。
58 0E 07 04 
69 1E 13 04 入ったら出口が壁の中で出られん。
6A 0F 14 04 
6B 1E 13 04 
75 05 0C 01 
77 17 1C 01 
77 1C 07 04 
78 07 1F 01 
79 19 0D 01 入れない。クリア可能バージョンディスクでは 79 19 0B 01 なので普通に入れる
7A 07 1D 01 
7B 05 01 04 
85 08 10 04 バグ?座標がずれてて入れない。階段のグラフィックスはある。
87 1E 0F 04 
88 13 0F 04 
89 05 16 04 出口が壁の中で出られん。
8A 07 1D 01 
8B 05 07 01

アイテム取得処理

アイテム取得は$0694からのルーチンで、敵キャラとの衝突もこの中で判定している。なお、敵キャラとの衝突はとくにダメージなどは受けない。単に進めないだけ。

JSR $15A8で多分実際取ったアイテムのコードを取得して、11より上ならシール、以下なら通常アイテムとして処理している模様。シールだと正しい順に取らないと取れないから。これは、取りこぼしがあったらわかるように配慮しているものと思う。DUR Maxでプレイヤーを拷問しておきながら、この点は親切。通常アイテムの場合、アイテムコードからジャンプテーブル ($076B~) のアドレスに飛ぶ。そうなんだよ。6809ってBSR [A,X]ができたら良かったんだけどJSRなんだよな。あるいはレジスタをオフセットとしてBRAができたらなあ。と、ゲームとは関係なくテーブルジャンプを見るたびに思ってしまう。

   06A9 BD 15 A8       JSR   $15A8  <- 多分この中で実際どのアイテム取ったかを判定。
   06AC 81 0B          CMPA  #$0B
   06AE 22 22          BHI   $06D2  <- 11より上だとシール
   06B0 C6 01          LDB   #$01
   06B2 96 15          LDA   <$15
   06B4 BD 15 A8       JSR   $15A8  <- 多分ここでアイテム表示消してるんだと思うんだよね
   06B7 97 15          STA   <$15
   06B9 8E 07 6B       LDX   #$076B
   06BC 48             LSLA
   06BD AD 96          JSR   [A,X]

ジャンプテーブルは以下の通り。

	0  0783  KEY
	1  0790  WEAPON 0 8-Shot
	2  0790  WEAPON 1 Guided
	3  0790  WEAPON 2 Framethrower
	4  0790  WEAPON 3 Boomerang
	5  0790  WEAPON 4 BOMB
	6  079D  L-PAC
	7  079D  M-PAC
	8  079D  W-PAC
	9  07CD  L-MAX
	10 07CD  M-MAX
	11 07CD  W-MAX

またシールの取得は以下の部分。CとかC++で書いてるときは、シール個数とシールのフラグと別に持つのが嫌だから多分フラグだけにすると思うんだけど。

   06D2 8E 32 8A       LDX   #$328A  <- 取ったシールのフラグ(1バイトにつきシールひとつ)
   06D5 80 0C          SUBA  #$0C    <- アイテム12がシール0版だから、アイテムコードから12を引く。
   06D7 27 08          BEQ   $06E1   <- 0番のシールはいつでも取れる
   06D9 4A             DECA          <- ひとつ前のシールをチェックするためにA=A-1
   06DA E6 86          LDB   A,X
   06DC 10 27 00 28    LBEQ  $0708   <- DECAによってシール[x-1]を既に持ってるかチェック
   06E0 4C             INCA          <- Aの値元に戻す
   06E1 6C 86          INC   A,X     <- シール取ったフラグ立てる
   06E3 0C 40          INC   <$40    <- シール個数増やす

ステージ切り替え処理

$7C00からの内容が切り替わることでステージが切り替わったことがわかる。ボス戦後も呼ばれてるようだ。

ここで重要なのが$7C00からの階段テーブルの読み込み。コピープロテクトの誤爆によってによってラスボス戦に進出できないとする説が正しいとすれば、高い確率でここでプロテクトのチェックが入って最終ステージの階段の位置をずらすと考えられる。

しかし、このルーチンの最後に階段テーブルを$7C00~に読み込んですぐPULS B,PCで抜けている。 したがって、読み込み後テーブルには何も手が加えられていない。

もちろんまったく別の場所で特別な処理をしている可能性もゼロでは無いけど。例えばここまで解析される可能性まで考慮していたとするならば、あえて階段テーブル読み込みとその修正を離れた場所で実行する可能性も考えられる。しかし、エミュレータの無かった当時そこまで考えていただろうか?

   0E87 34 04          PSHS  B        <- BレジスタがステージID
   0E89 86 02          LDA   #$02
   0E8B B7 FD 90       STA   $FD90
   0E8E 8E 0F 31       LDX   #$0F31
   0E91 E6 85          LDB   B,X
   0E93 8E 30 C3       LDX   #$30C3
   0E96 58             LSLB
   0E97 EC 85          LDD   B,X
   0E99 10 8E 00 60    LDY   #$0060
   0E9D 8E 90 00       LDX   #$9000
   0EA0 BD 29 91       JSR   $2991
   0EA3 E6 E4          LDB   ,S
   0EA5 58             LSLB
   0EA6 58             LSLB
   0EA7 58             LSLB
   0EA8 EB E4          ADDB  ,S       <- 多分Bがステージ番号。8倍して1倍を足してるから9倍。
   0EAA 8E 0E D7       LDX   #$0ED7   <- [0ED7~] 9バイトごとのテーブルのようだ
   0EAD 3A             ABX
   0EAE EC 84          LDD   ,X
   0EB0 DD 49          STD   <$49
   0EB2 EC 02          LDD   2,X
   0EB4 DD 4B          STD   <$4B
   0EB6 EC 04          LDD   4,X
   0EB8 DD 4D          STD   <$4D
   0EBA EC 06          LDD   6,X
   0EBC 97 4F          STA   <$4F
   0EBE D7 2F          STB   <$2F
   0EC0 A6 08          LDA   8,X
   0EC2 97 30          STA   <$30
   0EC4 8E 31 06       LDX   #$3106
   0EC7 EC 84          LDD   ,X
   0EC9 EB E4          ADDB  ,S        <- Dレジスタには、トラックとセクタが入る。
   0ECB 10 8E 00 01    LDY   #$0001
   0ECF 8E 7C 00       LDX   #$7C00    <- 階段テーブル読み込み先
   0ED2 BD 29 91       JSR   $2991     <- セクタ読み込み
   0ED5 35 84          PULS  B,PC

[0ED7~]
02 02 03 03 03 04 05 00 00 
02 02 03 03 03 04 05 01 00 
03 02 03 03 03 05 05 01 01 
03 02 03 03 04 05 06 01 01 
03 03 03 04 04 05 06 02 01 
04 03 04 04 04 05 06 02 02 
04 03 04 04 05 06 07 03 02 
05 04 04 05 05 06 07 03 03 
06 05 05 05 06 07 09 04 03 
07 06 06 06 06 07 0A 06 05

ボス戦

ボス戦は通常モードとメインループは共有。ボス戦用に別なループは存在しない。ボスのいるマップで敵を全滅させてしまうと、ボスを倒すまで出られない。ボス発生条件は敵全滅かつ全アイテム取得。敵を全滅させてしまうと、アイテムが絶対取れない場所に存在するため二度と脱出不能になる箇所が多数あった。(あ、だけどだからセーブしてロードするとフィールドの敵が復活するのか?それはまだ試してなかったけど、もういいや。)

ボスがいるか、いないか、または何匹目のボスか(1画面に最大2匹のボスがいる)は、<$2Eに書いてある。$80以上の場合、ボスを倒すまで出られなくなる。ボスを倒す前の状態では <$2Eは01となり、画面から出入りできるが階段に入ることができない。その値をコントロールしているのは、以下の場所。

0F61 LDA #$80   STA <$2E               アイテム全取得→ボス戦まで出られない状態

0FDF LDA #$81   STA <$2E               ボス発生時、$80->$81

106B LDA #$01   STA <$32    STA <$2E   画面から出られるけど階段に入れない状態

11D5 LDA #$82   STA <$2E               2匹目ボスのようだ。

11FE CLRA       STA <$2E    STA <$32   どうも<$32と<$2Eは連動してるのか? <$32の意味は未確認。

ボス発生ルーチンは$0F69~

   0F69 96 2E          LDA   <$2E
   0F6B 10 2A 01 19    LBPL  $1088      <- ボスがいないならスキップ。
   0F6F 81 80          CMPA  #$80
   0F71 10 22 00 6E    LBHI  $0FE3      <- 既にボス戦中?
   0F75 96 23          LDA   <$23
   0F77 9B 2B          ADDA  <$2B
   0F79 9B 52          ADDA  <$52
  >0F7B 10 26 FC 66    LBNE  $0BE5      <- <$23, <$2B, <$52の合計が0以外でもボス発生しないが、意味は未解明
   0F7F F6 32 99       LDB   $3299      <- ボスのID
                                           0: ミント
                                           1: 緑の脳みそ
                                           2: 銀の脳みそ
                                           3: 目玉焼き
                                           4: 水筒
                                           5: ロボ
                                           6: タコ
                                           7: ピエロ      ラスボス戦1匹目
                                           8: テントウムシ
                                           9: ピエロ
                                           A: ピエロ      ラスボス戦2匹目
   0F82 D7 13          STB   <$13

なお、敵の名前は僕が勝手に命名した。おもしろいのは、デバッガで$0F82で止めてBレジスタの値を書き換えると好きなボスを発生させられること。$0Bにしたところグラフィックスが崩壊した点、またラスボス線では値が$0Aだったこと、テーブルが11個分しかないことなどを考えるとボスは$00~$0Aの合計11種類と思われる。ラスボス($0A)以外にピエロが二種類いるのだが、ひょっとすると気が付かなかっただけで両方と対戦していたか、あるいは9番のピエロは未使用かもしれない。

なお、ボスのデータはふたつのテーブルに書いてある模様。$30F0からの2バイト単位のテーブルと$30CFからの3バイト単位のテーブル。意味は未解明。

$30CF
0 1F 01 16 
1 20 07 16 
2 21 0D 16 
3 43 02 24 
4 45 06 16 
5 46 0C 24 
6 48 10 24 
7 4B 04 16 
8 4D 0A 24 
9 4B 04 16 
A 14 03 16

$30F0
0 3C 0B 
1 46 1B 
2 64 1B 
3 78 05 
4 96 0F 
5 AA 05 
6 BE 05 
7 C8 0F
8 BE 05 
9 D2 1B 
A E6 1F

※パッケージやゲーム画像などの著作権は著作者に帰属します。

ゲーム保存協会 Game Preservation Society特定非営利活動法人ゲーム保存協会
ライター:山川 総司(賛助会員)
ホームページ:http://www.ysflight.com

【PasocomMini PC-8001使用レビュー】 パソコンミニでプログラムをつくってみた 編

はじめに

以前からサポーター会員の形でゲーム保存協会に協力していましたが、理事長さまからの強い要望により :) 昨年から正会員に加わりました UME-3 と申します。

今回、私の方にもパソコンミニの記事を書くよう仰せつかりまして、筆を取らせていただくことになりました。

 

私が主に興味のあるパソコンミニの技術的な話は、ウルサイ人(笑)がこれ以上ないくらいすばらしく濃い記事を既に書かれています。

他のいろいろな記事では、パソコンミニの紹介や使い方、内蔵ゲームソフトのレビューなどの記事が多いようです。

 

もちろんそれはそれでよいのですが、パソコンであればプログラミングも大きな楽しみ方の一つではないでしょうか。

パソコン黎明期には、ソフトというのは自分で作るものでした。マイコンBASICマガジンなどを参考に、ぽちぽちとBASICプログラムを作った経験のある方も多いことと思います。

内蔵のゲームやカセットテープから変換したゲームで遊ぶのもよいですが、せっかく電源ONですぐに使えるBASICがあることですし、ひとつなんか作って記事にしてみようか、と思い立ちました。

 

なお、私自身はPC-8801のN88-BASICやマシン語は扱っていますが、PC-8001のN-BASICを本格的に使うのは初めてです。

N-BASICでは画面クリアの命令がなくPRINT CHR$(12);で代用が必要とか、N88-BASICにはある便利な編集命令がないとか、変数は最初の2文字しか認識しないとかの制約はありますが、おおむね主要な命令はN-BASICにも実装されていましたので、慣れればそれほど困惑することもありませんでした。

 

 

ネタを考える

作ってみようと思いついても、まずはネタ出しが必要です。

さて何を作ろうか…と考えてみたわけですが、締め切りのある限られた時間内でアイデアをひねり出すというのはなかなか難しいものです。

少し考えた結果思いついたのが、学生の頃、つまらない授業の合間に:) ポケコン(PC-E500)のBASICで作ったワンキーゲームでした。

2000年頃までは定期的にポケコンの電池を交換してプログラムを維持していたのですが、それもやがて失われてしまい、当時のプログラムは現在は残っていません。

PC-E500用のデータレコーダやRS232Cのインターフェースは当時は持っておらず、バックアップも取れなかったのです。

 

しかしながら、当時作ったアイデアと、どうやって作ったかは今でもなんとなく覚えています。

それをベースに、ぽつぽつとプログラムを作り始めたのでした。

 

そのプログラムとはホームラン競争のゲーム。

単純に来たタマを打ち返すだけの単純なものですが、そこそこ丁寧に作ったこともあり、作り始めてから1週間くらいかかりました。

エミュレータやPC-8801実機などのN-BASICで作ってからパソコンミニに持ってくることもできるのですが、それだとパソコンミニの評価にならないので、製作はすべてパソコンミニ上で行いました。

 

 

まずはキャラクタパターン作りから

このゲームの元となったアイデアは、ノーマルキャラクタを連続でアニメーションぽく表示すれば、武骨なデザインでもそれっぽく見えるのではないか? という思いつきからです。

当時流行っていたス○2とかのキャラを、ポケコンの狭い画面で表示して、○どーけん とか ○ょーりゅーけん とか、雰囲気で動かしたりしていました。

 

というわけで最初にとりかかったのは、投球動作とバッティング動作のキャラクタパターン。

6つくらいのパターンを連続で表示するようにして、動きをチェックするところから始めました。

PC-E500の時は表示画面のサイズの関係で2×2で作った記憶がありますが、PC-8001だと画面が広く使えるので、40桁モードの3×3でデザインしています。

どんなもんかな。

 

 

キャラクタのパターンを作るには、できるだけ多くの種類の文字が使えた方がよいですが、ここで SHIFT + @, \, [, ] で入力する ~, |, {, }の各文字が入力できないことに気がつきました。

「バグじゃないのこれ」と思ったのですが、実はPC-8001初代やPC-8801のN-BASIC ver 1.2までは実機でもこれらの文字は入力できないのが仕様だそうで、後のPC-8001mkIIやPC-8801mkIIでは直接入力できるようになったのだそうです。なんと。

こんなところも後継機で地味に改良されたんですね。

 

直接入力できない文字は、

KEY 1,CHR$(&H7E)

などと、ファンクションキーに定義してから、F.1キーを押すことで入力するような小技もいまだになんとなく覚えています。

 

 

パソコンミニの公式のサポートサイトのキーボードレイアウトの説明は、2019/11/3現在では間違いがあるようです。

Insert,Delete,Home,End の各キーは実際にはINS,DEL,HOME,CLRとしては効きませんし、GRPHモード時のテンキー部の割り当てが、実際に入力されるキャラクタがWebの説明と違うようです。

101/106キーボードの説明ではUSBキーボードの刻印が表示されていないので、標準的なキーボード以外を使用している場合はどれがどのキーに相当するものなのか読み取れないのも困るところです。

USBキーボードとPC-8001で割り当てが違う特殊なキーについては、キーボードレイアウトの図に加えて、PC-8001とUSBキーボードのキー割り当ての対応表を掲載しておくと、より利便性が高まるのではないかと思います。

 

 

タマを投げて打つ

 

次に、スペースキーでタマを投げ、来た球を打ち返す部分を作ります。

ピッチャーとバッターを立たせて、ピッチャーがタマを投げ、バッターにスイングさせます。

燃えプ○のように「バントでホームラン」とかは実装として明らかにおかしいので、スイング開始からスペースキーをずっと押しっぱなしにしないと打球が飛ばないような仕組みもこの時に入れています。

ファ○スタでやるような、振り遅れを防ぐためにハーフスイング状態で構えても、やっぱり飛ばないようにしました。

簡単なゲームですし、ズルは許さんぜ。

 

スイングの動作が特定のパターンの時に当たるようにしていますが、このときのタマの位置で飛ぶ角度が変わるように仕込んでいます。

スイングを始めてから当たるまでに少しタイムラグがあることで、ゲームっぽくなった感じです。

 

 

タマを飛ばす

 

当たり判定をつけたら、次はタマを飛ばす動作です。

 

PC-8001のN-BASICにはテキスト画面を代用した160×100ドットのローレゾグラフィックがBASICの命令で手軽に使えます。

これは1つの文字キャラクタを 2×4 ドットのグラフィックパターンとして使うものなので、色をつけるのもこの1文字に相当する 2×4 の単位でしかできず、また色を変える回数も1行あたり最大20回までの制限があります。

後継のPC-8801のN88-BASICでは高精細なグラフィック画面がついたせいか、このようなローレゾグラフィックの命令はサポートされなくなってしまったのはちょっと残念ですね。

 

打ったタマの描画ですが、当時は理系の学生だったのでベクトルやら三角関数やらは普通に理解していたのですが、30年以上経過した昨今では「あれ、どうやって計算するんだっけ」と思い出すまで、頭の中の引き出しをひっかきまわす必要がありました。

結果的には大したことはやっていないんですが…。

 

また、単純に地平線だけで放物線を描くと、ゴルフみたいな感じになってしまうので、外野にフェンスをつけました。

が、このフェンスの当たり判定について、後にデバッグで苦労することになります。

 

 

最初はゲームとして、乱数の要素を入れずに純粋にテクニックだけで遊べるようにしたかったのですが、これだけ単純な動作だとタマを散らす要素が少なく、弾道がいつも決まったようなパターンになってしまいました。

そこで仕方なく乱数の要素を付け加えました。打球の初速と角度に色をつけることで、そこそこ弾道が散るようになったと思います。

 

 

 

その他の飾り

ゲームの本質と関係ありませんが、やっぱり体裁を整えるにはタイトル画面やらリザルト画面はあった方がいいかと思います。

手抜きですが、それっぽく実装しました。

N-BASICには行の色を反転するLINE命令があるのに気がついて、せっかくなので使ってみたり。

リプレイの確認のキーはY,y以外にもいろいろなキーが効くように、INSTR関数の小技を使ったりもしています。

 

 

ひたすら調整

タマを投げて打って、という基本的な動作ができるようになったところで、ここからはひたすら調整とデバッグです。

 

フェンスをつけたものの、フェンスの当たり判定がおかしくてタマがフェンスを突き抜けてしまったり、フェンスを越えたタマがおかしな動きをするようになったりしたので、その辺を地道に修正。

フェンスの位置や高さも変えられるようにして、最終的にフェンスは少し高めにしました。

横○スタジアムくらいの高さにはなったかな。フェンスの色を青にしたのもその影響です。

 

乱数の要素や初速等を調整して、なかなかホームランが出そうで出ない、くらいのいい感じになるようにしたつもりですが、どんなもんでしょうか。

このあたりはひたすらタマを投げて打って調整して、を繰り返していました。

 

キャラクタのパターンはもっといい表現ができないか、何度かいじっています。

なんとなくパターンの枚数も1枚増やして7枚にしてみたり。

 

いいアイデアが浮かばない時は、一晩ゆっくり寝るのがいいですね。

プロ生ちゃんの言うとおり、「プログラミングは心の整理整頓的な何か。」なのかもしれません。

 

ある程度満足したところで、完成とあい成りました。

 

 

 

つくってみて

 

というわけで、パソコンミニで1本プログラムを作ってみたわけですが、もろもろの所感など。

 

  • パソコンミニでの全体的なN-BASICの動作速度は、実機と比べてやや遅く感じました。

これが意図的にそのように調整されたものなのか、エミュレータの動作の都合によるものなのか、ラズパイZEROの能力によるものかはちょっとわかりません。

が、調べたところによると、どうもPC-8001初代とPC-8001mkII以降でも動作速度は異なるようで、処理によってPC-8001初代の方が遅かったり、PC-8001mkII以降の方が遅いケースなどもあるようです。パソコンミニではPC-8001初代に合わせて調整されているのかもしれません。

 

  • キーを押しっぱなしにしたときのキーリピートが効いたり効かなかったりするようです。

パソコンミニではPC-8001には本来存在しない←や↓のキーを押した場合、利便性を考慮してエミュレータが内部的にそれぞれSHIFT+→、↓のキーが押されたように変換しているようです。

このためファンクションキーを表示している状態(CONSOLE ,,1)ではファンクションキー表示が一瞬反転します。ここまではマニュアルにも記載があります。

ですが、キーリピートの不具合(?)か、キーを押しても表示が反転するだけでカーソルが移動しなかったり、SHIFTが判定されずに逆方向へカーソルが動きだすことがしばしばありました。
どうもキーを取りこぼしているような反応に見えます。ファンクションキー表示がされている状態では特に顕著です。
ファンクションキー表示を消す(CONSOLE,,0)ことで軽減できますし、表示のちらつきもなくなりますので、プログラムを行なうにはこちらの方がよいかもしれません。

キーリピート関連ではもう一つ、バッティングの際にSPACEキーの入力に当初INKEY$で検出しようと思って実装したところ、キーリピートが効かず1回分の入力にしかならなかったので、これもエミュレータの不具合かなと思ったのですが…

こちらはまた別で、実はここにもN-BASICの仕様が隠されていました。
(N-BASICはN88-BASICと似ているようでところどころ違うのでつまづきますね…)

実はN-BASICではINKEY$でキーリピートが効かないのは仕様だそうです。なんと。
N88-BASICや、他の機種でもINKEY$はキーリピートの効く機種が多かったので、びっくりしました。

今回のゲームではINKEY$の代わりにINP関数でキーボードI/Oを直接読むようにしましたが、これは正しい対応だったということになります。
なお、INKEY$の代わりにINPUT$(1)を使ってもキーリピートは効くそうです。

 

  • プログラムのセーブはCMTファイルへのセーブのほかにステートセーブが使えるのは非常に便利です。

テープイメージからCLOADしなくても、前回の続きからプログラミングができます。

気をつけるべきは、ロードしようとしたにも関らずうっかりセーブを選択して上書きしてしまったりすること。またその逆もありえます。

これは作成中本当に注意して操作していました。もし実際にやらかした場合、ロードしようとしてセーブした場合はせっかく作ったプログラムがきれいさっぱり空の内容で上書きされてしまいますし、逆にセーブしようと思ってロードした場合はせっかく修正したプログラムが修正前の状態に戻ってしまいます。

プログラム作成中はステートセーブだけに頼らず、バックアップの意味でもCMTイメージへのセーブを併用するようにしていました。

 

不具合については私が使ってみた時点(初期版)ではこのような現象があった、というだけことですので、今後のバージョンアップなどで解消されるかもしれません。

期待したいところですね。

 

パソコンミニは、当時のBASICがメーカーに公式に認められている環境として使用できる点でも貴重なものだと思います。

今更40年前のBASIC? なんて声も聞こえてきそうですが、手軽に扱えますし、アイデア次第で「おおっ」というものも作れるかもしれません。

楽しいゲームのプレイの合間に、たまにはものづくりの楽しさに触れてみるというのはいかがでしょうか。

 

ゲーム保存協会 UME-3

 

【おまけの付録】プログラムリスト

UME-3さんのご厚意により、今回の製作したプログラムリストとCMTファイルを公開いたします。
宜しければこちらのデータを使って皆さんも遊んでみてくださいね。

 

▼ダウンロードはこちら

シフトJISだとグラフィックキャラクタが文字化けしてしまいますので、プログラムは画像形式でも用意しました。

Koei

【PasocomMini PC-8001使用レビュー】理事長ルドンが語る!ゲームの歴史と発展を感じる、光栄ゲーム編

はじめに

みなさん、こんにちは。ゲーム保存協会理事長のルドンです。
実はソフトのデジタル化部分でPasocomMini PC-8001(以下勝手に「8001ミニ」)に協力していたゲーム保存協会。
先日からシリーズでお届けしている8001ミニのレビュー第3弾ということで、今回は私がゲームの歴史にも触れながら、PC-8001作品の魅力などお伝えしたいと思います。

すでにお手元に8001ミニをお持ちの方もいらっしゃることでしょうが、簡単な操作方法などを確認するビギナー向けのレビューはこちら、そしてマニアックな設定など上級者向けのレビューはこちらの記事をご覧ください。

 

8001ミニの見どころはここ!

まずは今回の8001ミニについて、レトロPCを愛するあまり海を渡った理事長の推薦ポイントをご紹介します。

 

①本体のディテールがよくできている!
小さいくせになかなか細かく作り込まれていて、見つめていて飽きませんね。

 

②ほぼ電源を入れるだけでBASICが立ち上がる!
PC-8001のエミュレーターはこれまでも存在していましたが、非公式エミュレーターでは実機からBASICやロムを吸い出さなければならず、ゲームを遊ぶ以前のハードルがとても高いですね。
今回の8001ミニは公式公認、マイクロソフトやNECの協力のもとに生まれたありがたい逸品です。
簡単操作の実現で、誰でも気軽に懐かしのPC-8001のゲームを遊ぶことができます。

 

③テープがあればすぐ遊べる!
副理事長の福田先生が言っていますが、今回の8001ミニはご自宅にPC-8001用のソフトのテープがある人なら、録音をするだけでコンバートしてくれて遊べます…といいつつ、細かな手間が色々あります。
今回のレビューで取り上げる2作品は、パソコンでコンバートしたものを使用しています。

 

ここが残念、アップデートに期待?

とってもかわいい8001ミニですが、あと一歩理想に届かず、という残念ポイントもございます。

①ディスク未対応
カセットテープには対応していますが、残念なことにディスク作品は遊べません。
イカツイ箱ケースに巨大な地図が入ったとっても渋い「珊瑚海海戦」や、Siriの走り「エミー」など、せっかくならば遊びたいソフトたちは今回は出番がありません。
ちなみに言えばPC-8001mkIIやPC-8001mkII SRも未対応、ちょっぴり幅が狭まり残念なポイントでしょう。

 

②ケーブルが入っていない
ゲーム保存協会のラボで使っているケーブルはどれも高品質の新しいものを用意しています。
今回、8001ミニをモニターに繋げようとHDMIケーブルを出したところ、ラボにある新しいケーブル類が軒並みダメで、いろいろ試してもまったく画面が映りませんでした。
急ぎ秋葉原まで走り安物の古臭いケーブルを買ってくる羽目に。
お値段高めの新しい良いケーブルは使えない、そんな縛りがあるのなら、メガドライブミニのようにせめて一本ケーブルを付けてくれても良かったのでは、と思います。

 

③自分で追加したゲームの起動がちょっと面倒
家にあるテープが遊べる、ということで意気揚々と準備した皆さんの中には、少なからず「あれ?どうやって起動するんだ?」となった方もいらっしゃるのでは。
付属のゲームと一緒に一覧に出てきてくれると良いのですが、そこには出てくれないのでちょっと不便。昔のゲームは起動のコマンドなどゲーム毎に違うので、それも確認していると結構手間になります。
セーブステート機能で登録しておく方法がありますが、ちょっぴり手間だなと思います。
もし今後アップデートされたら一回起動したゲームをF12画面に登録出来たらベストです。

 

④ 画面キャプチャ―機能、ポーズ機能がない

通常のエミュレーターならいとも簡単にレビュー用の画像を取ることができますが、このPC-8001miniにはポーズもキャプチャー機能もついていません。今回、レビューのための写真を撮るのに、カメラをセットし画面反射を抑えるための調整を行い、自分が遊んでいる横でシャッターを押して画面をパシャリとしてもらう必要があり、我が家では画面の外で夫婦喧嘩が勃発する騒ぎとなりました。自分は遊んでいるだけで、人の手を借りないと何もできないダメ夫と思われる可能性があり、ここはぜひアップデートで機能を追加して、我が家の平和を守ってもらいたいです(笑

 

セーブステート機能を使いこなそう

自分で読み込んだゲームを快適に遊ぶのに必須の技、セーブステート機能を使ったゲームの起動方法をご紹介しておきます。

無事にコンバートされた名作ゲーム達まずSDカードのコンバートフォルダにCMTファイル(T88)を入れましょう。
8001ミニのOPTIONからコンバートメニューを開き、コンバートします。
変換されたものがメディアメニューに出てくるので、これでセットが来ます。

ゲームのロード方法は各ゲーム毎に違います。
付属の説明書やメディアに書かれた説明を読んで、ロード手順を確認しましょう。

 

ちょっと面倒だが、ゲームを読み込むための入力例

ゲームが起動したら、ステートメニューを開き、F1でSAVE STATEを作成します。
そのまま現在のエミュレーターの状況を保存してしまえば、ここからすぐにゲームをはじめられます。
最後にSAVE STATEの上書をされないよう、F6(プロテクト)でロックをかけます。
今後はこのセーブデータから、一発でゲームが遊べるようになります。

 

起動に成功したら、まずセーブを!

 

 

★いよいよゲームレビュー

さぁ、準備ができたらいよいよゲームを遊んでみましょう。
今回は光栄マイコンシステムからエポックメイキングな2作品をご紹介。
日本のゲームの歴史的発展を感じてください。

■クフ王の秘密(ブランド:光栄マイコンシステム)

 

「クフ王の秘密」当時の豪華なパッケージ

皆さんはRPGというと何を想像しますか?ドラクエやファイナルファンタジーなどの和製RPGは現在海外ではJRPGと呼ばれ、本来のRPGとは別物として区別されていますが、今回ご紹介する「クフ王の秘密」は、日本にまだRPGというジャンルが知られていなかった時代に生まれた“ロールプレイングゲーム“で、日本のRPGの恐竜時代の一本です。

 

紙パッケージの裏側

そもそもRPGといいながら、こちらのゲームROLL PLAYING GAMEと書かれています。
ROLEじゃないらしい。(笑)

 

光栄マイコンシステム時代のタイトル画面

体力や食料などはランダムで決まる

ゲームの内容は、罠や敵のいるダンジョン(ピラミッド)を探検する、アドベンチャー要素満載のパズル系です。
高度に象徴化されたプレイ画面はすべて真上からの図、白い謎の記号がプレイヤー、椅子や机はすべて□です。
置いてあるものを動かすと、鍵やアイテムが現れ、罠や敵をかわしながら進むゲームです。

 

思わずマッピングがしたくなる画面

まず「O」(オープン)を入力して扉を開きましょう

現代ならRPGとは呼ばれないかな、と思う内容ですが、日本は昔からRPGの定義が何なのかよくわからないまま名称だけRPGと取ってゲームを作ってきた歴史があります。

 

ENTERキーで可能なコマンドが表示される

家具をopenすると鍵が出てくる

もともとRPGというのはテーブルトークRPGというテーブルゲームの一種から派生しています。
TRPGなどと呼ばれるジャンルで今でも愛好家がいますが、ゲームの間、プレイヤーはキャラクターを作り込んで、用意された世界観の中で過ごします。テーブルを囲み、紙と鉛筆など限られたものを使って遊ぶため、プレイヤーの想像力が求められる遊びです。

 

最初は攻撃力がないので逃げよー!

武器を発見!罠に注意しよー

ビデオゲームにおいて、用意された世界観の中で何らかの行動を行うという要素は、もちろんスペックの制約など様々な点で想像で補わなければなりませんが、どんなゲームでも必ずついてくる要素です。
画面の中に作られたもう一つの場所で何かをする、というのがビデオゲームですから、当然、何らかの世界観の中にプレイヤーは放り込まれることになります。

 

こまめに色々と動かしてみると貴重なアイテムが出てくる

どうしても開かない扉には、謎の呪文を使おう

ですからビデオゲームにおけるRPGの特異性は、むしろ「キャラクターを作り込む」という部分にこそあります。
特に海外ではここが強調されており、RPGといえば様々なパラメーターを操作して、ある世界観を楽しむための自分だけのキャラクターを作るタイプのゲームのことを指しています。

 

罠!墓から強い蛇が出てきた

剣があればサソリなんてもう怖くない

ですが、なぜか日本ではこの視点が抜け落ちてしまい、現在ではドラクエのような「与えられた物語があって、たまにアクションがあるけど、基本ゲーム内に仕込まれたパズルを解きながら進むゲーム」がRPGと呼ばれていたりします。
見てすぐにわかるパズル的なパズルでなくても、物語を進めるために必要な動きなどがあるものがほとんどでしょう。

 

王の部屋はもしかして…倉庫番?食料がもう限界だ

どうにか墓まで辿り着いた…開けると?

「クフ王の秘密」はそんな日本のRPGの祖と言えるでしょう。海外目線なら「ダンジョンクロウル」(ダンジョン巡り)というジャンルに入ると思います。
「クフ王の秘密」では、ゲームの最初にプレイヤーのパラメーターをランダムに決める仕組みがありますが、それ以外は、ダンジョンのパズルを解きながら進む、なんとなくアドベンチャーっぽいパズルゲームです。
「ROGUE」(ローグ)のようにランダムではないためにマッピングすればクリア出来るゲームです。
忙しい人はSAVE STATEで使いこなせながらぜひクリアしてみましょう。

 

はい、ミイラの一撃をいきなりくらって死んだ(笑

■ジャイラス(ブランド:COMIX)

ジャイラスの当時のジャケット

光栄の新しいコミックスブランドで出されたシューティングゲーム、ジャイラス。
COMIXブランドは気軽に遊べてヤングなイメージのあるゲームシリーズです。他に「ローリングペーパー」や「マイ・ロリータ」などが有名です。
この時代の作品としてはシューティングとしてまともに遊ぶことができる良作ですが、今回はジョイスティックが必須です。
もともと実機にはジョイスティックなんてないのですが、ないとクリアできる気がしません。自信のあるプレイヤーはテンキーで頑張ってみてください。

 

素敵なジャケットの裏側

光栄のネタなのでしょうか、今回も非常に酷いなんちゃって英語が随所に見られます。
家庭でシューティングゲームが遊べるワクワク感、重層的な背景、PC-8001のスペックでは文句ないしなかなかに味わい深い一作。

 

歴史に残りそうな素晴らしい英語

敵の動きがとても個性的

光栄のネタなのでしょうか、今回も非常に酷いなんちゃって英語が随所に見られます。
家庭でシューティングゲームが遊べるワクワク感、重層的な背景、PC-8001のスペックでは文句ないしなかなかに味わい深い一作。

 

時間が経つと敵が自動的に変わる

風景の色も変わって見難いパターンもある!

さて、このゲームの面白いところは「シューティングのくせに、何かやらなきゃいけないらしい」というところ。ところどころ、心の折れそうな敵の襲撃の合間にAIRPORTという表示が出てきます。

 

エアポートが現れたので着陸してみよう(ムリ

倒せない敵が現れたので上手く避けよう

滑走路的な開けた場所が出てきますが、地面に降りようとすると爆発して死に、また、機首をどこかに引っ掛けろと説明書に書いてありますが、どう頑張ってもどこにも引っかかる様子がなく、エネルギー補給が出来ぬまま2度、3度とAIRPORTを通過しました。そのたびに撮影で待ち構えていた妻から舌打ちが聞こえ、非常に気まずかった。

 

この敵は難易度が高いけど点数が稼げるチャンスとなる!

着陸ができる…はずだが一度も成功出来ずぅぅ。誰か教えて!(笑

どなたか、このゲームのAIRPORTでの給油方法を教えてください!
その謎さえ解ければ、シューティングとしては様々な敵の動きが愉快な良作だと思います。
敵のバリエーションは多くて、2周目からは重ねて攻撃してくるので熱い!当時としてはとても充実したゲームだと思います。

 

進むと風景が街に変わる(ときどき鳥居が見える)

そして敵が重ねで出てくるのでプレッシャーがマックス

ちなみにマニュアルによるとゲームの目標は謎の光(シャイア・エンタ)を取ることだそう。成功するとワープして次の作戦に進めます。
2周目の最後に出てきますが、突然出てきて、取れないとまた1周目からやり直しの罰ゲームです。

 

目標の謎の光がやっと出たぁ!と思ったら2秒で消えた(笑

最後に

今回、ゲーム保存協会は8001ミニ発売のために、10本ほどのソフトのデジタル化で協力をしています。
80年代のPCを中心に多くのソフトやハードなどの機器を保存保管したアーカイブを持つゲーム保存協会ですが、ゲームは遊ばれてこそ。
長期保存に向けて最良の保管方法でアーカイブしてきたソフトたちが、8001ミニを通して皆さんのお手元に再び戻っていけたことに、大きな喜びを感じます。
これから第3、第4のミニマイコンプロジェクトが続けば良いなと期待しています。

 

ゲーム作品の歴史を紐解けば、今の日本のゲーム文化を支えるものは、コンシューマーが普及する以前のマイコンゲームであることがわかります。
一般の家庭で気軽に遊べるゲームと、そうした家庭での遊び方に合わせ様々な試行錯誤のもと発展したゲーム文化を俯瞰するなら、この時代のPCソフトを遊んでみることが一番です。

 

80年代PCとそのソフトは、貴重な歴史資料ですが、今回紹介した光栄のソフトだけでも、他にたくさんの歴史的に重要なタイトルが並びます。ぜひ、8001ミニで遊んでみてください。
「ドラゴン&プリンセス」、「ダンジョン」、「ナイトライフ」…
もちろん、光栄といえばこの1本…「信長の野望」!
この名作も、PC-8001から始まりました。

 

ミニマイコンで広がるゲームの歴史研究。
ぜひ、皆さんも色々な作品を遊んで、その歴史的背景や技術的発展の様子、クリエイターの豊かな発想と試行錯誤の数々を体感してみてくださいね。

 

NPO法人ゲーム保存協会
理事長 ルドン・ジョゼフ

※パッケージやゲーム画像などの著作権は著作者に帰属します。