想像とか机上でなんとなくできているような感じがして、それで満足したり試行錯誤したりするわけですが、現実に当てはめれてみればなんということはない「物理的な制約」が先にあって、実現不可能な選択肢が削られて話が簡単になること、というのはプログラムの話。いや、煮詰めすぎると無理が来るってことか、あるいは時間的な制約ということか。
チープな画面を
少しリッチな画面に
変えていきます。
花札の画像は、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 とか、ちまちまと定義することになるのですが…まあ、無駄なプロジェクトを増やすよりは、ダブって書くということで、今回はあきらめ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 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 プロパティを作っておくのがミソです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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 をクリックしたときのイベントを追加しておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | 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; } |
■リッチな画面のコードを追加する
手札でカードをクリックしたときのイベントと、
1 2 3 4 5 6 7 8 9 10 11 12 | /// <summary> /// player1のカードを場に出す /// </summary> /// <param name="card"></param> void tefudaControl1_ClickCard(Card c) { _board.Player1.IntoBa(c); _board.Ba.PutCard(c); _curPlayer = _board.Player1; // 画面の更新 ScrUpdate(); } |
画面を更新するところのコードを少しだけ修正します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /// <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枚だしたときのコントロール(忘れてた)
- 持ち札のコントロール
- 役ができたときのコントロール(必要?)
なところです。これを追加すると「静的なデータの表現」が終わるわけで、アニメーションとかありませんが、画面キャプチャ的にはそれなりが画面ができます。そのあとに「動的なデータの表現」ってプロセスが待っていて、ゲームらしい「動き」を追加します。