GUST NOTCH? DIARY

スタートレックのソースを読む その6

その5からの続きです。

  • メインループとワープ


1270 PRINT "COMMAND:";
1280 INPUT A
1290 GOTO A+1 OF 1410,1260,2330,2530,2800,3460,3560,4630
1300 PRINT
1310 PRINT " 0 = SET COURSE"
1320 PRINT " 1 = SHORT RANGE SENSOR SCAN"
1330 PRINT " 2 = LONG RANGE SENSOR SCAN"
1340 PRINT " 3 = FIRE PHASERS"
1350 PRINT " 4 = FIRE PHOTON TORPEDOES"
1360 PRINT " 5 = SHIELD CONTROL"
1370 PRINT " 6 = DAMAGE CONTROL REPORT"
1380 PRINT " 7 = CALL ON LIBRARY COMPUTER"
1390 PRINT
1400 GOTO 1270

この部分がメインループになります。コマンド番号を受け付けて、その番号に応じた処理に飛びます。
変数 A に入力した番号が保存されますので、その番号に応じて1290行で分岐します。
1290行の「GOTO...OF」という命令は、変数の値によって、その後の行番号に飛ぶ命令です。ここではAは0から7の値が入りますので、A+1は1から8になります。1はならば1410行に、2ならば1260行に……といった形で分岐します。
プチコンをはじめ多くのBASICでは、これと同じことを行う命令は「ON..GOTO」というものです。プチコンでは行番号の代わりにラベルを指定します。また、変数が0の時に最初のラベル、1の時に2番目のラベル……というようになります*1
変数に対応する飛ぶ先がない場合は、そのまま次の行に移ります。この場合は、コマンドの説明が表示されて、またコマンド入力に戻ります。
似たような命令に「ON..GOSUB」というものがあります*2。こちらはサブルーチンの先でRETURNになると、この行の次の行に戻って処理を続けます。

さて、各コマンドの処理を見ていきましょう。まずはワープからです。

  • ワープ

大きく以下のことが行われます。


1410 PRINT "COURSE (1-9):";
1420 INPUT C1
1430 IF C1=0 THEN 1270
1440 IF C1<1 OR C1 >= 9 THEN 1410
1450 PRINT "WARP FACTOR (0-8):";
1460 INPUT W1
1470 IF W1<0 OR W1>8 THEN 1410
1480 IF D[1] >= 0 OR W1 <= .2 THEN 1510
1490 PRINT "WARP ENGINES ARE DAMAGED, MAXIMUM SPEED = WARP .2"
1500 GOTO 1410
1510 IF K3 <= 0 THEN 1560
1520 GOSUB 3790
1530 IF K3 <= 0 THEN 1560
1540 IF S<0 THEN 4000
1550 GOTO 1610
1560 IF E>0 THEN 1610
1570 IF S<1 THEN 3920
1580 PRINT "YOU HAVE"E" UNITS OF ENERGY"
1590 PRINT "SUGGEST YOU GET SOME FROM YOUR SHIELDS WHICH HAVE"S" UNITS LEFT"
1600 GOTO 1270
1610 FOR I=1 TO 8
1620 IF D[I] >= 0 THEN 1640
1630 D[I]=D[I]+1
1640 NEXT I
1650 IF RND(1)>.2 THEN 1810
1660 R1=INT(RND(1)*8+1)
1670 IF RND(1) >= .5 THEN 1750
1680 D[R1]=D[R1]-(RND(1)*5+1)
1690 PRINT
1700 PRINT "DAMAGE CONTROL REPORT:";
1710 GOSUB 5610
1720 PRINT " DAMAGED"
1730 PRINT
1740 GOTO 1810
1750 D[R1]=D[R1]+(RND(1)*5+1)
1760 PRINT
1770 PRINT "DAMAGE CONTROL REPORT:";
1780 GOSUB 5610
1790 PRINT " STATE OF REPAIR IMPROVED"
1800 PRINT
1810 N=INT(W1*8)
1820 A$=" "
1830 Z1=S1
1840 Z2=S2
1850 GOSUB 5510
1870 X=S1
1880 Y=S2
1885 C2=INT(C1)
1890 X1=C[C2,1]+(C[C2+1,1]-C[C2,1])*(C1-C2)
1900 X2=C[C2,2]+(C[C2+1,2]-C[C2,2])*(C1-C2)
1910 FOR I=1 TO N
1920 S1=S1+X1
1930 S2=S2+X2
1940 IF S1<.5 OR S1 >= 8.5 OR S2<.5 OR S2 >= 8.5 THEN 2170
1950 A$=" "
1960 Z1=S1
1970 Z2=S2
1980 GOSUB 5680
1990 IF Z3 <> 0 THEN 2070
2030 PRINT USING 5370;S1,S2
2040 S1=S1-X1
2050 S2=S2-X2
2060 GOTO 2080
2070 NEXT I
2080 A$="<*>"
2083 S1=INT(S1+.5)
2086 S2=INT(S2+.5)
2090 Z1=S1
2100 Z2=S2
2110 GOSUB 5510
2120 E=E-N+5
2130 IF W1<1 THEN 2150
2140 T=T+1
2150 IF T>T0+T9 THEN 3970
2160 GOTO 1260
2170 X=Q1*8+X+X1*N
2180 Y=Q2*8+Y+X2*N
2190 Q1=INT(X/8)
2200 Q2=INT(Y/8)
2210 S1=INT(X-Q1*8+.5)
2220 S2=INT(Y-Q2*8+.5)
2230 IF S1 <> 0 THEN 2260
2240 Q1=Q1-1
2250 S1=8
2260 IF S2 <> 0 THEN 2290
2270 Q2=Q2-1
2280 S2=8
2290 T=T+1
2300 E=E-N+5
2310 IF T>T0+T9 THEN 3970
2320 GOTO 810

