プログラムリスト
Version 1.3.0、544ステップです.
1 VAC :B$="♥[":E$=" ":F$=".":GOSUB 13:PRINT CSR 4;"B♥J"
2 PRINT " ᴇ⁻;π";MID(V*5+1,5);"=";:GOSUB 11
3 Z=0:GOSUB 8:C=Z:Z=0:GOSUB 9:W=3
4 PRINT "?";:GOSUB 6+W:IF 1<W↑Z THEN 4
5 Z=C:C=Y
6 GOSUB 8:IF Y-FRAC Z<14+A THEN 7-SGN Z
7 A=EXP SGN (Y-C:V(A)=V(A)+1:PRINT X;C;"×";Y;:GOSUB 11:GOTO 2
8 PRINT "♥";
9 D=1+INT (RAN#*13:J=D:I(LOG J)=10:I(J)=11.7:Z=Z+J
10 Y(Z/22)=SGN FRAC Z*(Z-10.7:Y=INT Z:PRINT D;Y;
11 R(X)=5:W=2
12 N=2:$="Σ:: ]&÷! ]&÷- )8[・D=: ・D=: ・y-":IF H<0;G=-30:H=-H:W=4
13 T=1:IF W≠N;IF V≠5;PRINT :RETURN
14 IF KEY="4";W=W-SGN W
15 IF KEY="6";W=W+SGN (4-W
16 G=G+H:IF G+6≦V THEN 12
17 IF KEY="Q";K(W)=SGN N:M(N*W)=0:P(W)=4
18 PRINT CSR 0;"/ □□ I=ᴇ ";E$(N/2);A$(T);CSR W+6;"Ω";CSR G+6;"&";
19 IF V≦T;H(ABS G)=1/(N+SGN W-5
20 IF G≦W THEN 14
21 PRINT "<!)"
シミュレータ用リスト
Windows 用アプリ Pocket BASIC Simulator 0.12で動作確認しています.Android 用アプリ PokecomGO - CASIO PB simulator で動作確認しています.
以下のプログラムリストをコピーしてファイルを作成する、または次のリンクからダウンロードします. blackjack_v130.bas をダウンロード(820 bytes)
[P0]
1 VAC :B$="\HT[":E$=" ":F$=".":GOSUB 13:PRINT CSR 4;"B\HTJ"
2 PRINT " \EM;\PI";MID(V*5+1,5);"=";:GOSUB 11
3 Z=0:GOSUB 8:C=Z:Z=0:GOSUB 9:W=3
4 PRINT "?";:GOSUB 6+W:IF 1<W^Z THEN 4
5 Z=C:C=Y
6 GOSUB 8:IF Y-FRAC Z<14+A THEN 7-SGN Z
7 A=EXP SGN (Y-C:V(A)=V(A)+1:PRINT X;C;"\CR";Y;:GOSUB 11:GOTO 2
8 PRINT "\HT";
9 D=1+INT (RAN#*13:J=D:I(LOG J)=10:I(J)=11.7:Z=Z+J
10 Y(Z/22)=SGN FRAC Z*(Z-10.7:Y=INT Z:PRINT D;Y;
11 R(X)=5:W=2
12 N=2:$="\SG:: ]&\DV! ]&\DV- )8[\DTD=: \DTD=: \DTy-":IF H<0;G=-30:H=-H:W=4
13 T=1:IF W<>N;IF V<>5;PRINT :RETURN
14 IF KEY="4";W=W-SGN W
15 IF KEY="6";W=W+SGN (4-W
16 G=G+H:IF G+6<=V THEN 12
17 IF KEY="Q";K(W)=SGN N:M(N*W)=0:P(W)=4
18 PRINT CSR 0;"/ \SQ\SQ I=\EX ";E$(N/2);A$(T);CSR W+6;"\OM";CSR G+6;"&";
19 IF V<=T;H(ABS G)=1/(N+SGN W-5
20 IF G<=W THEN 14
21 PRINT "<!)"
既知の問題
21点の時にトランプを引くことが出来てしまう、ごくまれに22点が出てしまう
マル雄が21点の時にもトランプを引く(6)ことができてしまう.
この時に手札が 1 と 10 等(1 を11点とカウントしたフラグが立っている状態)で、新しいカードが 1 の場合、マル雄の得点が0点にならず想定してない22点になる.
4行目を IF 1<W^Z;IF Y<21 THEN 4
(+6ステップ) とすることで21点でカードを引けなくなりますが、稀なケースであり、 機能追加を優先したため放置しています.Version 1.3.0の開発中に発見したもので、もっと以前に気付いていたら何かの機能を削って塞いでいたかもしれません.
この問題の修正案
Version 1.3.0で追加した4行目の PRINT "?";:
がちょうど6ステップなので、気になる方はここを削って問題を塞ぐのが良いでしょう. 僕は、表示されるキャラクタを増やしてゲーム画面を賑やかにすることを重視したことを記録しておきます.
4 PRINT "?";:GOSUB 6+W:IF 1<W^Z THEN 4
↓
4 GOSUB 6+W:IF 1<W^Z;IF Y<21 THEN 4
マル雄が負けて母の登場までにかなり時間がかかる場合がある
母が遠くにいて母の移動速度が遅い場合には、部屋モードに入るのにかなりの時間がかかります.この間ユーザーは操作不能になる為、ゲームを中断してしまうかもしれません.この点は不親切です.
また、エリが負けた時点での母の位置(≒エリのグラフィックが表示される時間)が本作におけるスコアと言えそうですが、このスコアを取り出して表示することは諦めて、他の機能を優先しています.
技術情報
変数表
✔ は大域(グローバル)変数です.▲ はブラックジャックモードで設定され、部屋モードで破壊されてはいけない、実質的に大域変数です.
D(新しく配られたカード), Z(スコア) は、マル雄の2枚目以降で部屋モードに突入しブラックジャックモードに戻った時の再表示(W=4
, GOSUB W+6
で10行に飛ぶ)の為に保持します.
× は被破壊変数として使用されることを示します.
変数 | 大域変数 | ブラックジャックモードの内容 | 部屋モードの内容 |
---|---|---|---|
A | ✔ | ブラックジャックの結果(0.367..., 1, 2.718...)、エリの思考でも使用 | |
B$ | ✔ | "♥[" | |
C | ▲ | エリのスコアの一時退避とマル雄のスコア | |
D | ▲ | 新しく配られたカード | |
E$ | ✔ | " " | |
F$ | ✔ | "." | |
G | ✔ | 母の位置 | |
H | ✔ | 母の移動速度 | |
I | × | × | |
J | 新しく配られたカードの得点 2~10, 11.7 | × | |
K | × | × | |
L | × | × | |
M | × | × | |
N | × | トランプの状態、オープニングデモを終えてからリターンする為のフラグ | |
O | × | × | |
P | × | × | |
Q | × | × | |
R | × | × | |
S | × | × | |
T | ×、エリの状態 | ||
U | × | ||
V | ✔ | エリの負け | |
W | ×、マル雄の2枚目以降で、1:ストップ, 3:もう一枚引く, 4:部屋モードを挟んだため再表示と再入力 | マル雄の X 座標(0~4) | |
X | ✔ | マル雄の負け | |
Y | ×、スコア(=INT Z ) | ||
Z | ▲ | スコア(小数点には 1 を11とカウントしたフラグ) |
並べて使用される変数
--:- ABCDEFGHIJKLMNOPQRSTUVWXYZ 7:2 0.2 18:1 01 18:1 .1..4
使用する変数と被破壊変数の関係
--:- HIJKLMNOPQRSTUVWXYZ 7:2 V*X 9:3 *K 9:4 K********* 10:1 *Z --:- HIJKLMNOPQRSTUVWXYZ 11:1 ****V 17:1 ***N* 17:2 *N*** * * 17:3 ****T 19:1 H*****
行番号マップ
行 | 処理 |
---|---|
1 | 初期設定、タイトル表示 |
2 | エリのグラフィック表示 |
3 | エリ1枚目、プレイヤー1枚目 |
4~5 | プレイヤー2枚目以降 |
5~6 | エリの2枚目以降 |
7 | 結果処理 |
8~10 | 新しいカードの加算、表示他 |
11 | マル雄の負けによるゲームオーバーの設定、部屋モードの初期設定 |
12 | 母の位置と移動方向の設定、部屋モードを終えたフラグ(W=4)を設定 |
13 | キー入力からの復帰他 |
14~15 | キー入力 |
16 | 母の移動、部屋モード開始または部屋モード終了の判定 |
17 | プレイヤーアクション |
18 | 部屋モード表示 |
19 | ゲームオーバーでないなら、母の移動速度設定 |
20 | 母に重なっていなければ部屋モードを継続 |
21 | おわり表示 |
オープニングデモの真相
まずはオープニングデモに注目ください. デモ表現は作品のコミカルな世界観に一役買っていますが、このクラスのゲームとしては不釣合いな機能に感じるかもしれません.
実はこのデモは変数の設定を兼ねていて、:GOSUB 13
の命令だけで G と H (母の初期位置と移動速度)さらに $ の設定を終えています.
$=" ... "
は巨大なので置き場に困り、結局12行目に落ち着くことになりました.ゲーム速度やメモリへの圧迫を最小にするベストな位置のはずです.
Version 1.3.0での変更について
オープニングデモでは、マルオは机についている(W=0)、トランプは引き出しの中(N=0)、エリは隠れていない(T=1)、を初期値とします.T の初期値が0から1になったのは Version 1.3.0からになります.
18行(A$(T);
)に到達する前に T=1 の初期化処理が必要になりました.この為に11行で代入していたものを13行に移動しました.
続く :IF W≠N;IF V≠5;PRINT :RETURN
は、N=0, W=0 で IF
文を抜けます.(IF V≠5;
は成立します.)
オープニングデモを終えた時は12行で N=2, W=4 が代入されるため、13行でリターンしてタイトル画面に進みます. N=2 の初期化処理を11行から12行に移動し、:IF W≠2;
を :IF W≠N;
に変更したのは、唯一、オープニングデモを抜ける為です.
Version 1.2.0まで16行にあった 1/H
は0で除算を行わないようにする為に、括弧の分の1ステップを犠牲にして19行に移しています.
以上の変更を行わなくとも、18行で B$(T/2);
などとして、0と1を同様に扱えれば良かったのですが、これには2ステップを要します.しかし1ステップの余裕しか無かったためにメチャメチャ試行錯誤しました.
以上、かなりトリッキーな流れになるので備忘も兼ねて詳述しました.僕には、和箪笥の隠し収納を開けるためのカラクリを想起させてお気に入りです.
配列変数を使って IF
文を省略する
さて、今作で特筆すべきは9行目のカードのスコア計算と、17行目の Q キーによるプレイヤーのアクションです.
それぞれ最初は数行が必要だったのですが、一行にまとめることができました.これには配列変数を使って IF
文を省略する、というテクニックが使われています.
省略前のリストを完成版と対応させて紹介しますので解析の参考にしてください.ちなみに私はこのテクニックをメビウス氏の『特集BASICプログラミング入門 1ステップに命を削る』で学びました.
パート | 処理 | 初めのリスト | 完成版リスト | 有効な変数 /被破壊変数 |
---|---|---|---|---|
スコア計算 | 新しいカード | D=1+INT (RAN#*13:J=D | ||
10以上のスコア | :IF 9<J;J=10 | :I(LOG J)=10 | I, J | |
A のスコア | 10 IF J=1;J=11.1 | :I(J)=11.1 | J, KLMNOPQRS | |
スコア処理 | 合計が22以上 | 10 IF Z>21;Z= | 10 Y(Z/22)= | Y, Z |
0点にする、または A を1点として再計算 | SGN FRAC Z*(Z-10.7 | |||
(行の追加は2ステップ消費!) | (別処理は次の行へ) | : (別処理が続く) | ||
アクション | キー入力判定 | 17 IF KEY≠"Q" THEN 21 | 17 IF KEY="Q" | |
トランプ拾う | 18 IF W=3;N=SGN N | ;K(W)=SGN N | KLM, N, O | |
トランプしまう | 19 IF N*W=1;N=0 | :M(N*W)=0 | M, N, OPQ,S,U | |
エリを隠す | 20 IF W=4;T=4 | :P(W)=4 | PQRS, T |
Version 1.3.0での変更について
Version 1.3.0では、H(母の移動速度)の更新と、マル雄が全裸になった時のゲームオーバー処理(R(X)=5
で X=4 の時に V=5 にする)でも、本テクニックを使うようになりました.
小数部にフラグを持たせる
9 D=1+INT (RAN#*13:J=D:I(LOG J)=10:I(J)=11.7:Z=Z+J
「1(Aエース)は11点になるのに11.7とするのはどうしてだい?」という声が聞こえてきたところで…その説明に入ります.
この0.7はエースを11点としてカウントした時のフラグです.そしてスコアが加算されたときフラグも合計スコアの小数部に保存されます.
10 Y(Z/22)=SGN FRAC Z*(Z-10.7
手札の中のエースを11点として計算していたら、合計スコアが21点を超えた場合エースを1点として数えなおす、というルールをプログラムでは合計スコア(変数 Z)が22以上になった場合、 Z の小数部が存在するなら(エースを11点としていたら)、Z-10.7
としてエースの数え直しとフラグの処理を一手で行います.しかもエースを2枚以上持っている場合にも正しく動作することにご注目ください!
FRAC
関数で簡単に小数部を抜き出せることもあり、フラグ用に新たな変数を用意するより少いステップで処理することができます.
それにしても「1ステップに命を削る
」、すばらしい.
Version 1.3.0では小数部を0.7へ
6 GOSUB 8:IF Y-FRAC Z<14+A THEN 7-SGN Z
この小数部を Version 1.3.0では、0.1から0.7にしました.FRAC Z
は6行目のエリの思考でも使っていて、これに関係する変更です.
エリの思考では、手札に11点として数えている1がある場合、大胆にカードを引きます. この他に、直前の勝負結果(A)も使用していて、負けていると慎重に、勝っていると大胆になります.
この A の値が Version 1.3.0では、-1, 0, 1 から 0.367..., 1, 2.718... になりました.これに伴った変更です.
コラム
配列変数を使って IF
文を省略するための変数使用状況の見直し
使い切らない基本変数
上表で被破壊変数の項を参照いただくと、9行目の処理では実に10個もの変数が破壊の対象です. これは変数 I, K L M N O P Q R S が9行目をまたいでデータを保持する利用には適さない、ということです.
一度にこれほどの変数領域を浪費する手法には抵抗感があるかもしれませんが、そもそも26個の基本変数を使い切るゲーム作品は少ないです.
マイナスの DEFM
命令(変数を減らしてステップを増やす命令ってどうしてないのでしょう?)といったものも無いので、頭の隅に入れておく価値はあると思います.
破壊可能変数の確保
ところで今作では連続する大規模な被破壊変数を設けるために、一時変数の使い回しをしています.
今回は幸いにしてリストの判読を困難にするほどのものではありませんでしたが、変数の使用状況が切迫すれば変数の使い回しが頻発することは容易に想像できます. (変数の増設はなるべく避けたいですし、Z(1)=100
などと参照しているとステップを大きく浪費します.)
こういった場合は後のバージョンアップやユーザーの解析に資するためにも、丁寧な変数表を用意しておきましょう. また所要の最適化が達成できた時点で、解析のしやすい変数使用にも留意したいところです.
変数もまたメモリの一部なのです.