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





