# Delphi で「追いかけマン」の Windows アプリケーションを作る --- tags: Delphi programming Pascal ポケコン objectpascal created_at: 2022-12-10 updated_at: 2022-12-18 --- # はじめに 皆さんは、人生で初めて入力したプログラムを覚えているでしょうか? 私は覚えています。ポケットコンピュータ『SHARP PC-1246』の取扱説明書に載っていた **「追いかけマン」** です。悪戦苦闘しながら入力して、ちゃんとゲームとして動いた時は嬉しかったなぁ。 『PC-1246』は遠い昔に手放したのですが、最近『PC-1245』や『PC-1251』を入手したため、また「追いかけマン」を入力して遊ぶ事が出来ました。 本記事は、その「追いかけマン」を Windows アプリケーションにしてみようというものです。アプリケーションの作成には **『Embarcadero Delphi』** を使います。 :::note info Delphi のバージョンは 2009 以降を想定していますが、(可能な限り) 古い Delphi へ移植できるように考慮したコードになっています。 ::: - [Delphi (Wikipedia)](https://ja.wikipedia.org/wiki/Delphi) - [Delphi (Embarcadero)](https://www.embarcadero.com/jp/products/delphi) - [DocWiki (Embarcadero)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%83%A1%E3%82%A4%E3%83%B3%E3%83%9A%E3%83%BC%E3%82%B8) - [Delphi Community Edition (Embarcadero)](https://www.embarcadero.com/jp/products/delphi/starter) <- 無償版 - [OBJECT PASCAL HANDBOOK for 11 ALEXANDRIA (Embarcadero)](https://lp.embarcadero.com/ObjectPascalHandbookD11) <- 言語解説 (英語、無償) # 「追いかけマン」 **「追いかけマン (英語名: BUGHUNT)」** についての説明です。 ## ルール 10x10 のフィールドで虫と追いかけっこをするというもので、『[HIT & BLOW](https://ja.wikipedia.org/wiki/%E3%83%9E%E3%82%B9%E3%82%BF%E3%83%BC%E3%83%9E%E3%82%A4%E3%83%B3%E3%83%89)』のようなゲームです。 ![image.png](./images/95e680fc-4e9e-23b2-960f-ea3771ce08d4.png) **[操作説明]** 1. 追いかけマンは `〔8〕` `〔2〕` `〔4〕` `〔6〕` キーで移動できます。 2. 追いかけマンが移動すると虫も移動します。 3. 虫がフィールドの隅に来ると、任意の位置へワープします。 4. 体力 (E) は初期値が 100 で、追いかけマンが移動してもしなくても 1 ずつ減ります。 5. 距離 (D) は虫との距離で `ABS(X-a) + ABS(Y-b)` の関係にあります。 6. 距離が 3 以下だと、BEEP が鳴ります (近さに応じて 1/2/3 回。または */**/*** による表示)。 7. 虫を見つけると BEEP が 5 回鳴ります。 8. 虫を退治できると体力が 5/10/15 のいずれか回復します。 9. 体力が 0 になるまでに虫をどれだけ退治できたかを競います。 **[画面説明]** ビジュアルなゲームだと思った?『PC-1246』は 16桁 1行でグラフィックが使えないポケコンなのよ? ![image.png](./images/68aa4452-9945-d3a9-1075-877ec1ac65fc.png) 左から、追いかけマンの現在位置 (X,Y)、虫との距離、体力値です。 ## バリエーション 「追いかけマン」にはバリエーションがあるようです。 | 機種 | 説明 | |:---|:---| | PC-1250/51/55 版 | オリジナル? | | PC-1245 版 | 16桁表示に合わせた改変 | | PC-1246/47 版 | BEEP 命令が省かれている | | PC-1260/61/62 版 | PC-125x 版と同じ? | :::note info 取扱説明書での「追いかけマン」の "虫との距離の説明" は PC-126x 版以外間違っていて、`ABS(X-a) + ABS(X-b)` と書かれています。 ::: ## BASIC ソースコード 次のリストは PC-1245 版のものです。 ```vb:OIKAKEMAN.BAS 10 "A": RANDOM : WAIT 250: PRINT "** OIKAKE MAN **": BEEP 3 20 X=0:Y=0:E=100:F=100:T=0:S=0 30 A=RND 9:B=RND 9 40 L=ABS (X-A)+ABS (Y-B) 50 IF X=A AND Y=B GOTO 400 100 IF L=1 BEEP 3 110 IF L=2 BEEP 2 120 IF L=3 BEEP 1 130 WAIT 50: PRINT "(";STR$ (X);",";STR$ (Y);") D=";STR$ (L);" E=";STR$ (E) 150 S=S+1:E=F-INT (S/2) 153 IF E<=0 THEN 500 155 G$=INKEY$ : IF G$=""GOTO 130 157 BEEP 1 160 IF G$="2"LET Y=Y-1: GOTO 210 170 IF G$="4"LET X=X-1: GOTO 210 180 IF G$="6"LET X=X+1: GOTO 210 190 IF G$="8"LET Y=Y+1: GOTO 210 200 GOTO 150 210 IF X<0 LET X=0: GOTO 150 220 IF Y<0 LET Y=0: GOTO 150 230 IF X>9 LET X=9: GOTO 150 240 IF Y>9 LET Y=9: GOTO 150 250 IF X=A AND Y=B GOTO 400 260 E=F-INT (S/2) 270 IF E<=0 GOTO 500 280 R=RND 5 290 IF R=1 LET B=B-1: GOTO 340 300 IF R=2 LET A=A-1: GOTO 340 310 IF R=3 LET A=A+1: GOTO 340 320 IF R=4 LET B=B+1: GOTO 340 340 IF A<0 OR A>9 GOTO 370 350 IF B<0 OR B>9 GOTO 370 360 GOTO 40 370 BEEP 4: PAUSE "*** WARP ***": GOTO 30 400 PAUSE "HIT! HIT!" 410 BEEP 5 420 PAUSE "BANG! BANG!" 430 T=T+1:C=RND 3*5:F=F+C 435 E=F-INT (S/2) 440 WAIT 100: PRINT "SCORE ";T: PRINT "ENERGY ";E 450 GOTO 30 500 WAIT 100: PRINT "SCORE ";STR$ (T): WAIT : PRINT " *GAME OVER!!*" 510 END ``` PC-1246 版は、すべての BEEP 命令が省かれ、100 行目からの 3 行が次のようになっています。 ```vb:OIKAKEMAN.BAS 100 IF L=1 PAUSE "***" 110 IF L=2 PAUSE "**" 120 IF L=3 PAUSE "*" ``` PC-125x 版は、24桁表示で動作するようになっていますので、一部画面表示が異なります。 ```vb:OIKAKEMAN.BAS 10 "A": RANDOM : WAIT 250: PRINT "** OIKAKE MAN GAME **": BEEP 3 130 WAIT 50: PRINT "(";STR$ (X);",";STR$ (Y);") KYORI=";STR$ (L);" E=";STR$ (E) 440 WAIT 100: PRINT "SCORE ";T;" ENERGY ";E 500 WAIT : PRINT "SCORE "; STR$ (T);" *GAME OVER!!*" ``` ## Delphi への移植 PC-1245 版の「追いかけマン」を Delphi へ移植してみます。 ### 画面 `[ファイル | 新規作成 | Windows VCL アプリケーション]` でプロジェクトを新規作成し、フォームに TEdit と TButton を貼ります。 ![image.png](./images/2bf86040-04eb-e198-112c-9fdd94efdb6e.png) #### ・フォームのプロパティ 固定サイズのウィンドウを持つフォームに指定します。 | プロパティ | 値 | |:---|:---| | BorderIcons | [biSystemMenu,biMinimize] | | BorderStyle | bsSingle | | Caption | 追いかけマン | | Font.Size | 16 | | KeyPreview | True | | Name | frmGameMain | | Position | poScreenCenter | | Scaled | False | #### ・エディットボックスのプロパティ エディットボックスのフォントは等幅が望ましいのでフォントを `BIZ UDゴシック` とかにしておき、エディットボックスの幅も 16 文字入るくらいに調整しておきます。 | プロパティ | 値 | |:---|:---| | Font.Name | BIZ UDゴシック | | Font.Size | 24 | | Height | 40 | | Name | edDisp | | Width | 266 (16桁) / 392 (24桁) | #### ・ボタンのプロパティ ボタンはキャプションとサイズを設定するくらいです。 | プロパティ | 値 | |:---|:---| | Caption | 開始 | | Height | 40 | | Name | btnStart | | Width | 120 | #### ・プロジェクトの保存 `[ファイル | プロジェクトに名前を付けて保存]` で、一旦プロジェクトを保存します。 | ファイルの種類 | ファイル名 | |:---|:---| | ユニットファイル | frmuGameMain.pas | | プロジェクトファイル | oikakeman.dproj | ### ゲームのための土台作り ポケコンの BASIC の命令を移植し、ゲームプログラムを移植します。まずはゲームを動かすための土台を作ります。 #### ・BASIC ルーチン用ユニット `[ファイル | 新規作成 | ユニット]` で新規にユニットを作成し、`[ファイル | 名前を付けて保存]` でそのまま保存します。 | ファイルの種類 | ファイル名 | |:---|:---| | ユニットファイル | uPokecomUtils.pas | 「追いかけマン」を移植するのに必要な命令をレコード型のメソッドとして実装します。レコード型のメソッドにしておくとコード補完が使えて便利なのです。 次のコードを記述して、`〔Ctrl〕+〔S〕` で上書き保存してください。 ```pascal:uPokecomUtils.pas unit uPokecomUtils; interface uses System.SysUtils, System.Math; type TPcRtn = record FWait: UInt32; function Abs(v: Double): Double; overload; procedure &End; function Int(v: Double): Double; procedure Random; function Rnd(v: Double): Double; function Sgn(v: Double): TValueSign; function Str(v: Double): String; procedure Wait(v: UInt32 = 0); end; implementation { TPcRtn } function TPcRtn.Abs(v: Double): Double; begin result := System.Abs(v); end; procedure TPcRtn.&End; begin Abort; end; function TPcRtn.Int(v: Double): Double; begin result := Trunc(v); end; procedure TPcRtn.Random; begin Randomize; end; function TPcRtn.Rnd(v: Double): Double; begin if v >= 1 then result := System.Random(Trunc(v)) + 1 else if v < 0 then result := System.Random // Need Debug else result := System.Random; end; function TPcRtn.Sgn(v: Double): TValueSign; begin result := Sign(v); end; function TPcRtn.Str(v: Double): String; begin result := FloatToStr(v); end; procedure TPcRtn.Wait(v: UInt32); begin FWait := v; end; end. ``` このユニットをメインフォームから使えるように、`frmuGameMain` の **uses** に追加しておきます。 ```pascal:frmuGameMain.pas unit frmuGameMain; interface uses ..., uPokecomUtils; ``` ポケコンの BASIC で使える型は文字列型と数値 (BCD) なのですが、様々な事情を考えて、文字列 (String) 型と実数 (Double) 型をメインで使う事にします。 :::note info End() メソッドに & が付いているのは、**end** が Delphi (Pascal) では予約語になっているからです。 ::: **See also:** - [【Delphi】とても長い数値の計算 (BCD) (Qiita)](./3d8b06f459c3d861b5d5.md) #### ・イベントハンドラ (その1) `frmuGameMain` に戻り、[オブジェクトインスペクタ] の [イベント] タブでイベントをダブルクリックしてイベントハンドラを作成します。 ![image.png](./images/1b99a006-d0e4-6fae-df1d-734fb225fe0b.png) **frmGameMain.OnShow** 雑に配置したエディットボックスやボタン、フォームのサイズを計算で設定します。「自分で配置したままの方がいい!」という方は記述しなくても構いません。 ```pascal:frmuGameMain.pas procedure TfrmGameMain.FormShow(Sender: TObject); begin OnShow := nil; edDisp.Top := 16; edDisp.Left := 16; ClientWidth := edDisp.Left + edDisp.Width + 16; btnStart.Top := edDisp.Top + edDisp.Height + 16; btnStart.Left := edDisp.Width - btnStart.Width + 16; ClientHeight := btnStart.Top + btnStart.Height + 16; edDisp.Text := ''; end; ``` **frmGameMain.OnKeyPress** 押されたキーを edDisp の Tag プロパティに数値として保存するようにします。 ```pascal:frmuGameMain.pas procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char); begin edDisp.Tag := Integer(Key); end; ``` #### ・ゲーム用スレッド ゲーム用のスレッドを作成します。ゲームループがあるため、スレッドを使わないとアプリケーションをマウスで移動できなかったり、動きがカクカクになったりすると思います…多分。 次の位置に、 ```pascal:frmuGameMain.pas type { この位置 } TfrmGameMain = class(TForm) ``` 以下のコードを貼り付け、このスレッドクラスの定義のどこでもいいので内側にカーソルを合わせて `〔Ctrl〕+〔Shift〕+〔C〕`を押してください。 ```pascal:frmuGameMain.pas TGameThread = class(TThread) private { Private 宣言 } FEdit: TEdit; protected { Protected 宣言 } procedure Execute; override; public { public 宣言 } constructor Create(Edit: TEdit); end; ``` 2 つのメソッドの実装部が自動で作成されたと思いますので、**uses** に必要なユニットを追加し、 ```pascal:frmuGameMain.pas uses ..., System.Diagnostics; ``` TfrmGameMain にフィールドを 2 つ追加し、 ```pascal:frmuGameMain.pas TfrmGameMain = class(TForm) edDisp: TEdit; btnStart: TButton; procedure FormShow(Sender: TObject); procedure FormKeyPress(Sender: TObject; var Key: Char); private { Private 宣言 } GT: TGameThread; // 追加 Buf: string; // 追加 public { Public 宣言 } end; ``` スレッドのメソッドを記述します。 ```pascal:frmuGameMain.pas constructor TGameThread.Create(Edit: TEdit); begin inherited Create(False); FEdit := Edit; end; {$HINTS OFF} procedure TGameThread.Execute; {$REGION '/* 内部ルーチン */'} const COLUMNS = 16; // 画面の桁数 var A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z: Double; A_, B_, C_, D_, E_, F_, G_, H_, I_, J_, K_, L_, M_, N_, O_, P_, Q_, R_, S_, T_, U_, V_, W_, X_, Y_, Z_: string; Rtn: TPcRtn; procedure Beep(n: Integer); begin Synchronize( procedure var i: Integer; begin for i := 1 to n do Winapi.Windows.Beep(4000, 500); end ); end; { Beep } function Inkey: Char; begin result := Char(FEdit.Tag); FEdit.Tag := 0; end; { Inkey } procedure Print(v: array of const); overload; var i: Integer; T, Tick: Int64; sw: TStopWatch; vs: string; begin for i:=Low(v) to High(v) do begin case v[i].VType of vtChar: vs := vs + String(v[i].VChar); vtAnsiString: vs := vs + String(PAnsiChar(v[i].VAnsiString)); vtWideChar: vs:= vs + v[i].VWideChar; vtWideString: vs:= vs + PWideChar(v[i].VWideString); vtUnicodeString: vs := vs + PWideChar(v[i].VUnicodeString); vtInteger: vs := vs + IntToStr(v[i].VInteger); vtInt64: vs := vs + IntToStr(PInt64(v[i].VInt64)^); vtCurrency: vs := vs + CurrToStr(PCurrency(v[i].VCurrency)^); vtExtended: vs := vs + FloatToStr(PExtended(v[i].VExtended)^); else end; end; Synchronize( procedure begin FEdit.Clear; FEdit.Text := vs; FEdit.Repaint; end ); if Rtn.FWait = 0 then begin while True do begin if Self.Terminated then Break; if FEdit.Tag = 13 then Break; end; Synchronize( procedure begin FEdit.Clear; FEdit.Repaint; end ); end else begin sw := TStopWatch.StartNew; Tick := sw.ElapsedMilliseconds; T := 1000 div 64 * Rtn.FWait; while T > (sw.ElapsedMilliseconds - Tick) do if Self.Terminated then Break; end; end; { Print #1 } procedure Print(v: string); overload; begin Print([v]); end; { Print #2 } procedure Print(v: Double); overload; var s: string; begin s := FloatToStr(v); s := Copy(s, 1, COLUMNS); s := StringOfChar(' ', COLUMNS - Length(s)) + s; Print(s); end; { Print #3 } procedure Pause(v: array of const); overload; var dWait: UInt32; begin dWait := Rtn.FWait; Rtn.FWait := 54; // 0.85s Print(v); Rtn.FWait := dWait; end; { Pause #1 } procedure Pause(v: string); overload; begin Pause([v]); end; { Pause #2 } procedure Pause(v: Double); overload; var s: string; begin s := FloatToStr(v); s := Copy(s, 1, COLUMNS); s := StringOfChar(' ', COLUMNS - Length(s)) + s; Pause(s); end; { Pause #3 } {$ENDREGION} begin try with Rtn do begin { --- ここから --- } { --- ここまで --- } end; except end; end; {$HINTS ON} ``` :::note info `Beep()` をメインスレッドで鳴らすようにしているのは、こちらの方が実機の動きに近いからです。 ::: :::note info TGameThread.Execute 内で 1 文字変数を事前に宣言しているため、H2164 のヒントが発生します。このヒント表示を抑制するために、全体が `{$HINT OFF}~{$HINT ON}` で括られています。 ::: **See also:** - [Delphi コンカレント (並行処理) プログラミング (Qiita)](./e8c1ff3a4c74e4c2a4f3.md) - [H2164 変数 '%s' が宣言されていますが '%s' の中では使用されていません (Delphi) (DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/H2164_%E5%A4%89%E6%95%B0_%27%25s%27_%E3%81%8C%E5%AE%A3%E8%A8%80%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%99%E3%81%8C_%27%25s%27_%E3%81%AE%E4%B8%AD%E3%81%A7%E3%81%AF%E4%BD%BF%E7%94%A8%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%BE%E3%81%9B%E3%82%93_%28Delphi%29) #### ・イベントハンドラ (その2) [オブジェクトインスペクタ] の [イベント] タブでイベントをダブルクリックしてイベントハンドラを作成します。 **btnStart.OnClick** `[開始]` ボタンが押されたらゲームスレッドを実行するようにします。 ```pascal:frmuGameMain.pas procedure TfrmGameMain.btnStartClick(Sender: TObject); begin btnStart.Enabled := False; edDisp.ReadOnly := True; edDisp.Color := $0080B090; edDisp.Font.Color := $00303020; Buf := edDisp.Text; GT := TGameThread.Create(edDisp); GT.OnTerminate := GameEnd; GT.FreeOnTerminate := True; end; ``` ゲームスレッドが終了した時のイベントハンドラも記述しておきます。**implementation** 以下のどこでもいいので以下のコードを記述し、 ```pascal:frmuGameMain.pas procedure TfrmGameMain.GameEnd(Sender: TObject); begin btnStart.Enabled := True; edDisp.ReadOnly := False; edDisp.Color := clWindow; edDisp.Font.Color := clWindowText; edDisp.Text := Buf; end; ``` このイベントハンドラのどこでもいいので内側にカーソルを合わせて `〔Ctrl〕+〔Shift〕+〔C〕`を押すと、宣言部 (**interface**) に宣言が追加されます。 ```pascal:frmuGameMain.pas TfrmGameMain = class(TForm) edDisp: TEdit; btnStart: TButton; procedure FormShow(Sender: TObject); procedure FormKeyPress(Sender: TObject; var Key: Char); private { Private 宣言 } procedure GameEnd(Sender: TObject); // 追加される public { Public 宣言 } end; ``` **frmGameMain.OnCloseQuery** フォームが [x] で閉じられようとした時のイベントハンドラを記述します。 ```pascal:frmuGameMain.pas procedure TfrmGameMain.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin if Assigned(GT) then begin GT.Terminate; while not btnStart.Enabled do Application.ProcessMessages; end; CanClose := True; end; ``` ### ゲームロジック ゲームのロジックをゲームスレッドの `Execute` メソッド内に記述します。 `{$REGION '/* 内部ルーチン */'}` となっている所は、行頭の [-] マークを押す事でコードを折り畳む事ができます。 ![image.png](./images/71151c04-9c78-b77e-c000-c1e5324e814a.png) [+] マークを押すとコードを元のように展開します。 ![image.png](./images/91c21072-5827-4ce1-aa98-dfc1b36839c6.png) コードを折り畳んでおくと、ゲームロジックの記述に専念できていいかと思います。 #### ・ラベルと変数の宣言 Delphi (Pascal) は、**var** ブロックに変数を宣言する必要がありますが [^1]、古いポケコンは 1 文字の変数しか使えないので、1 文字変数を事前に宣言してあります。A~Z が Doubel 型、A_~Z_ (アンダーバー付き) が **string** 型で宣言されています。`A`, `B` 等の数値型変数はそのまま、`C$`, `D$` のような文字列変数は `C_`, `D_` を使う事になります。 Delphi (Pascal) の **goto** 文ではジャンプ先にラベルしか使えないのですが、数値のみのラベルを定義できるので、疑似的な行番号を表現できます。ラベルもまた事前に宣言しておく必要がありますので、次の場所に追加します。 ```pascal:frmuGameMain.pas {$ENDREGION} label // 追加 30, 40, 130, 150, 210, 340, 370, 400, 500; // 追加 begin ... ``` この位置に挿入します。 ![image.png](./images/0df78799-837e-9821-07af-d4e1d6566bd2.png) 他に変数が必要になったら、**var** ブロックを記述して任意の変数を追加する事ができます。 ```pascal:frmuGameMain.pas {$ENDREGION} var // 追加 ZA_: array of string; // 追加 label 30, 40, 130, 150, 210, 340, 370, 400, 500; begin ... ``` #### ・本体 BASIC を Delphi (Pascal) に移植したコードを Execute メソッド内の ```pascal { --- ここから --- } { --- ここまで --- } ``` の間に記述します。BASIC の命令とほぼ 1 対 1 で対応するメソッドが用意されているため、移植は容易です。 ```pascal:frmuGameMain.pas ... begin try with Rtn do begin { --- ここから --- } Random; Wait(250); Print('** OIKAKE MAN **'); Beep(3); X := 0; Y := 0; E := 100; F := 100; T := 0; S := 0; 30:A := Rnd(9); B := Rnd(9); 40:L := Abs(X - A) + Abs(Y - B); if (X = A) and (Y = B) then goto 400; if L = 1 then Beep(3); // if L=1 then Pause('***'); if L = 2 then Beep(2); // if L=2 then Pause('**'); if L = 3 then Beep(1); // if L=3 then Pause('*'); 130:Wait(50); Print(['(', Str(X), ',', Str(Y), ') D=', Str(L), ' E=', Str(E)]); 150:S := S + 1; E := F - Int(S / 2); if E <= 0 then goto 500; G_ := INKEY; if G_ = #$00 then goto 130; Beep(1); IF G_ = '2' then begin Y := Y - 1; goto 210 end; IF G_ = '4' then begin X := X - 1; goto 210 end; IF G_ = '6' then begin X := X + 1; goto 210 end; IF G_ = '8' then begin Y := Y + 1; goto 210 end; goto 150; 210:if X < 0 then begin X := 0; goto 150 end; if Y < 0 then begin Y := 0; goto 150 end; if X > 9 then begin X := 9; goto 150 end; if Y > 9 then begin Y := 9; goto 150 end; if (X = A) and (Y = B) then goto 400; E := F - Int(S / 2); if E <= 0 then goto 500; R := Rnd(5); if R = 1 then begin B := B - 1; goto 340 end; if R = 2 then begin A := A - 1; goto 340 end; if R = 3 then begin A := A + 1; goto 340 end; if R = 4 then begin B := B + 1; goto 340 end; 340:if (A < 0) or (A > 9) then goto 370; if (B < 0) or (B > 9) then goto 370; goto 40; 370:Beep(4); Pause('*** WARP ***'); goto 30; 400:Pause('HIT! HIT!'); Beep(5); Pause('BANG! BANG!'); T := T + 1; C := Rnd(3 * 5); F := F + C; E := F - Int(S / 2); Wait(100); Print(['SCORE ', T]); Print(['ENERGY ', E]); goto 30; 500:Wait(100); Print(['SCORE ', Str(T)]); Wait; Print(' *GAME OVER!!*'); &End; { --- ここまで --- } end; except end; end; {$HINTS ON} ``` オリジナルの BASIC リストと[見比べてみてください](./images/e8120f3f-6f6a-1604-f2e9-4b08d05c8a8b.png)。命令がメソッド (関数) に、**if** 文のマルチステートメント (`:` 区切り) が複合文 (`begin~end`) になったくらいの違いしかありません。Dephi (Pascal) ではこのような **行番号 BASIC** に寄せた書き方も可能です。 :::note warn Delphi (Pascal) は可変個引数を扱えないため、`Print()` や `Pause()` で複数の値を同時に指定するには、`Print([A, 123])` のように、中括弧を使って記述します。これは **型可変オープン配列パラメータ** と呼ばれるものです。 ::: ## 遊んでみよう! コンパイルして実行 (`〔F9〕`) してみると、次のような画面になります。エディットボックスには普通に文字を入力できますが… ![image.png](./images/1b23e0c4-e2ef-13b5-85e3-c190c71504ba.png) [開始] ボタンを押すとゲームが開始されます。 ![image.png](./images/bef39650-94eb-d6c6-5283-f14262458547.png) ![image.png](./images/b3bce4a1-036f-378d-95a7-ac112c6040bd.png) ゲームオーバーになったら〔Enter〕キーで元の画面に戻ります。 ![image.png](./images/7ead6228-b012-569b-7116-eb305f895812.png) 入力されていた文字も元に戻ります。 ![image.png](./images/1b23e0c4-e2ef-13b5-85e3-c190c71504ba.png) くれぐれも仕事で使うアプリケーションに組み込まないでくださいね。バレて怒られても私は責任を取りません (w ### バグ発見 ん?ゲーム中に `[x]` で閉じた時にすぐにゲームが終了しませんね。スレッドの終了を調べずにキーループを回しているため、体力のカウントダウンが終わるまではスレッドを抜けられないようです。キーループ内に一行追加して、すぐにスレッドを抜けられるようにしましょう。 ```pascal:frmuGameMain.pas 130:Wait(50); Print(['(', Str(X), ',', Str(Y), ') D=', Str(L), ' E=', Str(E)]); if Terminated then Exit; // 追加 150:S := S + 1; E := F - Int(S / 2); if E <= 0 then goto 500; G_ := INKEY; if G_ = #$00 then goto 130; ``` ### 音ウルサイ PC-1246 相当のプログラムに改変してください。キー入力や接近時の BEEP で処理が止まらないので難易度は少し上がるかもです。 ### 改造して遊ぶ ソースコードのあるゲームは改造して遊ぶのも楽しいです! - BASIC でいう 20 行目の E の値 (現在の体力) や F の値 (体力最大値) をいじると難易度を変更できます。 - BASIC でいう 130 行目の `Wait(50)` の値を小さくすると難易度が上がります (64 = 1s)。 - エディットボックスの幅を広げた上で、BASIC でいう 130 行目の `Print()` を 次のように書き換えると虫の位置が判ります。 ```pascal 130:Wait(50); Print(['(', Str(X), ',', Str(Y), ') D=', Str(L), ' E=', Str(E), ' (', Str(A), ',', Str(B), ')']); ``` 虫の位置が判ってもそこそこ難しいんですけどね、このゲーム。 ![image.png](./images/8e8d3ed8-09d1-b8e7-1178-a7fead6f2e64.png) # おわりに Pascal で限りなく BASIC に近いコードを書いてみましたが、いかがだったでしょうか? ![image.png](./images/09584979-4416-7f1a-a7da-56c457abea84.png) [Delphi Advent Calendar 2022](https://qiita.com/advent-calendar/2022/delphi) に間に合わせるために急いで作ったのであまりいいコードではないと思います。識者の方に添削して頂けると幸いです m(_ _)m ### More ポケコンゲーム移植 グラフィック機能等を使っていなければ、他のゲームも移植可能です。この記事で使われている `uPokecomUtils.pas` はすべての BASIC 命令を実装していないので、適宜命令を実装する必要はあります。 スレッドクラスの Execute メソッドに、BASIC コードの移植を書けばいいだけなので楽ちんです。Delphi (Pascal) の文法を詳しくない方でも、あのコードなら結構読めると思うんですよね。 折角なので、ポケコンゲーム移植用のスケルトンプロジェクトを用意してみました。 - [Pokecom_Skeleton_100.zip (ht-deko.com)](https://ht-deko.com/software/Pokecom_Skeleton_100.zip) ゲームロジック以外は記述されていますので、 1. [Delphi Community Edition](https://www.embarcadero.com/jp/products/delphi/starter) を DL してインストール。 2. スケルトンを DL して適当な場所に解凍し、Delphi で `[ファイル | プロジェクトを開く]` で開く。 3. [ゲームロジック](#ゲームロジック)を組み込み。 4. `〔F9〕`でコンパイル&実行。 これだけで「追いかけマン」が動作します [^2]。 ### FireMonkey への移植 キー入力待ちにプラットフォーム依存の機能を使わなかったので、FireMonkey への移植は難しくないと思います。面倒なのは BEEP くらい? **See also** - [標準 Pascal 範囲内での Delphi 入門 (Qiita)](./e2796788c65c2cda1de5.md) - [TLCD5x7 (LCD コンポーネント) (Delphi Forum)](https://ht-deko.com/delphiforum/?vasthtmlaction=viewtopic&t=1243.0) [^1]: Delphi 10.3 Rio 以降でインライン変数宣言が可能になっています。 [^2]: 今更、僕らがポケコンでやっていた苦行 (写経) をしろとは申しません (^^;A