1410行から1440行はコースの入力です。変数C1でコースの値をうけます。コースは右が1で左周りに9までで一周します。
0の場合はワープのキャンセルで、メインループに戻ります。それ以外でコースの範囲外の数値が入力された場合は、コースの値の入力のやり直しです。

1450行からは移動量の受付です。W1 に0から8の値が入ります。この範囲外の値が入力された場合は入れなおしです。ざっくりといえば、隣の宇宙域に移動したい場合は1、二つ隣の宇宙域に移動したい場合は2、同じ宇宙域内で移動したい場合は1以下の値、といった感じです。詳細は後述します。
ワープエンジンが壊れている場合は、0.2以下の値しか指定できないことになっています。これを判断しているのが1480行で、装置が壊れていない場合、あるいは0.2以下の値が指定された場合は通常の処理1510行に飛びます。それ以外、つまり壊れているのに0.2より大きな値が指定された場合はメッセージを表示して、コースの入力からやり直しです。

1510行でクリンゴンがいるか確認しています。宇宙域中にクリンゴンがいる場合、クリンゴンからの攻撃をうけます。クリンゴンの攻撃は3790行からのサブルーチンで行われます。


3790 IF C$ <> "DOCKED" THEN 3820
3800 PRINT "STAR BASE SHIELDS PROTECT THE ENTERPRISE"
3810 RETURN
3820 IF K3 <= 0 THEN 3910
3830 FOR I=1 TO 3
3840 IF K[I,3] <= 0 THEN 3900
3850 H=(K[I,3]/FND(0))*(2*RND(1))
3860 S=S-H
3870 PRINT USING 3880;H,K[I,1],K[I,2],S
3880 IMAGE 4D," UNIT HIT ON ENTERPRISE AT SECTOR ",D,",",D," (",4D," LEFT)"
3890 IF S<0 THEN 4000
3900 NEXT I
3910 RETURN

クリンゴンの攻撃ルーチンです。基地に停泊中だった場合は、基地のシールドがエンタープライズを守ってくれるので、攻撃を回避することができます。

3820行で、クリンゴンの有無を確認し、いない場合は3910行のRETURNにスキップします。もともとクリンゴンがるからこのルーチンにきているはずなので、冗長な確認です。

3830行から3900行のループで、最大3つ居るはずのクリンゴンからの攻撃です。
3840行でクリンゴンの残エネルギーを確認します。既に撃墜していれば0以下になっているはずですので、NEXTまでスキップします。
3850行の H は、クリンゴンからの攻撃力を計算します。FND()関数はエンタープライズとのユークリッド距離を返すものでした。H の式は、「クリンゴンのエネルギーの2倍までのエネルギーで攻撃を受け、その影響は距離に反比例する」ということを意味していると解釈できます。
クリンゴンからの攻撃はシールドが吸収しますので、3860行でシールドの値を減らし、残りのシールド量を表示します。
エネルギーが残っていても、シールドがない状態で攻撃を受けると撃墜されてしまいます。3890行でその確認を行い、シールドの値が負になっている場合はゲームオーバーになります。

