前回まではC#でババ抜きの作成をしてきました。
今回は同じカードゲームでも別のものに挑戦します。
Contents
新しいカードゲームに挑戦
こんなサイトを見つけました。
ポーカーなどの対戦ゲームではコンピュータ側のアルゴリズムを考える必要があるのですが、比較的簡単にできそうなので、ドボンをやってみようと考えています。
ドボンのルールについて
簡単にどのようなゲームなのか説明しておきましょう。
NO(ウノ)のトランプ版といってもよいゲームです。場札と同じ数字かマークのカードを出して自分の手札を先になくしたほうが勝ちです。
それからカードには役札があります。次にカードを出すはずの人にカードを出させない強制パス、順番を逆回りにすることができるカード、NO(ウノ)のワイルドカードのように機能するカード……、このような役札があるのです。
またドボンの特徴として、自分の持っている手札の数字の合計が、台札の数字とおなじときは「ドボン」と宣言して、手札のカードを全部いっきに出してあがるという一発逆転のようなこともできます。
ドボンにもローカルルールが・・・
ドボンにもローカルルールがあります。
よくC#の参考にさせていただいているサイトに
があります。
そのなかにこんなページもあります。
ここでは
上がるための唯一の手段は「ドボン」のみ
誰かがドボンをしてもゲームは終わらない。「ドボン返し」がある。
これはドボンして出した一番上のカードに別の誰かがドボンできる場合、さらにドボンすることができるというものです。
最終的にドボンをしたプレーヤーが勝者、ドボンをされたプレーヤーが敗者。
となっています。
最初に紹介したルールではドボンであがることも可能ですが、このルールではドボンでなければあがることができないということになります。しかもドボン返しのリスクあり。汗
一口にドボンといってもいろいろなルールがあります。
どうでしょうか? 面白そうでしょう?
そこでさっそく作成することにします。
カードは前回作成したものをそのまま使える?
七並べやババ抜きでCardというクラスを作成しました。これはカードゲーム一般でも使い回しができそうです。そこでこれをそのまま使います。一回クラスを作ってしまうと使い回しができるのが便利なところです。いっそのことライブラリ化してしまったほうがいいかもしれませんね。
Cardクラスをライブラリ化してしまおう
ここではCardクラスをライブラリ化することにします。
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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
namespace CardLibrary { public class Card { public Card() { } public Card(Suit suit, int number) { Suit = suit; Number = number; } public Suit Suit { protected set; get; } public int Number { protected set; get; } // カードの大きさ(縦横のサイズ) public Size Size { get; set; } = new Size(50, 75); // カードの絵柄 public Bitmap Bitmap { get { if(_Bitmap == null) _Bitmap = GetBitmap(); return _Bitmap; } } Bitmap _Bitmap = null; Bitmap GetBitmap() { Bitmap bitmap = new Bitmap(Size.Width, Size.Height); var image = Properties.Resources.cardimage; int x, y = 0; x = 382 * (Number - 1); if(Suit == Suit.Spade) y = 1800; if(Suit == Suit.Hart) y = 1200; if(Suit == Suit.Dia) y = 600; if(Suit == Suit.Club) y = 0; // ジョーカー if(Suit == Suit.Joker) { y = 2400; x = 0; } // スペードのエース if(Suit == Suit.Spade && Number == 1) { y = 2400; x = 382 * 1; } Rectangle srcRect = new Rectangle(new Point(x, y), new Size(354, 490)); Graphics graphics = Graphics.FromImage(bitmap); graphics.DrawImage(image, new Rectangle(0, 0, Size.Width, Size.Height), srcRect, GraphicsUnit.Pixel); graphics.Dispose(); return bitmap; } // カードの裏の絵柄 public Bitmap BackBitmap { get { if(_BackBitmap == null) _BackBitmap = GetBackBitmap(); return _BackBitmap; } } Bitmap _BackBitmap = null; Bitmap GetBackBitmap() { Bitmap bitmap = new Bitmap(Size.Width, Size.Height); var image = Properties.Resources.cardimage; int x = 382 * 2; int y = 2400; Rectangle srcRect = new Rectangle(new Point(x, y), new Size(354, 490)); Graphics graphics = Graphics.FromImage(bitmap); graphics.DrawImage(image, new Rectangle(0, 0, Size.Width, Size.Height), srcRect, GraphicsUnit.Pixel); graphics.Dispose(); return bitmap; } // 座標とサイズを指定してカードを表示させる public void ShowCard(Graphics graphics, Point pt, Size size) { graphics.DrawImage(Bitmap, new Rectangle(pt, size)); } // 矩形を指定してカードを表示させる public void ShowCard(Graphics graphics, Rectangle rect) { graphics.DrawImage(Bitmap, rect); } // card を「スペードのA」などの文字列に変換する public string GetCardString() { string suit = ""; if(Suit == Suit.Spade) suit = "スペード"; if(Suit == Suit.Hart) suit = "ハード"; if(Suit == Suit.Dia) suit = "ダイヤ"; if(Suit == Suit.Club) suit = "クラブ"; string number = ""; if(Number == 1) number = "A"; else if(Number == 11) number = "J"; else if(Number == 12) number = "Q"; else if(Number == 13) number = "K"; else number = Number.ToString(); return String.Format("{0}の{1}", suit, number); } } // カードの種類 public enum Suit { None = -1, Joker = 0, Spade = 1, Hart = 2, Dia = 3, Club = 4, } } |
カードの絵柄を取得するのに若干時間がかかってしまう問題
カードの絵柄を取得するのに若干時間がかかります。1枚だけならたいした時間ではありませんが、53枚のカード全部だとけっこうな時間になってしまいます。そのためカードが初めて表示されるときにカードの絵柄を取得して、次回以降は保存したBitmapを使用しています。
コンストラクタのなかで取得するという方法もあるのですが、これでは呼び出し側でこのような使い方をするとフォームが表示されるまでかなりの時間がかかってしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Card { public Card() { } public Card(Suit suit, int number) { Suit = suit; Number = number; _Bitmap = GetBitmap(); _BackBitmap = GetBackBitmap(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); CreateAllCard(); } void CreateAllCard() { for(int i = 1; i <= 13; i++) Cards.Add(new Card(Suit.Spade, i)); for(int i = 1; i <= 13; i++) Cards.Add(new Card(Suit.Hart, i)); for(int i = 1; i <= 13; i++) Cards.Add(new Card(Suit.Dia, i)); for(int i = 1; i <= 13; i++) Cards.Add(new Card(Suit.Club, i)); Cards.Add(new Card(Suit.Joker, 0)); } } |
カードの絵柄の取得を非同期処理でおこなうのはどうか?
非同期処理にすればフォームはすぐに表示されますが、フォームが表示されたときにすぐにカードを表示させる場合、問題がおきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); Task.Run(() => CreateAllCard()); } private void Form1_Paint(object sender, PaintEventArgs e) { // これはダメ。 // まだ生成されていないものを表示させようとしているのだからエラーになる for(int i = 0; i < 13; i++) Cards[i].ShowCard(e.Graphics, new Rectangle(10 + i * 60, 10, 50, 75)); } } |
こんな方法もあるかもしれません。
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 |
public partial class Form1 : Form { bool isNotReady = true; public Form1() { InitializeComponent(); Task.Run(() => { CreateAllCard(); isNotReady = false; Invalidate(); return 0; }); } private void Form1_Paint(object sender, PaintEventArgs e) { // Cardオブジェクトが生成されるまで「準備中」であることを示す表示 if(isNotReady) { e.Graphics.DrawString("準備中", new Font("MS ゴシック", 30), Brushes.Black, 0, 100); return; } else { // 準備が完了したあとにおこないたい処理 for(int i = 0; i < 13; i++) Cards[i].ShowCard(e.Graphics, new Rectangle(10 + i * 60, 10, 50, 75)); } } } |
それでは次回からDobonの作成をおこなうことにします。