【DELPHI STARTER チュートリアルシリーズ】 シーズン2 第8回 ‟作ってみよう„ Delphiの部[JAPAN]
Delphi Starter Edition チュートリアルシリーズ
シーズン2 第8回 「作ってみよう」Delphiの部
2017年1月23日より 「Delphi / C++Builder Starter チュートリアルシリーズ」 シーズン2、全9回、3月27日まで、毎週月曜日、Delphiパートが 17時00分~17時20分、 リアルタイム放送スペシャルコンテンツが5分~10分、C++Builderパートが 17時30分~17時50分の時間割でお送りしています。
無料でダウンロード & 利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、プログラミング言語のDelphi (Object Pascal ), C++の基礎を学ぶオンラインセッションです。
[第8回 「作ってみよう」Delphiの部]
アジェンダ
- ねらい
- 第1~7回までの機能を使ったDelphi(FireMonkey) プログラム
- 実施内容
- 三目並べゲーム
今回の第8回で紹介しているコードを実際にみれるサンプルをGithubに上げております。
以下のリンクからダウンロードしてご確認ください。
https://github.com/mojeld/embarcadero_noughts_and_crosses/
[ルール]
- 9個(3x3)のボタン順に〇と×先に3つ並べた方が勝ち。
- コンピュータと1対1の対戦をします。
- 先攻・後攻が選べます。
[画面デザイン]
[三目並べ 処理]
- ゲームボタンのクリア
- ボタンクリック”〇”を入れる
- 三目並びリストからCOMが候補を選んで”×”を入れる
- 三目が並んだかを判定
- 三目パターン配列を作る
- 勝敗判定とコンピュータ側の思考
[三目並べ 処理クラスを作る]
//// TMarupeke = class(TObject) constructor Create(const tim_: TTimer); destructor Destroy; override; private Fstart_flg: Boolean; FButtons: TList<TSpeedButton>; //1~9までのボタン FMainTimer: TTimer; //メイン側のタイマー function getButtons: TList<TSpeedButton>; public ///<summary>ボタンを呼出すプロパティ</summary> property Buttons: TList<TSpeedButton> read getButtons; procedure game_clear(start, attacked: Boolean; event_: TNotifyEvent); class procedure line_list(proc: TProc<TLines>); function triumph: Integer; function triumph_row(r: TLines): Integer; property start_flg: Boolean read Fstart_flg write Fstart_flg; end;
TList<>がジェネリッス型のクラスです。 http://docwiki.embarcadero.com/Libraries/Berlin/ja/System.Generics.Collections.TList
- ジェネリックスは、アルゴリズム(手続き、関数など)やデータ構造(クラス、インターフェイス、レコードなど)を、そこで使用される 1 つ以上の特定の型から切り離せるようにする一連の抽象化ツールです。
- 以前実施した DELPHI BOOT CAMP / DELPHIでビジュアル開発に挑戦しよう ◆ DAY3 にてご紹介しています。
[クラス定義を別のユニットに分離]
- ユニットの追加
- メニューから [新規作成 | ユニット - Delphi]
- なぜ別クラスにするのか
- 1つ型を作っていれば他で使える
- なぜ別ユニットファイルに分けたのか
- 別プロジェクトを作成した場合ファイルが共有できる
[三目並べ 処理クラスを作る]
- メインフォームからTMarupekeクラスのインスタンスを作る
-
//// uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Layouts, FMX.Controls.Presentation, FMX.StdCtrls, FMX.ScrollBox, FMX.Memo, System.DateUtils, FMX.Objects, System.Generics.Collections, Unit2;
usesにUnit2を追加します
-
//// type TForm1 = class(TForm) procedure SpeedButton12Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure FormDestroy(Sender: TObject); private Marupeke1: TMarupeke; public end;
TForm1のプライベート変数としてTMarupeke を追加
(このフォームだけで使用するものなので privateのところに記述しています)
[OnCreateとOnDestroyにTMarupeke.Createの記述と、DisposeOfの記述を追加]
TMarupekeはMarupeke1と言う変数名で利用します。
そのための記述です
//// procedure TForm1.FormCreate(Sender: TObject);//フォーム作成時 var i: Integer; begin Marupeke1 := TMarupeke.Create(Timer1); //TMarupekeインスタンスを作る for i := 0 to 8 do //1~9のボタンをTMarupekeにセット Marupeke1.Buttons.Add( TSpeedButton(Self.FindComponent(Format('SpeedButton%d', [i+1]))) ); Timer1.Enabled := False; SpeedButton10.IsPressed := True; Randomize; Marupeke1.game_clear(False, SpeedButton11.IsPressed, SpeedButtonsClick); end; procedure TForm1.FormDestroy(Sender: TObject);//フォーム終了時 begin Marupeke1.DisposeOf; //TMarupeke捨てる end;
[初期設定 ゲームボタン表示など全て初期化(クリア)する]
TMarupekeクラス内にgame_clear()を作る
// procedure TMarupeke.game_clear(start, attacked: Boolean; event_: TNotifyEvent); var btn_: TSpeedButton; begin for btn_ in FButtons do //FindComponentで1~9までのSpeedButtonを探す with btn_ do begin IsPressed := False; //プレス OnClick := event_; //共通クリックイベントの設定 Text := ''; Tag := 0; if start then Enabled := True else Enabled := False; end; FMainTimer.Enabled := attacked; //後攻ならCOMのターン end;
TMarupeke.game_clear()をコールする
- フォーム作成時や、最初からゲームを開始する時にこのメソッドを呼びます
/// Marupeke1.game_clear(False, SpeedButton11.IsPressed, SpeedButtonsClick);
[ボタンクリック”〇”を入れる]
1-9までのボタン共通イベントを作成する
それぞれのTSpeedButtonのOnClickイベントに共通の関数をセットする。
// procedure TForm1.SpeedButtonsClick(Sender: TObject); begin ///三目9マスのボタンのクリックイベント with (Sender as TSpeedButton) do begin if Tag = 0 then begin IsPressed := True; Tag := player; ///Unit2の定数の値 Text := '〇'; ///プレーヤは先攻/後攻関係なく〇が入る if Marupeke1.triumph > 1 then begin ///条件が揃ったらWinを出してゲームクリア ShowMessage('Win'); Marupeke1.game_clear(False, SpeedButton11.IsPressed, SpeedButtonsClick); end; Timer1.Enabled := True; ///タイマーを使いコンピュータへターンを渡す end; end; end;
[三目パターン配列を作る]
1-9までのボタン三目並べれるパターン
TMarupekeのクラスメソッド(line_list)を追加します。
//// class procedure TMarupeke.line_list(proc: TProc<TLines>); const triumph_naname: array[0..2, 0..2] of Integer = ((0,4,8),(2,4,6),(0,0,0)); var i: Integer; i1: Integer; i2: Integer; z: TLines; zi: Integer; begin SetLength(z, 3); for i := 0 to 2 do for i1 := 0 to 2 do begin for i2 := 0 to 2 do begin case i of 0:z[i2] := i2 + (i1*3); 1:z[i2] := (i2*3) + i1; 2: begin z[i2] := triumph_naname[i1,i2]; end; end; end; if not ((z[0] = 0) and (z[1] = 0) and (z[2] = 0)) then proc(z); end; end;
このパターン配列の利用は下記項目です
line_listで無名メソッドを使っています。
[Delphi での無名メソッド]
無名メソッドとは、名前が付いていないプロシージャや関数のことです。
変数に代入したりメソッドへのパラメータとして使用できるエンティティとしてコード ブロックを扱います。さらに、無名メソッドは、自らが定義されるコンテキストで変数を参照したりそれらの変数に値をバインドすることもできます。無名メソッドは、簡単な構文で定義および使用することができます。
以前実施した DELPHI BOOT CAMP / DELPHIでビジュアル開発に挑戦しよう ◆ DAY3 にてご紹介しています。
https://community.embarcadero.com/blogs/entry/delphi-boot-camp-day3-japan
[無名メソッドを なぜ使うのか]
このコーディング方法を初めから覚えておくと後で困らない
- 新しい技術でも特殊なものでも無い為最初から無名メソッドありきで思考する方が有利です
- ある程度覚えてからだと、このコーディングスタイルに慣れるまで時間がかかります
無名メソッドは、JAVAやC#, Node.jsなどにも使われている為他の言語からDelphi移植する場合、容易に理解できる思考が身につきます
[コンピュータ側(×側)で次の手を考える]
コンピューター側の処理は全てタイマーイベントを使っています。
//// procedure TForm1.Timer1Timer(Sender: TObject); var random_: Integer; sp_button_: TSpeedButton; next_: Boolean; dtStart: TDateTime; kouho: Integer; bflg: Boolean; counts_: TCounts; begin Timer1.Enabled := False; dtStart := IncSecond(Now, 1); ///タイムアウト用1秒以上経過するとタイムアウト処理 next_ := False; kouho := -1; if Marupeke1.start_flg then begin bflg := False; ///無名メソッドを使い次の手を考えさせます TMarupeke.line_list(procedure (line_: TLines) var i: Integer; begin counts_ := TCounts.Create(0,0); {************************************************************************} { TLines配列に〇と×が何個入っているか調べる このコードではプレーヤ側のリーチ目だけを見ています。(保守的) Question: 自分(コンピュータ)のリーチ目を調べて自分から勝つコードを 追記修正してみてください。 } {************************************************************************} for i:=0 to Length(line_)-1 do begin sp_button_ := TSpeedButton(Self.FindComponent(Format('SpeedButton%d', [line_[i]+1]))); if sp_button_.Tag = player then Inc(counts_.count1); if sp_button_.Tag = computer then Inc(counts_.count2); if (sp_button_.Tag = 0) and (not bflg) then begin kouho := line_[i];//次の手の候補を選ぶ end; end; if (counts_.count1 >= 2) and (counts_ < TCounts.Create(3,0)) then begin //TLines配列に〇が2つある場合で配列が埋まっていない場合 counts_ := TCounts.Create(0,0); bflg := True; end; end); while (not next_) do begin if kouho >= 0 then //次の手があればそれを利用し random_ := kouho else //手が何も無い場合randomで選ぶ random_ := Random(9); Sleep(100); sp_button_ := TSpeedButton(Self.FindComponent(Format('SpeedButton%d', [random_+1]))); if sp_button_.Tag = 0 then begin ///ここでコンピュータ側の手を入れます。 sp_button_.Tag := computer; ///定数 sp_button_.Text := '×'; ///コンピュータ側は先攻/後攻全て× sp_button_.IsPressed := True; next_ := True; end else kouho := -1; if (dtStart < Now) then begin //無限ループしないようにタイムアウトを作る next_ := True; ShowMessage('draw'); end; end; {*********************************************************************} { triumphは三目並べれば1以上を返しています。 Question: function TMarupeke.triumph: Integer;を変更し Win/Loseを作ってみてください。 } {*********************************************************************} if Marupeke1.triumph > 1 then begin //triumphで勝敗がついている場合 ShowMessage('Win'); Marupeke1.game_clear(False, SpeedButton11.IsPressed, SpeedButtonsClick); end; end; end;
ここまででコンピュータ側の"×"攻撃部分は完了です。
このタイマーイベント内でTCountsを使っています。
//// TCounts = record count1: Integer; count2: Integer; constructor Create(c1, c2: Integer); overload; class operator LessThan(a: TCounts; b: TCounts): Boolean; end;
このTCountsはcount1をプレーヤー、count2をコンピュータの入力された回数です
他タイマーイベント内にtriumphメソッドが呼ばれています。
//// function TMarupeke.triumph: Integer; var answer_: Integer; begin answer_ := 0; line_list(procedure (z: TLines) begin if (z[1] > 0) and (z[2] > 0) then answer_ := triumph_row(z); //三目揃ったか? if answer_ >1 then //どちらかが三目揃った場合 begin Fstart_flg := False; FMainTimer.Enabled := False; end end); if not Fstart_flg then Result := 2 else Result := answer_; end; function TMarupeke.triumph_row(r: TLines): Integer; var com_count: Integer; iTag, temp_tag: Integer; answer_: Integer; begin answer_ := 0; iTag := -1; for com_count := 0 to Length(r)-1 do begin temp_tag := FButtons[r[com_count]].Tag; if not(temp_tag = 0) then if (not(com_count=0) and (temp_tag = iTag)) then Inc(answer_) else iTag := temp_tag; end; Result := answer_; end;
これは三目揃ったのかを判断するメソッドです。
TMarupeke.triumph内部でさらにfunction TMarupeke.triumph_row(r: TLines): Integer;をコールしています。
タイマーイベント中に"Question"と言うコメントがあります。
//// { TLines配列に〇と×が何個入っているか調べる このコードではプレーヤ側のリーチ目だけを見ています。(保守的) Question: 自分(コンピュータ)のリーチ目を調べて自分から勝つコードを 追記修正してみてください。 } {************************************************************************} { triumphは三目並べれば1以上を返しています。 Question: function TMarupeke.triumph: Integer;を変更し Win/Loseを作ってみてください。 }
ぜひチャレンジしてみてください。
あと先行は"○"。後攻は"×"などの処理を入れてオリジナルのゲームを作っていくのも楽しいかと思います。
近いうちに上記2つのQuestionの答えをGithubで更新かける予定をしています。


Comments
-
Please login first in order for you to submit comments