【C++Builder Starter チュートリアルシリーズ】シーズン2 第9回 ‟作ってみよう„ [JAPAN]

Posted by on in Programming

2017年1月23日より 「Delphi / C++Builder Starter チュートリアルシリーズ」 シーズン2、全9回、3月27日まで、毎週月曜日、Delphiパートが 17時00分~17時20分、 リアルタイム放送スペシャルコンテンツが5分~10分、C++Builderパートは 17時30分~17時50分の時間割でお送りしています。(なお、後述いたしますが、残り2週分の放送はDelphi/C++Builderの2部制ではありません

無料でダウンロード&利用できる開発環境のDelphi / C++Builder Starter エディションを使用して、プログラミング言語のDelphi (Object Pascal ), C++の基礎を学ぶオンラインセッションです。

https://community.embarcadero.com/blogs/entry/2-japan

 

ねらい

  • 第1~7回までの機能を使った C++Builder(FireMonkey) プログラム
  • C++11の機能を利用

実施内容

  • 三目並べゲーム

ルール

  • 9個(3x3)のボタン順に〇と×先に3つ並べた方が勝ち。
  • コンピュータと1対1の対戦をします。
  • 先攻・後攻が選べます。

ソースコード

https://github.com/mojeld/embarcadero_noughts_and_crosses/tree/master/marupeke_cpp

 

画面デザイン

 

三目並べ 処理

  • ゲームボタンのクリア
  • ボタンクリック”〇”を入れる
  • 三目並びリストからCOMが候補を選んで”×”を入れる
  • 三目が並んだかを判定
    • 三目パターン配列を作る
    • 勝敗判定とコンピュータ側の思考

 

三目並べ 処理クラスを作る

別ユニットを用意しTMarupekeクラスを作ります

////
//---------------------------------------------------------------------------

#include <array>
#include <random>
#include <functional>
//---------------------------------------------------------------------------
namespace tutorial2
{
	using inherited = System::TObject;
	using button_array = std::array<TSpeedButton* , 9>;
	class TMarupeke : public inherited
	{
	private:
		TTimer* f_main_timer;
		button_array f_buttons;
		std::random_device rnd_;
		std::mt19937 	randomize_{rnd_()};
		bool f_start_flg{false};
		button_array& __fastcall getButtons();
		int __fastcall triumph_row(int r[]);
	public:
		__fastcall TMarupeke(TTimer* tm );
		__fastcall virtual ~TMarupeke();
		///ゲームのクリア(ボタンなど初期化)
		void __fastcall game_clear(bool start, bool attacked, TNotifyEvent _event);
		///パターン用関数
		static void __fastcall line_list(std::function<void(int[])> _proc);
		///〇もしくは×揃った場合 return 2
		int __fastcall triumph();
		///ランダムで0~maxまでを返す
		unsigned int __fastcall select_random(const unsigned int max);

		///ボタン配列
		__property button_array& Buttons = {read = getButtons};
		///ゲームスタートflg
		__property bool start_flg = {read = f_start_flg, write = f_start_flg};
	};
}

いくつかのSTLを使っています。

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Dinkumware 標準 C++ ライブラリ

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/C++の名前空間:インデックス

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Static_cast(型変換演算子)

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/テンプレートでの型保障された汎用リストの使い方

 

 

クラス定義を別のユニットに分離

ユニットの追加

  • メニューから [新規作成 | ユニット – C++Builder] 
  • 保存は「 uMarupeke.cpp」

なぜ別クラスにするのか

  • 1つ型を作っていれば他で使える

なぜ別ユニットファイルに分けたのか

  • 別プロジェクトを作成した場合ファイルが共有できる

 

三目並べ 処理クラスを作る

ゲームボタンのクリア

 

TMarupekeクラス内にgame_clear()を作る

//////
void __fastcall TMarupeke::game_clear(bool start, bool attacked, TNotifyEvent _event)
{
	///ボタンなど初期クリア
	for (auto _btn : f_buttons)
	{
		_btn->IsPressed = false;  //プレス
		_btn->OnClick   = _event; //共通クリックイベントの設定
		_btn->Text      = "";
		_btn->Tag       = 0;
	  (start)? _btn->Enabled = true: _btn->Enabled = false;
	}
	f_main_timer->Enabled = attacked;   //タイマーを引数に合わせてセットする
}

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/条件演算子

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/Auto

 

TMarupeke.game_clear()をコールする

  • フォーム作成時や、最初からゲームを開始する時
////
marupeke1->game_clear(false, SpeedButton11->IsPressed, SpeedButtonsClick);

 

ボタンクリック”〇”を入れる

1-9までのボタン共通イベントを作成する

  • それぞれのTSpeedButtonのOnClickイベントに共通の関数をセットする。
////
void __fastcall TForm1::SpeedButtonsClick(TObject *Sender)
{
	///プレーヤーのボタンをクリックした場合のイベント(〇側)
	auto _btn = static_cast<TSpeedButton* >(Sender);
	if (_btn->Tag == (NativeInt)0)
	{
		_btn->IsPressed	= true;
		_btn->Tag			= tutorial2::player;
		_btn->Text		= L"〇";
		if (marupeke1->triumph() > 1)
		{
			ShowMessage("Win");
			marupeke1->game_clear(false, SpeedButton11->IsPressed, SpeedButtonsClick);
		}
		Timer1->Enabled	= true;
	}
}

 

三目パターン配列を作る

1-9までのボタン三目並べれるパターン

////
void __fastcall TMarupeke::line_list(std::function<void(int[])> _proc)
{
	///斜めパターンの初期化
	std::array<std::array<int, 3> , 3> triumph_naname{
		{ {{0,4,8}},{{2,4,6}},{{0,0,0}} } };
	///縦横のパターンはループで作成
	int z[3]{0,0,0};
	for (int i1 = 0; i1 < 3; i1++)
		for (int i2 = 0; i2 < 3; i2++)
		{
			for (int i3 = 0; i3 < 3; i3++)
			{
				switch (i1)
				{
				case 0:
					z[i3] = i3 + (i2*3); break;
				case 1:
					z[i3] = (i3*3) + i2; break;
				case 2:
					z[i3] = triumph_naname[i2][i3]; break;
				default:
					;
				}
			}
			if (!((z[0] == 0) && (z[1] == 0) && (z[2] == 0))) {
				_proc(z);
			}
		}
}

 

引数にはstd::functionを使っています。

作った配列は2つの用途に利用する

  • コンピュータ側(×側)で次の手を考える時
  • 勝負が決まったか判断する時

 

コンピュータ側(×側)で次の手を考える

タイマーイベントを作成し

////

void __fastcall TForm1::Timer1Timer(TObject *Sender)
{
///ここにコンピュータ次の手を実装
}

タイマーイベント内処理1

・・・・・
tutorial2::TMarupeke::line_list([this, &bflg, &kouho](int _line[3]){
	tutorial2::TCounts _counts{0,0};
	for (int i=0; i < 3;i++)
	{
		auto _btn = static_cast<TSpeedButton*>(
			this->FindComponent(Format("SpeedButton%d", ARRAYOFCONST((_line[i]+1)) )));
		if ((int)_btn->Tag == tutorial2::player)		_counts.count1++;
		if ((int)_btn->Tag == tutorial2::computer) 	_counts.count2++;

		if (((int)_btn->Tag == 0) && (!bflg) )
			kouho = _line[i]; //次の手の候補を選ぶ
	}

	tutorial2::TCounts temp{3,0};
	if ((_counts.count1 >= 2) && (_counts < temp))
	{
		_counts = tutorial2::TCounts{0,0};
		bflg    = true;
	}

});

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/ラムダ式

lambdaを なぜ使うのか

  • このコーディング方法を初めから覚えておくと後で困らない
  • C++11からの新しい技術ですがこれから主流となるコーディングスタイルなので利用する方が有利です
  • ある程度覚えてからだと、このコーディングスタイルに慣れるまで時間がかかります

次の手を考える用の構造体を作る

http://docwiki.embarcadero.com/RADStudio/Tokyo/ja/構造体:インデックス

//
struct TCounts
{
	int count1{0},
		count2{0};
	TCounts(int i1,int i2)  {count1 = i1; count2 = i2;}
	TCounts(const TCounts& c) _ALWAYS_INLINE {count1 = c.count1; count2 = c.count2;}
	inline bool operator <(const TCounts& a) {
		if ((count1 + count2) < (a.count1 + a.count2)){
			return true;
		}
		else
			return false;
    }

};

タイマーイベント内処理2

////
bool _next{false};  //ループ用
while (! _next)
{
	std::this_thread::sleep_for(microseconds(100));
	unsigned int _random{0};
	(kouho >= 0)?	//次の手があればそれを利用し
					//手が何も無い場合randomで選ぶ
		_random = kouho: _random = marupeke1->select_random(9);

	auto _btn = static_cast<TSpeedButton*>(
		this->FindComponent(Format("SpeedButton%d", ARRAYOFCONST((_random+1)) )));
	if ((int)_btn->Tag == 0)
	{
		_btn->Tag        = tutorial2::computer;
		_btn->Text       = "×";   //COMが×を入れる
		_btn->IsPressed  = true;
		_next = true;
	}
	else
		kouho = -1;

	if (dt_start <= system_clock::now()) //無限ループしないようにタイムアウトを作る
	{
		_next = true;
		ShowMessage("draw");
	}
}

コンピュータ次の手が無い場合ランダムを作っています。

////
unsigned int __fastcall TMarupeke::select_random(const unsigned int max)
{
    //0~maxまでのランダム作って返す
	std::uniform_int_distribution<int> randomm_( 0, max-1 ) ;
	return randomm_(randomize_);
}

勝敗判定(タイマーイベント内)

/////
if (marupeke1->triumph() > 1) //triumphで勝敗がついている場合
{
	ShowMessage("Win");
	marupeke1->game_clear(false, SpeedButton11->IsPressed, SpeedButtonsClick);
}

triumph関数内部

////
int __fastcall TMarupeke::triumph_row(int r[])
{
	///勝敗判定その2
	int _answer{0};
	int tag_num{0}, temp_tag_num{0};
	for (int com_count = 0;com_count < 3; com_count++)
	{
		temp_tag_num = f_buttons[r[com_count]]->Tag;
		if (temp_tag_num != 0)
			((com_count != 0) && (tag_num == temp_tag_num))?
				_answer++: tag_num = temp_tag_num;
	}
	return _answer;
}

int __fastcall TMarupeke::triumph()
{
	///勝敗判定その1
	int _answer{0};
	line_list([this, &_answer](int z[3]){
		if ( (z[1] > 0) && (z[2] > 0) )
			_answer = triumph_row(z);  //三目揃ったか?

		if (_answer > 1)  //どちらかが三目揃った場合
		{
			f_start_flg = false;
			f_main_timer->Enabled = false;
		}
	});
	if (f_start_flg){
		return _answer;
	}
	else
		return 2;
}

 

セミナー動画

チュートリアルセミナー視聴ページにて、シーズン2の放送分を視聴できるようにしています。チュートリアルシリーズに申し込み済みの方はメールにてご案内済みの視聴ページよりご視聴ください。まだ申し込んでいない方は下記アドレスよりお申込みいただくと視聴できます。

http://forms.embarcadero.com/starter-tutorial-webinar

また、シーズン1の放送分は youtube の下記ページよりご視聴いただけます。

https://www.youtube.com/playlist?list=PLoQxxVNY10oEmNSjiPdYPV_E9IL9ipico

シーズン1のまとめ記事もございますので、こちらも併せてお読みください。

https://community.embarcadero.com/blogs/entry/delphi-c-builder-starter-japan

■最終回となった Starter チュートリアルシリーズで、シーズン2の締めとして “作ってみよう„ をお送りしました。

 


About
Gold User, No rank,
Delphi / C++Builder blogger

Comments

Check out more tips and tricks in this development video: