Delphi で「追いかけマン」の Windows アプリケーションを作る

# はじめに

皆さんは、人生で初めて入力したプログラムを覚えているでしょうか？

私は覚えています。ポケットコンピュータ『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) &lt;- 無償版
 - [OBJECT PASCAL HANDBOOK for 11 ALEXANDRIA (Embarcadero)](https://lp.embarcadero.com/ObjectPascalHandbookD11) &lt;- 言語解説 (英語、無償)

# 「追いかけマン」
**「追いかけマン (英語名: 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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)](https://qiita.com/ht_deko/items/3d8b06f459c3d861b5d5)

#### ・イベントハンドラ (その1)
`frmuGameMain` に戻り、[オブジェクトインスペクタ] の [イベント] タブでイベントをダブルクリックしてイベントハンドラを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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)](https://qiita.com/ht_deko/items/e8c1ff3a4c74e4c2a4f3)
 - [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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/71151c04-9c78-b77e-c000-c1e5324e814a.png)
[+] マークを押すとコードを元のように展開します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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 リストと[見比べてみてください](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/e8120f3f-6f6a-1604-f2e9-4b08d05c8a8b.png)。命令がメソッド (関数) に、**if** 文のマルチステートメント (`:` 区切り) が複合文 (`begin～end`) になったくらいの違いしかありません。Dephi (Pascal) ではこのような **行番号 BASIC** に寄せた書き方も可能です。

:::note warn
Delphi (Pascal) は可変個引数を扱えないため、`Print()` や `Pause()` で複数の値を同時に指定するには、`Print([A, 123])` のように、中括弧を使って記述します。これは **型可変オープン配列パラメータ** と呼ばれるものです。
:::

## 遊んでみよう！
コンパイルして実行 (`〔F9〕`) してみると、次のような画面になります。エディットボックスには普通に文字を入力できますが…
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/1b23e0c4-e2ef-13b5-85e3-c190c71504ba.png)
[開始] ボタンを押すとゲームが開始されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/bef39650-94eb-d6c6-5283-f14262458547.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/b3bce4a1-036f-378d-95a7-ac112c6040bd.png)
ゲームオーバーになったら〔Enter〕キーで元の画面に戻ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/7ead6228-b012-569b-7116-eb305f895812.png)
入力されていた文字も元に戻ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/8e8d3ed8-09d1-b8e7-1178-a7fead6f2e64.png)


# おわりに
Pascal で限りなく BASIC に近いコードを書いてみましたが、いかがだったでしょうか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/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)](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5)
 - [TLCD5x7 (LCD コンポーネント) (Delphi Forum)](https://ht-deko.com/delphiforum/?vasthtmlaction=viewtopic&t=1243.0)

[^1]: Delphi 10.3 Rio 以降でインライン変数宣言が可能になっています。
[^2]: 今更、僕らがポケコンでやっていた苦行 (写経) をしろとは申しません (^^;A