クリンゴンの攻撃ルーチンから1530行に戻ります。
1530行ではまたクリンゴンの数を確認しています。1510行でクリンゴンがいたからサブルーチンに飛んだのに、戻ってきてまた確認というのは冗長です。
次の1540行も、サブルーチン内でシールドが残っているか確認していて、ない場合は直接ゲームオーバーになるので、これも同じく冗長な処理です。

というわけで、1560行にくるのは、クリンゴンがいない場合です。
エネルギーがある場合は1610行の故障/回復処理に飛びます。
エネルギーがなくなっている場合は、シールドに回っている残量を確認します。シールドの残量もない場合は3920行に飛び、動けなくなった状態でのゲームオーバーになります。
まだシールドにエネルギーが残っている場合は、シールドを下げるようにメッセージを出して、ワープはせずにコマンド入力のところに戻ります。

1610行から1800行は計器の故障/回復処理です。
1620行から1640行のループで、故障している計器のダメージが1回復します。このことは、ワープを行うことで基本的には計器が回復する方向に向かうということを示しています。

しかし、1650行によって、20%の確率で突発的な事象が発生します。突発的な事象というのは「計器の故障」または「計器の回復」です。1670行からわかるように、故障か回復かはそれぞれ半分の確率で発生します。

1660行で、どの計器に「突発的な事象」が発生するのかを適当に決めます。
1680行から1740行が「故障」する場合の処理で、1750行から1800行が「回復」する場合の処理です。
1680行と1750行からわかるとおり、1〜6のダメージを受けるか、急激な回復をすることがわかります。
このとき、対象となる計器が壊れている状態かそうでない状態かとは関係なく発生しますので、既に壊れている計器が更なるダメージを受けることもあれば、壊れていない計器が「回復」して壊れにくくなるということも発生します。

途中で、5610行からのサブルーチンが呼ばれていますが、これは、1660行でもとめた計器の番号R1に対応する名前を切り出して表示する処理です。


5610 REM **** PRINTS DEVICE NAME FROM ARRAY *****
5620 S8=R1*12-11
5630 IF S8>72 THEN 5660
5640 PRINT D$[S8,S8+11];
5650 GOTO 5670
5660 PRINT E$[S8-72,S8-61];
5670 RETURN

D$とE$に計器名が入っているので、その部分文字列を取り出しています。
プチコンの場合もMID$()*3を使うことで同じ処理ができます。


D$="WARP ENGINESS.R. SENSORSL.R. SENSORSPHASER CNTRLPHOTON TUBESDAMAGE CNTRLSHIELD CNTRLCOMPUTER "
〜〜〜
@P_DEV
S8=(R1-1)*12
PRINT MID$(D$,S8,12)
RETURN
しかし、D$を配列にしてしまえばもっと楽です。

D$(0)="WARP ENGINES"
D$(1)="S.R. SENSORS"
D$(2)="L.R. SENSORS"
D$(3)="PHASER CNTRL"
D$(4)="PHOTON TUBES"
D$(5)="DAMAGE CNTRL"
D$(6)="SHIELD CNTRL"
D$(7)="COMPUTER"
以上のようになっていれば、サブルーチンを呼ばずに、PRINT D$(R-1) で済みます。

1810行から、ワープ先座標の決定を行います。
1810行に N がワープ量を設定します。入力された値 W1 を8倍して整数化します。実は銀河系は 64x64のグリッドで構成されています。そのうちの8x8の部分がひとつの宇宙域なのです。ワープの処理は(8+64)x(8+64)のグリッドの単位で計算されるので、ワープ量1は実は8単位の移動を意味していたのでした。

さて、エンタープライズは移動しますので、1820行から1850行で、元居た座標の情報を空白にします。
1670行と1680行で現在の座標(S1,S2)を(X,Y)として保存しておきます。
1885行で、指定されたコースの値の整数部分をC2として求めます。
1890行と1900行のX1とX2は、(8+64)x(8+64)のグリッドを一単位移動した時のX軸方向の変量とY方向の変量を求めています。
具体的にC1=2.3が指定されたときのX1とX2の値を求めた場合を図示してみます。

このように、(C[2,1],C[2,2])から(C[3,1],C[3,2])へ向かうベクトルを(2.3-2.0)倍した位置を向くベクトルの成分を求めていることになります。

1910行から2070行のループで、1単位ずつ進めながら新たな座標を求めるということを繰り返します。
宇宙域内で移動したい方向に、クリンゴン、基地、星があると、ぶつかってしまいその手前で停止してしまいます。

