脳内補完を現実にすることで物理的制約により準優先が決まる

想像とか机上でなんとなくできているような感じがして、それで満足したり試行錯誤したりするわけですが、現実に当てはめれてみればなんということはない「物理的な制約」が先にあって、実現不可能な選択肢が削られて話が簡単になること、というのはプログラムの話。いや、煮詰めすぎると無理が来るってことか、あるいは時間的な制約ということか。

チープな画面を

少しリッチな画面に

変えていきます。

花札の画像は、Windows ストア アプリのサンプル画像からピックアップ。

■UIのプロジェクトを分ける

チープな画面プロジェクト(Hanahuda)とは別にリッチなUI用の HanahudaUI プロジェクトを作ります。

.NET のライブラリ参照が便利なのは、元の Hanahuda アセンブリのクラスを UI プロジェクトでも簡単に参照できるところですね。簡単すぎて、先行き複雑になったとき(特に複数人数で開発になったとき)には困ることが多いのですが、ロジックと UI を強引に分離させたいときは、こういう風にプロジェクトを分けてしまいます。と云いますか、元のチープな画面はロジックテスト用に残しておいて、リッチな UI チェック用は別に作る、って方法ですね。

■花札のリソースを追加

となると、花札の画像はどちらに置くかというと、UI プロジェクトに置くわけで、このリソースの名前と花札の ID とをどうやって結び付けるのかが問題になるのです。同じプロジェクトに置けば、リソースを参照するのも楽なのですが、ここは UI を別にしておきたいし。そうなると、UI の方に花札のリソースが含まれるのは当然なわけで。そうなると、プロジェクトは HanahuhaUI から Hanhuda を参照する一方向しかできないわけで、さて、ID とリソースの関係はどうするのか?ってのが問題で悩みます。

が、現実的なところ

  • ひとつにはまとめたくない(まとめない)
  • 循環参照はできない。

という制約のなかで、以下のような対比表が書かれることになるわけです。
“A1” というのは花札の ID なので、Hanahuda プロジェクトの ID クラスとダブって書かれることになります。C 言語の場合は、インクルードファイルってことになるのですが、果たして .NET の場合はどうするべきなのか?ってところですね。業務的には、別の ID 定義用のプロジェクトをひとつ作って、enum か static を使って Card.A1 とか、ちまちまと定義することになるのですが…まあ、無駄なプロジェクトを増やすよりは、ダブって書くということで、今回はあきらめ。

class CardUI
{
	protected static Dictionary<string, Bitmap> _resNames;
	public static Bitmap GetUra()
	{
		return Properties.Resources.FC097_2;
	}
	public static Bitmap GetResName(string id) 
	{
		if (CardUI._resNames == null)
		{
			CardUI._resNames = new Dictionary<string, Bitmap>();
			CardUI._resNames["A4"] = Properties.Resources.FC001;
			CardUI._resNames["A3"] = Properties.Resources.FC002;
			CardUI._resNames["A2"] = Properties.Resources.FC003;
			CardUI._resNames["A1"] = Properties.Resources.FC004;
			CardUI._resNames["B4"] = Properties.Resources.FC005;
			CardUI._resNames["B3"] = Properties.Resources.FC006;
			CardUI._resNames["B2"] = Properties.Resources.FC007;
			CardUI._resNames["B1"] = Properties.Resources.FC008;
			CardUI._resNames["C4"] = Properties.Resources.FC009;
			CardUI._resNames["C3"] = Properties.Resources.FC010;
			CardUI._resNames["C2"] = Properties.Resources.FC011;
			CardUI._resNames["C1"] = Properties.Resources.FC012;
			CardUI._resNames["D4"] = Properties.Resources.FC013;
			CardUI._resNames["D3"] = Properties.Resources.FC014;
			CardUI._resNames["D2"] = Properties.Resources.FC015;
			CardUI._resNames["D1"] = Properties.Resources.FC016;
			CardUI._resNames["E4"] = Properties.Resources.FC017;
			CardUI._resNames["E3"] = Properties.Resources.FC018;
			CardUI._resNames["E2"] = Properties.Resources.FC019;
			CardUI._resNames["E1"] = Properties.Resources.FC020;
			CardUI._resNames["F4"] = Properties.Resources.FC021;
			CardUI._resNames["F3"] = Properties.Resources.FC022;
			CardUI._resNames["F2"] = Properties.Resources.FC023;
			CardUI._resNames["F1"] = Properties.Resources.FC024;
			CardUI._resNames["G4"] = Properties.Resources.FC025;
			CardUI._resNames["G3"] = Properties.Resources.FC026;
			CardUI._resNames["G2"] = Properties.Resources.FC027;
			CardUI._resNames["G1"] = Properties.Resources.FC028;
			CardUI._resNames["H4"] = Properties.Resources.FC029;
			CardUI._resNames["H3"] = Properties.Resources.FC030;
			CardUI._resNames["H2"] = Properties.Resources.FC031;
			CardUI._resNames["H1"] = Properties.Resources.FC032;
			CardUI._resNames["I4"] = Properties.Resources.FC033;
			CardUI._resNames["I3"] = Properties.Resources.FC034;
			CardUI._resNames["I2"] = Properties.Resources.FC035;
			CardUI._resNames["I1"] = Properties.Resources.FC036;
			CardUI._resNames["J4"] = Properties.Resources.FC037;
			CardUI._resNames["J3"] = Properties.Resources.FC038;
			CardUI._resNames["J2"] = Properties.Resources.FC039;
			CardUI._resNames["J1"] = Properties.Resources.FC040;
			CardUI._resNames["K4"] = Properties.Resources.FC041;
			CardUI._resNames["K3"] = Properties.Resources.FC042;
			CardUI._resNames["K2"] = Properties.Resources.FC043;
			CardUI._resNames["K1"] = Properties.Resources.FC044;
			CardUI._resNames["L4"] = Properties.Resources.FC045;
			CardUI._resNames["L3"] = Properties.Resources.FC046;
			CardUI._resNames["L2"] = Properties.Resources.FC047;
			CardUI._resNames["L1"] = Properties.Resources.FC048;
		}
		return _resNames[id];
	}
}

■場と手札を表示するためのユーザーコントロールを作る

チープな画面では、場と手札は ListBox を使っているので、これに対応するようにリストを扱えるようなユーザーコントロールを作ります。

場には最大12枚、手札は最大8枚なので、PictureBox をペタペタと張り付けてリスト化しておきます。可搬性はないのですが、このぐらいのユーザーコントロールならばペタペタ作ったほうがよいかと。
データバインドするために、DataSource プロパティを作っておくのがミソです。

public partial class BaControl : UserControl
{
	public BaControl()
	{
		InitializeComponent();

		_pics = new List<PictureBox>();
		_pics.Add(this.pictureBox1);
		_pics.Add(this.pictureBox2);
		_pics.Add(this.pictureBox3);
		_pics.Add(this.pictureBox4);
		_pics.Add(this.pictureBox5);
		_pics.Add(this.pictureBox6);
		_pics.Add(this.pictureBox7);
		_pics.Add(this.pictureBox8);
		_pics.Add(this.pictureBox9);
		_pics.Add(this.pictureBox10);
		_pics.Add(this.pictureBox11);
		_pics.Add(this.pictureBox12);
	}
	protected List<PictureBox> _pics;

	protected List<Card> _data;
	public List<Card> DataSource
	{
		get { return _data; }
		set
		{
			_data = value;
			UIUpdate();
		}
	}

	protected void UIUpdate()
	{
		int i = 0;
		if (this.DataSource != null)
		{
			foreach (var c in this.DataSource)
			{
				_pics[i++].Image = CardUI.GetResName(c.ID);
			}
		}
		for (; i < _pics.Count; ++i)
		{
			_pics[i].Image = CardUI.GetUra();
		}
	}
}

