前回のコンピュータと対戦できるようにする C#で素数大富豪をつくる(5)で一応ゲームとしての体裁は整いましたが、やっぱりカードゲームなのでカードを表示させたいです。今回はカードを表示させます。
ちょっと長くなってしまったので「カードを表示させてゲームらしくする」は2回にわけます。今回はカードを生成、シャッフル、配る処理と、クリックしてカードを選択するところまでやります。
Contents
Cardクラスをつくる
カードをつくるための画像はここからダウンロードします。
トランプ カードイラスト – No: 934665/無料イラストなら「イラストAC」
画像を入手したら、これをプロジェクト ⇒ プロパティ ⇒ リソースに追加します。
ではカードを描画するためのクラスをつくりましょう。
1 2 3 4 5 6 7 |
public enum Suit { Spade, Club, Diamond, Heart, } |
コンストラクタにはスートと番号、幅、高さを渡します。描画するときはXY座標とサイズを指定してからDrawメソッドを呼びます。CardImageは一回取得したらそれを使い回します。
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 |
public class Card { public Card(Suit suit, int number, int width, int height) { Suit = suit; Number = number; Size = new Size(width, height); } static Bitmap CardImageAll = Properties.Resources.cardimage; public Suit Suit { get; } public int Number { get; } public int X { get; set;} public int Y { get; set; } public Size Size { get; set; } public void Draw(Graphics graphics) { graphics.DrawImage(CardImage, new Rectangle(X, Y, Size.Width, Size.Height)); } Bitmap _cardImage = null; public Bitmap CardImage { get { if (_cardImage == null) _cardImage = GetImage(Suit, Number); return _cardImage; } } Bitmap GetImage(Suit suit, int number) { int y = 0; if (suit == Suit.Club) y = 0; if (suit == Suit.Diamond) y = 600; if (suit == Suit.Heart) y = 1200; if (suit == Suit.Spade) y = 1800; Bitmap bitmap = new Bitmap(360, 500); Graphics graphics = Graphics.FromImage(bitmap); graphics.DrawImage( CardImageAll, new Rectangle(0, 0, 360, 500), new Rectangle(360 * (number - 1) + 20 * (number - 1), y, 360, 500), GraphicsUnit.Pixel); graphics.Dispose(); return bitmap; } } |
裏面を表示させたくなることも考えられるので裏面を描画するメソッドも作りました。裏面は全部同じなので最初に1回だけ取得してstatic変数にいれてしまいます。
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 |
public class Card { public void DrawBack(Graphics graphics) { graphics.DrawImage(CardBackImage, new Rectangle(X, Y, Size.Width, Size.Height)); } static Bitmap _cardBackImage = null; static public Bitmap CardBackImage { get { if (_cardBackImage == null) { Bitmap bitmap = new Bitmap(360, 500); Graphics graphics = Graphics.FromImage(bitmap); graphics.DrawImage( CardImageAll, new Rectangle(0, 0, 360, 500), new Rectangle(760, 2400, 360, 500), GraphicsUnit.Pixel); graphics.Dispose(); _cardBackImage = bitmap; } return _cardBackImage; } } } |
フォームにカードを表示させる
次にデザイナで以下のようなフォームを作成します。
最初にコンストラクタとフィールド変数を示します。
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 Form1 : Form { public Form1() { InitializeComponent(); // Labelの文字を空文字列にする LabelMessage.Text = ""; LabelPlayer.Text = ""; LabelComp.Text = ""; LabelPlayerErrer.Text = ""; // フォームの大きさはこれくらいがよさそう Size = new Size(700, 650); } // 山札に積まれているカード List<Card> Cards = new List<Card>(); // プレイヤーとコンピュータが持っているカード List<Card> CompCards = new List<Card>(); List<Card> PlayerCards = new List<Card>(); // プレイヤーとコンピュータが場に出したカード // ~2は素因数場に出されたカード List<Card> CardsPutByPlayer1 = new List<Card>(); List<Card> CardsPutByPlayer2 = new List<Card>(); List<Card> CardsPutByComp1 = new List<Card>(); List<Card> CardsPutByComp2 = new List<Card>(); // プレイヤーとコンピュータが場に出したカードで表示はされていないが流されていないもの List<Card> CardsPutByAll = new List<Card>(); // 合成数出しのときに素因数をいれておくリスト List<int> Primefactors = new List<int>(); List<int> TempPrimefactors = new List<int>(); // Labelに表示させる文字列を一時的に格納しておく変数 string PlayerText1 = ""; string PlayerText2 = ""; string CompText1 = ""; string CompText2 = ""; // 次のプレイヤーが出さなければならないカードの枚数と最小の数 int CountCardsPutMust = -1; long MinValueCards = -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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public partial class Form1 : Form { private void ButtonGameStart_Click(object sender, EventArgs e) { // ゲームの途中でボタンが押されるかもしれないので // いったん両者が持っているカードをクリアする CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); CardsPutByComp1.Clear(); CardsPutByComp2.Clear(); // 素因数場に出されているカードの情報をクリアする Primefactors.Clear(); TempPrimefactors.Clear(); CheckBoxPrimeFactor.Checked = false; // ゲーム開始直後であれば好きな枚数で好きな数を出せるので-1を設定する CountCardsPutMust = -1; MinValueCards = -1; // カードを生成してシャッフルする CreateCards(); ShuffleCards(); // カードを配り、見やすくするために番号が小さい順に並べる CompCards = Cards.Take(13).ToList(); CompCards = CompCards.OrderBy(card => card.Number).ToList(); Cards = Cards.Skip(13).ToList(); PlayerCards = Cards.Take(13).ToList(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); Cards = Cards.Skip(13).ToList(); // Labelのテキストをリセットする CompText1 = ""; CompText2 = ""; LabelComp.Text = "コンピュータ:"; PlayerText1 = ""; PlayerText2 = ""; LabelPlayer.Text = "あなた:"; // プレイヤーのミスを表示する文字列をリセットする LabelPlayerErrer.Text = ""; // 配られたカードを描画してプレイヤーが先攻である旨を表示する ShowCards(); LabelMessage.Text = "あなたが親です。"; } } |
上記のとおり、ゲーム開始にともなってカードの生成とシャッフルがおこなわれるのですが、Cardオブジェクトを作ったのでカードの生成とシャッフルの処理をするメソッドを変更しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public partial class Form1 : Form { void CreateCards() { // Cardsはフィールド変数 Cards.Clear(); for (int i=1; i<=13; i++) { Cards.Add(new Card(Suit.Spade, i, 70, 95)); Cards.Add(new Card(Suit.Heart, i, 70, 95)); Cards.Add(new Card(Suit.Diamond, i, 70, 95)); Cards.Add(new Card(Suit.Club, i, 70, 95)); } } } |
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 { void ShuffleCards() { List<Card> ret = new List<Card>(); // 乱数を生成してCardsからカードを抜き取りretへ追加する // 最後にretをCardsに代入する Random random = new Random(); while (true) { int count = Cards.Count(); if (count == 0) break; int r = random.Next(count); ret.Add(Cards[r]); Cards.RemoveAt(r); } Cards = ret; } } |
カードの位置を指定して描画する
次に描画の処理ですが、一番上の列にコンピュータが持っているカード、二番目の列にコンピュータが場に出したカード、三番目にプレイヤーが場に出したカード、一番下にプレイヤーが持っているカードを配置し、これを描画します。
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 |
public partial class Form1 : Form { void ShowCards() { int marginTop = 20; int marginLeft = 20; // コンピュータが持っているカードの位置を指定する int i = 0; foreach (Card card in CompCards) { card.X = i * 35 + marginLeft; card.Y = 0 + marginTop; i++; } // プレイヤーが持っているカードの位置を指定する i = 0; foreach (Card card in PlayerCards) { card.X = i * 35 + marginLeft; card.Y = 300 + marginTop; i++; } // コンピュータが場に出しているカードの位置を指定する i = 2; foreach (Card card in CardsPutByComp1) { card.X = i * 20 + marginLeft; card.Y = 100 + marginTop; i++; } // コンピュータが素因数場に出しているカードの位置を指定する i = 10; foreach (Card card in CardsPutByComp2) { card.X = i * 20 + marginLeft; card.Y = 100 + marginTop; i++; } // プレイヤーが場に出しているカードの位置を指定する i = 2; foreach (Card card in CardsPutByPlayer1) { card.X = i * 20 + marginLeft; card.Y = 200 + marginTop; i++; } // プレイヤーが素因数場に出しているカードの位置を指定する i = 10; foreach (Card card in CardsPutByPlayer2) { card.X = i * 20 + marginLeft; card.Y = 200 + marginTop; i++; } Invalidate(); } } |
実際の描画処理を示します。描画したい位置が確定したらCard.Drawメソッドを呼ぶだけです。
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 { protected override void OnPaint(PaintEventArgs e) { foreach (Card card in CompCards) card.Draw(e.Graphics); foreach (Card card in PlayerCards) card.Draw(e.Graphics); foreach (Card card in CardsPutByPlayer1) card.Draw(e.Graphics); foreach (Card card in CardsPutByPlayer2) card.Draw(e.Graphics); foreach (Card card in CardsPutByComp1) card.Draw(e.Graphics); foreach (Card card in CardsPutByComp2) card.Draw(e.Graphics); base.OnPaint(e); } } |
プレイヤーがカードを出せるようにする
プレイヤーがカードを出す処理を示します。
クリックでカードを選択
出したいカードをクリックすればよいのですが、スペースの関係でカードを重ねて描画しています。そのためカードの見える部分がクリックされたカードを選んだように処理をしなければなりません。
そこでカードのX座標を大きい順にソートします。これならクリックされた座標を含むカードを探して最初に見つかったものが選ばれたカードということになります。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { protected override void OnMouseClick(MouseEventArgs e) { List<Card> cards = PlayerCards.OrderByDescending(card => card.X).ToList(); Card firstCard = cards.FirstOrDefault(card => card.X < e.X && e.X < card.X + card.Size.Width && card.Y < e.Y && e.Y < card.Y + card.Size.Height); if (firstCard != null) PutCard(firstCard); base.OnMouseClick(e); } } |
カードを出す処理はPutCardメソッドで行なわれます。これも前回のものとは引数が異なっています。CheckBoxをみて素数としてカードを出すのか合成数出しをするのかを切り分けています。
カードをCardsPutByPlayer1またはCardsPutByPlayer2リストに移動させるとともに出されたカードをLabelに表示させています。この操作でカードを描画すべき位置がかわるので再描画の処理をしています。
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 { void PutCard(Card card) { int number = card.Number; if (!CheckBoxPrimeFactor.Checked) { PlayerText1 += number.ToString(); CardsPutByPlayer1.Add(card); PlayerCards.Remove(card); } else { PlayerText2 += number.ToString(); // 合成数出しの場合はかけ算のボタンが押されるまで // カードの番号をTempPrimefactorsに格納していく TempPrimefactors.Add(number); CardsPutByPlayer2.Add(card); PlayerCards.Remove(card); } string str; if (PlayerText2 == "") str = PlayerText1; else str = $"{PlayerText1} = {PlayerText2}"; LabelPlayer.Text = "あなた: " + str; ShowCards(); } } |
合成数出しのための処理
かけ算のボタンが押されたときの処理は前回とほぼ同じです。Labelに素数または合成数と素因数のかけ算の式を表示させる部分が追加されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { private void ButtonX_Click(object sender, EventArgs e) { if (!CheckBoxPrimeFactor.Checked) return; Primefactors.Add(GetNumber(TempPrimefactors)); TempPrimefactors.Clear(); PlayerText2 += " × "; LabelPlayer.Text = $"あなた: {PlayerText1} = {PlayerText2}"; } } |
キャンセルの処理
クリアのボタンが押されたときの処理を示します。Labelに表示されるテキストをクリアするのとCardsPutByPlayer1、CardsPutByPlayer2に移動していたCardオブジェクトをPlayerCardsに戻しています。またカードの描画位置が変わるので再描画の処理をしています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { private void ButtonClear_Click(object sender, EventArgs e) { LabelPlayerErrer.Text = ""; Primefactors.Clear(); TempPrimefactors.Clear(); PlayerText1 = ""; PlayerText2 = ""; LabelPlayer.Text = "あなた: "; PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); CheckBoxPrimeFactor.Checked = false; ShowCards(); } } |
山札からカードを取る
出せるカードがない場合、山札から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 |
public partial class Form1 : Form { private void ButtonDraw_Click(object sender, EventArgs e) { ButtonDraw.Enabled = false; PlayerCards.Add(this.Cards[0]); Cards = Cards.Skip(1).ToList(); Primefactors.Clear(); TempPrimefactors.Clear(); // 出したカードはいったん返却する PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); // Labelのテキストをクリア PlayerText1 = ""; PlayerText2 = ""; LabelPlayer.Text = "あなた:"; LabelPlayerErrer.Text = ""; CheckBoxPrimeFactor.Checked = false; ShowCards(); } } |
パスの処理
プレイヤーがパスをするときの処理を示します。パスをすると手番がコンピュータへ移ります。
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 |
public partial class Form1 : Form { async private void ButtonPass_Click(object sender, EventArgs e) { CheckBoxPrimeFactor.Checked = false; LabelPlayerErrer.Text = ""; PlayerText1 = ""; PlayerText2 = ""; LabelPlayer.Text = "あなた:"; Primefactors.Clear(); TempPrimefactors.Clear(); PlayerCards.AddRange(CardsPutByPlayer1); PlayerCards.AddRange(CardsPutByPlayer2); Pass(); PlayerCards = PlayerCards.OrderBy(card => card.Number).ToList(); ShowCards(); await Task.Delay(500); CompPutCards(-1, -1, ref CountCardsPutMust, ref MinValueCards); if (CompCards.Count == 0) { LabelMessage.Text = "コンピュータの勝ちです。"; ButtonDecision.Enabled = false; return; } if (CountCardsPutMust != -1) LabelMessage.Text = $"{CountCardsPutMust}枚のカードで{MinValueCards}以上の数を出してください"; else LabelMessage.Text = $"コンピュータもパスをしました。あなたが親です。"; } } |
パスをすると場に出されているカードがすべて流れます。流されたカードは山札の一番下に置かれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { void Pass() { Cards.AddRange(CardsPutByComp1); Cards.AddRange(CardsPutByComp2); Cards.AddRange(CardsPutByPlayer1); Cards.AddRange(CardsPutByPlayer2); Cards.AddRange(CardsPutByAll); CardsPutByComp1.Clear(); CardsPutByComp2.Clear(); CardsPutByPlayer1.Clear(); CardsPutByPlayer2.Clear(); CardsPutByAll.Clear(); } } |