あの日はこう考えていたけど、今はこう考えていて、それを実装しようとするときに、以前の他人の自分を「他人」として捉えるのも良し。いやいや、同一人物なんだから同じ考えでオーケーでは?と再び考えてみて、それなりにリスペクトしたコードを書いてみれば、なるほど、そう考えていたのか、と自分に納得する、という現象なのか。
■2枚を選択させるUIを場コントロールに追加する
2つの選択肢をしめすために、RectangleShape を使って枠線を描画する、ってのはいいとして、もともとのメイン画面からの呼び出しが、こんな風にコールバックになっているわけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /// <summary> /// 2枚から選択する /// </summary> /// <param name="c1"></param> /// <param name="c2"></param> /// <returns></returns> Card Player1_EventSelCard(Card c1, Card c2) { string msg = string .Format( "{0} と {1} があります。{2} を選択しますか?" , c1.ID, c2.ID, c1.ID); var btn = MessageBox.Show(msg, "" , MessageBoxButtons.YesNo); if (btn == System.Windows.Forms.DialogResult.Yes) { return c1; } else { return c2; } } |
これを場コントロールで選択できる、という風に仮定すると、こんな感じなるのが理想的…つーか、最初はコールバック関数を作るとか、モロモロ変な感じなコードになっていたのですが、Player1_EventSelCard メソッド自体が、コールバック関数なので、この中で処理をしないといけないんですよね。非同期の async/await が使えればそれはそれでいいのだけど、今回の場合は Windows フォームなので、それを使うのも変な話だし。
1 2 3 4 5 6 7 8 9 10 | /// <summary> /// 2枚から選択する /// </summary> /// <param name="c1"></param> /// <param name="c2"></param> /// <returns></returns> Card Player1_EventSelCard(Card c1, Card c2) { return baControl1.SelCard(c1, c2); } |
という訳で、場コントロールには、次のロジックを追加。
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 | PictureBox _selPic1; PictureBox _selPic2; Card _selCard1; Card _selCard2; Card _selCard; /// <summary> /// 2枚から選択する /// </summary> /// <param name="c1"></param> /// <param name="c2"></param> /// <returns></returns> public Card SelCard(Card c1, Card c2) { // 指定の2枚に枠を付ける var pic1 = _pics.Find( p => p.Image == CardUI.GetResName(c1.ID)); var pic2 = _pics.Find( p => p.Image == CardUI.GetResName(c2.ID)); this .rectangleShape1.Location = new Point(pic1.Location.X - 3, pic1.Location.Y - 3); this .rectangleShape2.Location = new Point(pic2.Location.X - 3, pic2.Location.Y - 3); this .rectangleShape1.Visible = true ; this .rectangleShape2.Visible = true ; pic1.Click += selPic_Click; pic2.Click += selPic_Click; _selPic1 = pic1; _selPic2 = pic2; _selCard1 = c1; _selCard2 = c2; // 選択待ち _selCard = null ; while (_selCard == null ) { Application.DoEvents(); } return _selCard; } private void selPic_Click( object sender, EventArgs e) { _selPic1.Click -= selPic_Click; _selPic2.Click -= selPic_Click; this .rectangleShape1.Visible = false ; this .rectangleShape2.Visible = false ; PictureBox pic = (PictureBox)sender; if (pic == _selPic1) { _selCard = _selCard1; } else { _selCard = _selCard2; } } |
メソッド内でユーザーの選択待ちの動作が入ってしまうので、Application.DoEvents() でクリック待ちをするという荒業です。これを使わない場合には、メイン画面のほうにコールバック関数を作って、という具合になるわけですが、そうなるとロジックと UI の部分の分離がうまくできない。ここは鬼っ子なのか?というのは後で検証するとして、まあ、これで2枚の花札を選択するという動作ができます。
■お次は「動的なデータ表現」
大体、静的なデータ表現ができたので、今度は動的なデータ表現を作ります。「動的な」ってのは、アニメーションとか、一連の動作のところですね。手札がから1枚場に捨てる場合、一連の動作としては、
- 手札から場に1枚出す。
- 場にマッチさせる。
- 山から1枚引いて、場に出す。
- 場にマッチさせる。
- マッチした札を取り札にする。
というところまで1連の動作なわけで、これを「静的なデータ」だけで表現すると、1の手順を行ったときに、一瞬で5になってしまって「何が起こったのかさっぱりわからない」状況になります。なので、このあたりを「時間」を差し込んで、アニメーションで表す、ってところですね。この時間を差し込むところは、業務アプリではほとんど使わないかと。強いて言えば、ドラッグ&ドロップのところぐらいでしょうか。リストから選んだり、テキストを入力してボタンを押した直後に、何か結果が出てくるのが業務アプリの特徴。ゲームの場合は、この間にアクションを差し込んで「わかりやすく」≒「エンターテインメント性を持たせる」ってことになります。
Windows メトロアプリの場合、XAML を使って storyboard を使うと、このゲーム性の部分が比較的簡単にできるのでは?と思っています。このあたりの実験も兼ねて。そう、まずは、Windows フォーム版を、XAML 版に移植するところから始めます。WPF を作ってから Windows メトロのほうがいいのかな?ちょっと思案中。
XAML 版に移しみると、DoEvent がないので、構造的な欠陥が~、ってな具合なオチがあります。
となると、チープ版、ちょっとリッチ版、XAML 版(Windows ストア アプリ版)と同じ画面ロジック(内部ロジックも)にするためには、この DoEvent の部分を改造せねばならず。
ひとまず、そのあたりを修正したので後で書くということで。