1920行と1930行で、宇宙域内での新たな移動先座標(S1,S2)を求めます。このとき、S1,S2 は小数値をとります。
1940行で、新たな座標が現在の宇宙域の内部にあるかを確認します。宇宙域の外に出た場合は、新たな移動先を求めるためにループを抜けて2170行からの処理に飛びます。

まだ宇宙域の内部の座標である場合は、求めた座標(小数値)に一番近いグリッドの座標(整数値)を求めて、その座標が移動可能であるかを確認します。移動可能である場合はNEXTまで飛んで次の移動先を求めます。
5680行からの文字列比較処理は既にでてきました。前の時はZ1,Z2が整数値だったのですが、今回は小数値が渡されるので、Z1=INT(Z1+.5) している意味がでてきます。0.5を足してから整数化することで、「近いほう」の座標に表示されるわけです。
プチコンでやるならこんな感じになるでしょう。


Z1=FLOOR(Z1+0.5)
Z2=FLOOR(Z2+0.5)
Z3=0
IF Q$(Z1,Z2)==A$ THEN RETURN
Z3=1
RETURN
プチコンでは小数を「.5」のように書くことはできないようですので、ちゃんと「0.5」のように書きましょう。

もし、移動可能でない(何かがある)場合は、2030行で、移動できないというメッセージを出してから、ひとつ前の座標に戻して、エンタープライズを書きこみます(2080行〜2110行)。


5370 IMAGE " WARP ENGINES SHUTDOWN AT SECTOR ",D,",",D," DUE TO BAD NAVIGATION"

ワープを行うとエネルギーを消費するわけですが、2120行でエネルギーを減らしています。エネルギーは(N-5)だけ減ります。つまり、コース指定を誤って宇宙域の外に出て行けなくても、最初に指定した移動量に応じたエネルギーを消費してしまうのです。

2130行から2150行が経年処理です。移動量として 1 以上を指定した場合、ワープ一回で1年が経過します。逆にいえば、1以下の値であれば時間は経過しません。最初に指定された任務期間を超えてしまうと、ゲームオーバーとなります。

ちなみに、1単位の移動には最低0.125必要です。また故障中は0.2までしか指定できませんが、これで2単位移動できます。実はこのとき、N=1 となります。E=E-N+5 でしたので、なんと移動しながらもエネルギーが4増えるのです。0.2でのワープを繰り返せば、時間が経過することなく、エネルギーが微増し、計器は1回復する、ということになります。しかし、故障からは逃れられないので、計器を直そうと小さなワープを繰り返したら、さらにダメージを受けるということも発生します。

まだ任務期間が残っている場合は、1260行に戻り、基地に停泊中かを確認してコマンド入力に戻ります。

現在の宇宙域を離脱できた場合は、2170行からの処理で、移動先の宇宙域の座標と宇宙域内での座標を求めます。
X,Y はワープしようとした時点での宇宙域内の座標でした。X1,X2は、(8+64)x(8+64)のグリッドを一単位移動した時のX軸方向の変量とY方向の変量、NはINT(8*W1)で求められた移動量でした。
2170行と2180行で、(8+64)x(8+64)の中での移動先を(小数で)求めます。(Q1*8+X) が元いた座標で、(X1*N) がそこからの移動量です。Y軸方向も同じです。
2190行と2200行で、銀河系の中の宇宙域の座標を求めます。8で割って整数化するだけです。この時点ではQ1,Q2は1から8の値になります。
2210行と2220行で、宇宙域中の座標を求めます。8単位の下駄が履かせてあるので、(X-Q1*8) で宇宙域内の座標が求められます。0.5 を足してから整数化しているのは、より近いグリッドに表示するためです。
2230行から2280行は宇宙域の境界における処理です。S1またはS2が0の場合は、実は隣の宇宙域の方が近いことを意味します。そこで、宇宙域をひとつ小さい座標のものにして、宇宙域内の座標は端の部分である8にします。
2290行から2310行で、経年処理をしてエネルギーを減らします。先ほど「0.2でのワープを繰り返せば、時間が経過することがない」と述べましたが、それは同じ宇宙域内を動いている間だけで、宇宙域を移動してしまうと、やはり1年が経過してしまいます。
宇宙域を超えるワープが発生した場合は、810行に移動し、移動した先の宇宙域の配置の初期化が行われ、コマンド入力待ちとなります。このことは、同じ宇宙域でも、訪れるたびに配置が変化することを意味します。

次回は長距離センサの処理です。
その7へ続きます。