手札のほうも似た感じなのですが、PictureBox をクリックしたときのイベントを追加しておきます。

public partial class TefudaControl : UserControl
{
	public TefudaControl()
	{
		InitializeComponent();

		_pics = new List<PictureBox>();
		_pics.Add(this.pictureBox1);
		_pics.Add(this.pictureBox2);
		_pics.Add(this.pictureBox3);
		_pics.Add(this.pictureBox4);
		_pics.Add(this.pictureBox5);
		_pics.Add(this.pictureBox6);
		_pics.Add(this.pictureBox7);
		_pics.Add(this.pictureBox8);
	}
	protected List<PictureBox> _pics;

	protected List<Card> _data;
	public List<Card> DataSource
	{
		get { return _data; }
		set
		{
			_data = value;
			UIUpdate();
		}
	}

	protected void UIUpdate()
	{
		int i = 0;
		if (this.DataSource != null)
		{
			foreach (var c in this.DataSource)
			{
				_pics[i++].Image = CardUI.GetResName(c.ID);
			}
		}
		for (; i < _pics.Count; ++i)
		{
			_pics[i].Image = CardUI.GetUra();
		}
	}

	private void pict_Click(object sender, EventArgs e)
	{
		int i=0;
		foreach (var p in _pics)
		{
			if (p == sender)
			{
				if (p.Image != CardUI.GetUra())
				{
					if (ClickCard != null)
					{
						ClickCard(_data[i]);
						break;
					}
				}
			}
			i++;
		}
	}
	public event Action<Card> ClickCard;
}

■リッチな画面のコードを追加する

手札でカードをクリックしたときのイベントと、

/// <summary>
/// player1のカードを場に出す
/// </summary>
/// <param name="card"></param>
void tefudaControl1_ClickCard(Card c)
{
	_board.Player1.IntoBa(c);
	_board.Ba.PutCard(c);
	_curPlayer = _board.Player1;
	// 画面の更新
	ScrUpdate();
}

画面を更新するところのコードを少しだけ修正します。

/// <summary>
/// 画面の更新
/// </summary>
void ScrUpdate()
{
	listBox1.Items.Clear();
	listBox2.Items.Clear();
	listBox3.Items.Clear();
	listBox4.Items.Clear();
	listBox5.Items.Clear();
	listBox1.Items.AddRange(_board.Ba.Cards.ToArray());
	listBox2.Items.AddRange(_board.Player1.MyCards.ToArray());
	listBox3.Items.AddRange(_board.Player1.TakenCards.ToArray());
	listBox4.Items.AddRange(_board.Player2.MyCards.ToArray());
	listBox5.Items.AddRange(_board.Player2.TakenCards.ToArray());

	label7.Text = _board.Ba.PlayerCard.ToString();

	// 点を表示
	Yaku yaku1 = _board.Game.CalcYaku(_board.Player1.TakenCards);
	Yaku yaku2 = _board.Game.CalcYaku(_board.Player2.TakenCards);
	int ten1 = _board.Game.CalcTen(yaku1);
	int ten2 = _board.Game.CalcTen(yaku2);
	label10.Text = ten1.ToString();
	label11.Text = ten2.ToString();
	// ここだけ追加
	baControl1.DataSource = _board.Ba.Cards; 
	tefudaControl1.DataSource = _board.Player1.MyCards;
	tefudaControl2.DataSource = _board.Player2.MyCards;
}

こうするだけで、いや「だけ」ってほどではないですが、あまりコードを修正をせずに「ゲームらしい画面」ができがあるということで。

あと、追加するのは、

  • 手札や山から場に1枚だしたときのコントロール(忘れてた)
  • 持ち札のコントロール
  • 役ができたときのコントロール(必要?)

なところです。これを追加すると「静的なデータの表現」が終わるわけで、アニメーションとかありませんが、画面キャプチャ的にはそれなりが画面ができます。そのあとに「動的なデータの表現」ってプロセスが待っていて、ゲームらしい「動き」を追加します。

カテゴリー: C# パーマリンク