CASIO pocket computer
PB-100の宇宙

5 LINE RPG
ゴーストオブイツシマ
by PBロッキー

公開日 2023/04/18

このページの情報はゲームのネタバレを含みます.まずはゲームを存分に遊んだ後に読むことをお勧めいたします.

改造情報

初期値を強くする

2行目の第1ステートメントの数値を増やすことでプレイヤーの強さの初期値を変えることが出来るぞ.ゲーム開始時点の死にやすさを理不尽に感じる場合には変えてみよう.

2 H=A+16

バトルの展開を遅くする

バトルの展開が速すぎると感じる場合、5行目の π を次のように移動させることで遅くすることが出来る.逃走の判断がし易くなるだろう.

5 T=5+P*π:P=RAN#

1ステップ減らす! 但し、エンディング後に再開すると「死霊使いの呪い」が…!?

2行目の Fin. の前のスペースを詰めて、3行目の24を23にすることで、総ステップ数を1ステップ減らすことが出来る.

この変更をしてからクリアして再開すると、プレイヤーの HP が0になってしまう.死霊使いを倒した地点からクリスタルまで慌てて逃げ帰る、おまけゲームと思えばいいかな.但し、ほぼ運試しです.

2 $="...*♣Fin."
3 A(J)=23

詳細解説

フローチャート

draw.io で編集する(2023/08/21 追記, 2024/03/14 チャート微修正)

単一の IF THEN 文がつくるループをまわり続けます.2~5行は、フィールド移動か、バトル中かに関わらず実行されるのが特徴です.

このシンプルなフローをグレートループと呼んでいました.「グレートリセット」という単語が耳に残ったのが命名の由来です.

各変数に割り当てる役割と被破壊変数のメモ

小文字の変数は常に0です.角括弧内([])は行番号とステートメント番号です.

       :  T   U   V   W   X   Y   z
U      :              0   1   2     ⇐ HP設定[L1-5]
G(9-T) :  n . .   .   J   i   H   g ⇐ 攻撃力[L2-3]
T      :  2   .   .   5   6   7   8 ⇐ R(T) [L2-3, L3-1]
J      :  .   20                    ⇐ Fin.設定[L3-4]
J/5    :          0   .   .   3   4 ⇐ X座標表示/敵HP表示[L4-2]
U/5    :  .   1   2   3   .         ⇐ バトル終了, X座標を0[L4-4]

1行目

フィールド移動、敵との遭遇、敵とプレイヤーの HP の設定を押し込んでいます.

V=V+B(X)

X 座標(V)を更新します.B(X)X=1 の場合に -1 ないし 1 です.X が 0, 2, 3, 4, 5 の場合は 0 になります. この為に B, D, E, F, G は常に0です.

V は常に整数で、負数にもなります.

:U=ABS SGN V:T(T*U/8)=2

この2つのステートメントで、回復地点(X 座標:0)とこれ以外の地形を分けるフラグを用意し、敵出現判定が行われ T, U は次のように変化します.

5行の RPG を成立させるにあたって興味深い働きをしている部分です.このコードは試行錯誤している内に現れました.

変数X 座標:0()フィールド移動敵に遭遇
T228~8.1416
U012
:J=3*FRAC EXP V+U↑4

J はキャラクタの番号です.敵の場合は攻撃力も兼ねます. 現在の地形と出現する敵の種類が完全に一致してしまうのは、トロネコの大冒険4(出現する敵は座標と前方の壁の状態で固定)やムシハカセから続く省メモリの為の簡素化です.

変数/キャラクタ_+#&°¥
J0123161718
U012

EXP が生んだ荒涼とした世界

EXP は引数に0や負数を許容し、その小数部が乱数的で循環しないことから、本作のフィールド生成に採用しました.

LN をフィールド生成に使う拙作「ムシハカセ」では、X 座標が0以下になるのを防ぐ処理を入れていますが、本作にはその余裕がなかったことが出発点です.

負数方向では -1 から先、整数方向でも30から先は荒地が続いてしまいます.この挙動の逆算で、荒涼としたイメージイラストを用意しました. つまり、世界観よりプログラミング上の制約が先にあったわけです.

:W(U)=INT H(U)*9

現在の地形がクリスタルの場合(U=0)は、プレイヤーの攻撃力(H)を元にプレイヤーの HP(W) を最大値にします. 敵に遭遇した場合(U=2)は、敵の攻撃力(J)を元に敵の HP(Y) を設定します.

フィールド移動中(U=1)の場合は X(キー入力値を元にした値) が0になりますが、この変数は使われません.

2行目

1行からはみ出てしまったプレイヤーの強さの設定、27文字ある $ の設定、ダメージ計算を押し込んでいます.

H=A+15:$=" ♦_+#Win!Esc,Die?&°¥*♣ Fin."

第一ステートメントは、経験値を元にしたプレイヤーの強さです.

第二ステートメントでは、本作品で使用する全てのキャラクタとメッセージを $ に代入します. $ は5行目では KEY 入力の数値化にも使用されます.この為にここで常に再設定されます.

キャラクタとメッセージをどのような順番で並べればゲームになるのか?とても苦しんだ部分です. 様々な組み合わせを検討しました. ゲームを完成させた未来の自分のコードを読むことが叶うなら、この $ の内容だけでも覗くことが出来れば、他の部分のコードは自ずと判るのではないか?などと思い詰めたりもしました.

:R(T)=R(T)-INT (P*G(9-T)

ダメージ計算です.フィールド移動中(T=2)は、T=T-INT (P*0 が実行されて、T=2 のままです.ナイス!

戦闘中は、5<T<6(敵の攻撃), 6≦T<7(逃走可能), 7≦T<8(プレイヤーの攻撃), 8≦T<8.1416(何も起きない) の内、 敵の攻撃とプレイヤーの攻撃の場合には、其々の HP をダメージで減算します. これ以外の逃走可能、何も起きないでは0で減算されるため、変数値に変化は起きません.

3行目

バトル結果のメッセージ設定、ボス遭遇、経験値の上昇を押し込んでいます.

IF R(T)≦0;U=34-INT T*4

この条件式はフィールド移動中には入りません(T=2IF T≦0; のため).ナイス!

条件が成立するのは、バトル中にどちらかの HP が0以下になった場合と、逃走可能(6≦T<7)で 5 を押していた(X=0)場合、何も起きない(8≦T<8.1416)場合です.

IF 文に続いて、メッセージの番号を決定しています.何も起きない(8≦T<8.1416)場合でもここに入りますが U=34-8*4U に変化が起きません.ナイス!

TU
=2(フィールド移動中)=0, 1(変化なし)
5<T<6(敵の攻撃)14:Die?
6≦T<7(逃走可能)10:Esc,
7≦T<8(プレイヤーの攻撃)6:Win!
8≦T<8.1416(何も起きない)2(変化なし)
:J(4-SQR (T+SIN V))=20

ボス遭遇の為のコードです.T+SIN V が9を超える場合に、敵キャラクタ番号をボスのソレに差し替えます. これが成立する T の範囲は 8≦T<8.1416 です.先に「何も起きない」としましたが、ボス出現のトリガーになっています. T の最大値は ≒8.1416 なので V は60以上または-240以下で成立します.

ボス出現の裏側

ボス出現処理のための余裕が殆どないことから、一番弱い敵の現在の HP をボスがそのまま使っています.ボスの HP は常に0と表示されるのでプログラムを解析しないと分からない点です.

プレイヤーが強くなりすぎると、ボスがあっという間に屠られてしまうこともある為、ネタバレ気味になってしまいますが、ボスに何らかの特殊能力があることは早々にアナウンスをしました.

多くのケースで、ボスは最初から出現しますが、ごく稀に、& からボスへの変わり身に遭遇します.これをイラスト化したものが、スペシャル・ピンナップの1枚です. 種を明かしてみれば単に余裕が無いための挙動ですが、心を込めてイラストを描くとドラマチックなキービジュアルになりました.

ボスの出現条件に X 座標(V)を絡ませることで、辛うじて 1D フィールドが意味のあるものになっている点にも注目です.例えばプレイヤーの経験値を出現の条件にしてしまうとフィールドが存在する意味が怪しくなってきます.

:IF Y≦0;A=A+.5:A(J)=24

敵の HP が0以下になった場合に、プレイヤーの経験値を0.5増やします.ここに入るのはプレイヤーの攻撃(7≦T<8)に限ります.

続いて敵がボスの場合には、エンディングメッセージ(T=24, Fin.)を設定しています.ボスの強さを21に出来なかったのは、メッセージ変数(U)をもう1つ後ろに出来なかったからです.各変数の役割はタイトに決まっていて、現在は動かしようがありません.

諦めた処理

:T=19 にして * を表示したかったのですが諦めました.ボス出現判定の SQR() が不要なら入れる事が出来ますが、被破壊変数領域の確保ができず SQR を外すことが出来ませんでした.

被破壊変数領域の確保とは、N を0にし続ける必要が無ければ確保できました.ちなみに N が0で無いと不都合なのは、フィールド移動中の2行目の第3ステートメントです,

4行目

メイン画面とメッセージ表示に加えて、バトル後の処理、Die? 時の処理を押し込んでいます!

PRINT :PRINT MID(J+2,1);V(J/5);W;

現在の地形ないし敵キャラクタ、X 座標ないし敵の HP、プレイヤーの HP を表示します.

ボス戦にギミックを仕込め!

2021年11月の段階で、ボス戦に何か仕掛けを用意するなら「ボスのキャラクタ番号の調節で、ボスの HP は0を表示し続けて実際の HP を隠すこと」とあたりを付けていました. この追加機能は、ボスのキャラクタ番号と26個の変数への役割の割り当てさえうまくはまれば、ノーコストで実現出来るからです.HP 表示が0のままで闘い続ける敵ということで、 ネクロマンサーをボスに抜擢し、おのずと「ゴースト」を冠したタイトルが決定しました.

このギミックを仕込むことが出来なければ、僕は作品を公開しなかったかもしれません. というのも、フィールド探索、バトル、成長と回復とエンディングを単に5行に収めた段階ではゲームはまだ技術デモに過ぎず、「作品」へと昇華するもう一歩が足りないからです.

544ステップ以上といった、ある程度のメモリを利用できるゲーム開発では、自ずと作者の体臭、情念のようなものが入ってくるので、このような心配は無用に思います.

:IF 2<U;PRINT MID(U,4);

メイン画面に続いて、メッセージ表示をしています.

:T(U/6)=0

Win!, Esc, の場合は U=0 にしてバトルを抜けます.Die? の場合は X 座標をクリアします.変数の並びが上手くいくことで、1つのステートメントで2つの働きを実現しています. Fin. の場合は変数値に変化はありません.

Die? 時に X 座標のマニュアルでの再設定が不要なのは前作「プチRPG ムシハカセ」よりも優れている点です.このお陰で死にすぎるゲームバランスを許容する判断が出来ました.

5行目

キー入力の数値化、乱数関係、ループを押し込んでいます!

$=KEY:$="0"+$:C=VAL($)-5:X=ABS C

キー入力を数値化して変数に控えています.C はフィールド移動に使用しています.

C を絶対値化した X は、フィールド移動とバトルの逃走判定で使用しています. フィールド移動では、46 を押した時だけ X=1 になることを利用します.逃走判定では 5 を押した時だけ X=0 になり、それ以外は1以上になることを利用しています.

KEY 入力の数値化について

PB-100 では KEY を変数に控える際に、式の中では使えません.その為に $="0"+KEY と書くことは出来ません.

VAL() も引数に式を使うことが出来ず、VAL("0"+$) と書くことが出来ません.

"0" を足しているのは、数字キー以外が押された場合にエラーの発生を抑えるためです. この手当をしても E が押された場合にはエラーが発生します.更に手当するには記述が長くなるため、5行 RPG では入れる事が出来ませんでした.

:T=5+P:P=RAN#*π

乱数でダメージの倍率を決めています.また、直前のダメージの倍率から、フィールド移動時の敵との遭遇の有無、またはバトルのアクションを決定しています.

:IF U≦2 THEN 1+U/2

Die?, Fin. でなければループを続けます.フィールド移動およびバトルが終了している場合(Win!, Esc,)は1行へ、継続している場合は2行へ飛びます.

この章の終わりに
新たな詰め込みテクニックは生まれたのか?

本作品で最も頭を悩ましたのが、26個の変数のどこにどの役を当てるか? そして $ にどんな順番でキャラクタとメッセージを記述するか? でした.

リストが最短になりつつ、より多くの機能を実現できる、以上2つの順番を特定することが、最後に待っていた長い長い旅でした.

本作で使った詰め込み技術を一文で表すなら、計26個の各変数に与える役割(被破壊変数や常に0のものも)と、$ に格納する文字列を工夫することで、1つのステートメントに複数の働きを持たせる、です.

つまり、マイコン誌時代から取り組まれてきた PB-100 シリーズ用のプログラミングテクニックの延長線上でした.何か、新しい地平が視える予感がしていたのですが、残念ながら不発でした.

3行目の第1ステートメントで3種類のバトルの終了判定が出来ているなど、1つのステートメントに持たせることが出来た働きの量が、PB-100 史上初の5行 RPG を実現した要因です.


さて、前人未到の制約に挑んだ本作ですが、残念ながらこれを成し遂げたテクニックは、マイコン誌時代からのものの延長に留まり、本質的な新規性を有さない、というのが僕の結論です.

しかし、願望を述べるのが許されるならば、本作の詳細解説を読み込んだプログラマの書くリストは、ひと味もふた味も変わってくることでしょう.

2021年時点の非対称戦バージョン

2021年の10月時点で到達していた、いわゆる「非対称戦」バージョンを収録します.非対称戦の由来は、プレイヤーに比べて敵が強過ぎるので、プレイヤーの攻撃頻度を上げる処理を入れて調整を試みていることに因ります.

残念ながら「作品水準」には至りませんでしたが、リリース版より優れた点もあります.プログラムの変化を追う史料としてここに収録します.

プログラムリスト

U=U+B(X):T=ABS SGN U:S(S*T/9)=2:J=3*FRAC EXP U+T↑4:W(T)=INT H(T)*9
H=A+9:$="♦_+#*Win!Esc,Die?&¥♣Fin.":Q(S)=Q(S)-INT (RAN#*G(10-S)
IF Q(S)≦0;T=38-INT S*4:J(5-S/3-SIN U)=19:IF Y≦0;A=A+1:A(J)=21:J=4
PRINT :PRINT MID(J+1,1);U(J/4);W;:IF 2<T;PRINT MID(T,4);:S(T/6)=0
$=KEY:$="0"+$:C=VAL($)-5:X=ABS C:S=ATN RAN#/14+6:IF T≦2 THEN 1+T/2

リリース版との差異

  1. 鬼火のキャラクタが ?Die? とキャラクタを共有しています.
  2. 敵の HP と攻撃力が高いので、プレイヤーの攻撃頻度を上げることで調整を試みています.(5行 S=ATN RAN#/14+6)

    それでも敵が強すぎるため A の初期値を増やすチートをしないとゲームを進めることが出来ません.

  3. ボスの HP を0と表示し続けるギミックはこの時点ではありません.
  4. RAN# を使いまわしていない点、勝利時に敵のキャラクタを * に差し替える点、はこのバージョンの方が優れています.

各変数に割り当てる役割と被破壊変数のメモ

小文字の変数は常に0です.角括弧内([])は行番号とステートメント番号です.

       :  S   T   U   v   W   X   Y   z
T      :                  0   1   2     ⇐ HP設定[L1-5]
G(10-S):  o . .   .   .   J   i   H   g ⇐ 攻撃力[L2-3]
S      :  2   .   .   .   6   7   8   9 ⇐ R(T) [L2-3, L3-1]
J      :  .   .   20                    ⇐ Fin.設定[L3-4]
J/4    :          0   1   .   .   4     ⇐ X座標表示/敵HP表示[L4-2]
T/6    :      1   2   3                 ⇐ バトル終了, X座標を0[L4-4]