花札ゲームの対象は、最終的に Windows 8 のメトロアプリ(XAMLアプリ)と iPad のアプリな訳で、データ構造やゲームの内部構造を共通化すると同時に、表面的には、XAML の部分と、xcode の storyboard のところ(たぶん、ユーザーコントロールを自作)を作ることになるハズ。その前提として、データ指向的なデータバインドの方法と、少し古めの MVVM の方法(observerパターンを使うという点で)を組み入れる予定です。なので、View からデータへの連結はできるだけ簡単な機構を使いたい、ってのがある。
ゲームロジックと画面への描画をべたべたに書くほうが楽だとは思うのだけど、腐心しているのは「後から修正可能にする」ということと、同時に「後から修正を短時間で行えること」を目標とするわけで。このとき「後から」ってのは、今から未来を見通すことになるわけで、その昔…というかオブジェクト指向的にいうと拡張可能にしたり、あらかじえインターフェースを用意したりしようとする方法に陥るのだが、いやいや、そのような「未来予想のコスト」は、変更時のコストがかなり安くなてしまったので(というか、作り直すというコストがかなり安くなってきている)、枠というか「考え方」だけ残してざっくりと作り直したほうが、バグ修正も含めて早かったりする。
となると、今度は「作り直すコスト」を下げるために、今のコードを作る、という不思議なコーディングスタイルがあっても良いかなと思ったり。
■山のコントロールを作る
山のコントロールは作る必要もないのだけど、山から花札を取るという動作をチープな画面では「get」ボタンに割り当てていたので、同じように山のイメージだけ作っておきます。
他のコントロールと同じように、DataSource プロパティがあるわけですが、中身が空っぽです。花札の画像をクリックしたときに、クリックイベントを画面のほうに投げるだけです。正確に作るならば、DataSource に山用の List<Card> を渡してやって、クリック時に1枚メイン画面に渡す、ってコードになるわけですが、1枚山のリストから引っ張ってくるというコードはメイン画面に書いてあるし、ってことでそのまま。
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 | public partial class YamaControl : UserControl { public YamaControl() { InitializeComponent(); } protected List<Card> _data; public List<Card> DataSource { get { return _data; } set { _data = value; UIUpdate(); } } protected void UIUpdate() { } public event Action<Card> ClickCard; /// <summary> /// 山をクリック /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void pict_Click( object sender, EventArgs e) { if (ClickCard != null ) { ClickCard( null ); } } } |
さらに理屈を加えると、大抵の花札のゲームでは山から1枚取るのは自動なんですよね。ゲームの動きとしては、
- 手札から1枚場に出す。
- 場とマッチングさせる。
- 山から1枚引く。
- 場とマッチングさせる。
- 役があれば紹介アニメを表示する。
- マッチした札を取り札に移動する。
まで一連の流れになるわけで(実際には、2枚マッチがあった場合にはユーザーに選択させるわけですが)、ここで使っているテスト用の「チープな画面」では、このあたりの操作はボタンを押して遷移させています。
まあ、そうしないと一瞬で画面から札を取ってしまって途中経過がわからないと、この「途中経過」の部分をどうやって MVVM なのか、データバインドなのか、データ指向なのか、に仕立てるということで、外してあります….というのは半分嘘で、アニメーションのほうを先に考えたら失敗したので、「静的データの表現」から「動的データの表現」へと開発の向きを変えてみたわけです。
■場に札を出したときの処理を加える
場に一枚、札を出したときのコントロールを別に作ろうかと思ってもみたのですが、「場に札を出す」という動作から考えて、場コントロールを拡張させることにします。
ここでちょっと悩んだのが、データバインドの DataSource プロパティをどう拡張しようか?ってことなのですが、いやいや、そんなデータ構造を複雑にせずに、バインドするプロパティを DataSource と PutCard という2つにすれば良いのだ、ということに気づきました。そうそう、データバインドって、なんとなくひとつだけというイメージがあったのですが、別に複数あっても構いません。ただし、あまり多くなってしまうと、どれをどのようにバインドしたらよいのかと、コントロールを使う側が迷ってしまうので、少な目にしておきます。ってのがポイントですね。
このあたり、16駒パズルを作ったときに失敗したのですが、枠線やら画像やら位置やらのひとつひとつにバインドをさせるのは、面倒…と言いますか、バインドの指向的に間違っています。「設定する値」というデータの意味的な部分と、「視覚的に表現する形」という UI 的な部分とは、別々にするとコードが簡単になるし、簡単になりますね。そのあたりの反省もあって、今回は「ユーザーコントロール」を多用しています。下手に、標準的なコントロール(この場合は 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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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 Card _putCard; public Card PutCard { get { return _putCard; } set { _putCard = 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(); } if (_putCard != null ) { this .pictureBoxPut.Image = CardUI.GetResName(_putCard.ID); } else { this .pictureBoxPut.Image = CardUI.GetUra(); } } public event Action<Card> ClickPutCard; /// <summary> /// 場をクリック /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void pict_Click( object sender, EventArgs e) { if (ClickPutCard != null ) { ClickPutCard( null ); } } } |
■表示位置を調節してゲームらしく
フォームの位置を変えて、チープ画面用の ListBox などを隠すと、ほら花札ゲームのようになります。
リッチ UI 絡みのほうはだいたいできたのですが、もうひとつ問題があって、場に2枚マッチしたときに、どちらかを選ばさせないといけません。いままのままだと無骨なダイアログがでるので、これを場コントロールを使ってなんとか表現させたいのです。
方法はいくつかって、他の花札ゲームのように場の札を枠線であらわして選択させるとか、札を点滅させるとか、別の画像つきのダイアログを出して選択するとか、まあ、いろいろ。ただ、win タブレットとか iphone を考えると、ダイアログが出るよりは、枠を光らせて選択させる、ってのが「自然」ではありますね。
という訳で、お次は、2枚選択の部分を「場コントロール」にフィードバックして表現させる、ってところを実装しています。