想像とか机上でなんとなくできているような感じがして、それで満足したり試行錯誤したりするわけですが、現実に当てはめれてみればなんということはない「物理的な制約」が先にあって、実現不可能な選択肢が削られて話が簡単になること、というのはプログラムの話。いや、煮詰めすぎると無理が来るってことか、あるいは時間的な制約ということか。
チープな画面を
少しリッチな画面に
変えていきます。
花札の画像は、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枚だしたときのコントロール(忘れてた)
- 持ち札のコントロール
- 役ができたときのコントロール(必要?)
なところです。これを追加すると「静的なデータの表現」が終わるわけで、アニメーションとかありませんが、画面キャプチャ的にはそれなりが画面ができます。そのあとに「動的なデータの表現」ってプロセスが待っていて、ゲームらしい「動き」を追加します。