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

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

チープな画面を

少しリッチな画面に

変えていきます。

花札の画像は、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枚だしたときのコントロール(忘れてた)
  • 持ち札のコントロール
  • 役ができたときのコントロール(必要?)

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

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