前回の続きです。
カードを配り終わったら数字がそろっているカードを捨てます。2枚そろっているときは2枚、3枚そろっているときは1枚残して2枚捨てます。4枚ともそろっているときは4枚とも捨てます。
ではこのような処理をするためにはどうすればいいでしょうか?
Contents
同じカードをみつけたら捨てるメソッド
これでどうでしょうか? 1から13まで持っているカードのなかにあるか調べます。2枚見つかったときと4枚見つかったときは捨てるカードのリストに加えます。3枚あるときは先に見つけた2枚を捨てるカードリストに加えます。そして最後にプレイヤーが持っているカードと捨てるカードの差集合をもとめます。
そしてこれはPlayerクラスのメソッドとします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Player { public void TakeoutCardsInit() { List<Card> takeoutCards = new List<Card>(); for(int i = 1; i <= 13; i++) { var card = Cards.Where(x => x.Number == i).ToList(); if(card.Count == 2 || card.Count == 4) takeoutCards.AddRange(card); else if(card.Count == 3) { card = card.Take(2).ToList(); takeoutCards.AddRange(card); } } Cards = Cards.Except(takeoutCards).ToList(); } } |
TakeoutCardsメソッドは最初にカードが配られたときに実行するだけでよさそうです。あとはカードを1枚しか引かないので、引いたカードと同じ数があれば一緒に捨てる、なければ手持ちのカードに加える処理をすればいいからです。
相手のカードを引いてそろったら捨てる
ゲームがはじまったら相手がもっているカードを1枚引いて、数字がそろっていたら捨てます。カードを引くときは「誰から」「どのカードを」を考える必要があります。
ババ抜きのルールでは時計回りに時計回りの順にカードを左となりの人にひいてもらって、同じカードがあれば中央の場にすてていくことになっています。ということは順番だけでなくカードも時計回りに移動することになります。
プログラム的には
1が0のカードを引く
2が1のカードを引く
3が2のカードを引く
4が3のカードを引く
5が4のカードを引く
0が5のカードを引く
以上の繰り返しになります。
PullCardというメソッドを作りました。これは相手とカード(リストの何番目にあるか)を指定してカードを引いた処理をおこなうものです。引いたカードの数字を調べて自分も持っているのであればカードを捨てる、ないなら引いたカードを自分のカードに加えます。
当然のことながら引いたカードは相手の手元からはなくなります。そのための処理もいっしょにおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Player { public void PullCard(Player player, int i) { // 存在しないカードは引けない if(player.Cards.Count <= i) return; // 引いたカードは相手の手元からなくなる Card card = player.Cards[i]; player.Cards.Remove(card); // 数字があっているならカードを捨てる、ないなら引いたカードを自分のカードに加える Card card1 = Cards.FirstOrDefault(x => x.Number == card.Number); if(card1 == null) Cards.Add(card); else Cards.Remove(card1); } } |
カードを表示させる
これまではデータ的な処理しかしませんでした。今回は自分のカードを表示させることにします。
ババ抜きは相手のカードを引くので自分のカードだけでなく相手のカードも表示させる必要があります。そこで手前に自分のカード、向こう側に相手のカードを裏向きに表示します。
カードのイメージを作成する
まずはカードを表示する位置を決めなければなりません。それからカードのイメージを取得する必要があります。
まずカードのイメージはこれまでどおりの方法でよいと思います。
トランプ カードイラスト No: 934665/無料イラストなら「イラストAC」
で入手した画像からカードの絵柄を取得します。
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 |
public class Card { 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 Bitmap { get { if(_bitmap == null) _bitmap = GetBitmap(); return _bitmap; } } Bitmap _bitmap = null; } |
カードの裏側のイメージを作成する
これはカードの裏面のイメージを取得するメソッドとプロパティです。
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 |
public class Card { static 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; } static public Bitmap BackBitmap { get { if(_backBitmap == null) _backBitmap = GetBackBitmap(); return _backBitmap; } } static Bitmap _backBitmap = null; } |
それからカードの大きさを取得できるようにしておきましょう。
1 2 3 4 5 6 7 8 |
public class Card { // カードの大きさ(縦横のサイズ) static public Size Size { get { return new Size(50, 75); } } } |
カードを表示させる
次にカードを表示する場所を決めます。デザイナでこんな感じにしてみました。
パネルを2枚貼り付けています。手前(下)が自分のカードで上が相手のカードです。
カードが配り終えたら自分のカードを表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { void GameStart() { foreach(Player player in Players) player.IsFinished = false; HandoutCards(); // 相手のカードを裏向きで表示する panel1.Invalidate(); // 自分のカードを表向きに表示する panel2.Invalidate(); // 現在自分のターンかどうかを示すプロパティ IsMyTurn = true; } } |
Invalidateメソッドを呼ぶことでパネルが更新されます。panel1には相手のカード、panel2には自分のカードが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { private void panel1_Paint(object sender, PaintEventArgs e) { ShowRivalCards(e.Graphics); } private void panel2_Paint(object sender, PaintEventArgs e) { ShowMyCards(e.Graphics); } } |
ShowMyCardsメソッドとShowRivalCardsメソッドは以下のようになっています。
カードをパネルの真ん中あたりに表示させたいので1枚目のカードの左上のX座標を計算しています。
カードが表示される部分の幅は
カードの横幅
カードの間隔 × (カードの枚数-1)
この両者をあわせたです。
求めるX座標はパネルの幅とこの長さを差を半分にしたものです。そのためこのような処理になっています。
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 { int MARGIN = 10; void ShowMyCards(Graphics g) { Size cardSize = Card.Size; // カードのサイズは 50 × 75 int cardWidth = cardSize.Width; int myCardCount = Players[0].Cards.Count; int cardsWidth = cardWidth * myCardCount + MARGIN * (myCardCount - 1); int startX = (panel2.Width - cardsWidth) / 2; int count = 0; foreach(Card card in Players[0].Cards) { int x = startX + count * (cardWidth + MARGIN); Rectangle rect = new Rectangle(new Point(x, 0), cardSize); g.DrawImage(card.Bitmap, rect); count++; } } } |
相手のカードを表示させるために作成したのがShowRivalCardsメソッドです。カードを引く相手はあがったプレイヤーによって前回と変わってしまうことがあります。全部で5人なのでPlayers[4]のカードを表示すればよいというわけではありません。
表示するのはあがっていないプレイヤーでインデックスが最後のものです。そこで
1 |
Player last = Players.Last(x => !x.IsFinished); |
のようにして取得しています。
また自分があがってしまったあとは相手のカードは表示する必要はありません。
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 |
public partial class Form1 : Form { void ShowRivalCards(Graphics g) { // 自分があがってしまったあとはなにも表示させない if(Players[0].IsFinished) return; Size cardSize = Card.Size; int cardWidth = cardSize.Width; // リストの最後でまだあがっていないプレイヤーは? Player last = Players.Last(x => !x.IsFinished); int rivalCardCount = last.Cards.Count; int cardsWidth = cardWidth * rivalCardCount + MARGIN * (rivalCardCount - 1); int startX = (panel2.Width - cardsWidth) / 2; int count = 0; foreach(Card card in last.Cards) { int x = startX + count * (cardWidth + MARGIN); Rectangle rect = new Rectangle(new Point(x, 0), cardSize); g.DrawImage(Card.BackBitmap, rect); count++; } } } |
自分のターンになったらカードを出せるようにする
自分の番になったら相手のカードをクリックしてカードを引きます。
クリックのイベントハンドラを示します。
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 Form1 : Form { private void panel1_MouseDown(object sender, MouseEventArgs e) { // 自分のターンでないときはなにもしない if(!IsMyTurn) return; int cardIndex = GetIndexClickedCard(e.X); // 不正な場所がクリックされた if(cardIndex == -1) { MessageBox.Show("カードのある位置をクリックしてください", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } // 取得されたカードを処理して表示されているカードを更新する Players[0].PullCard(Players.Last(x => !x.IsFinished), cardIndex); panel1.Invalidate(); panel2.Invalidate(); if(Players[0].Cards.Count == 0) { Players[0].IsFinished = true; PlayerFinished(Players[0]); } // ライバルのターンへ(後述) RivalsTurn(); } } |
クリックされた場所にある相手のカードをしらべてこれを取得します。あとはPlayerクラスで作成したメソッドが捨てることができるカードがあるかどうか調べて適切な処理をしてくれます。
またIsMyTurnプロパティを作成することで、自分のターンでないときにクリックしてもなにもおきないようにしています。
まずクリックされた場所にあるカードを調べるためのGetIndexClickedCardメソッドを示します。
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 |
public partial class Form1 : Form { int GetIndexClickedCard(int posX) { Size cardSize = Card.Size; // 50, 75 int cardWidth = cardSize.Width; int rivalCardCount = Players.Last(x => !x.IsFinished).Cards.Count; int cardsWidth = cardWidth * rivalCardCount + MARGIN * (rivalCardCount - 1); int startX = (panel2.Width - cardsWidth) / 2; int x1 = startX; int x2 = startX + cardWidth; int index; bool isFind = false; for(index = 0; index < rivalCardCount; index++) { if(x1< posX && posX < x2) { isFind = true; break; } x1 += cardWidth + MARGIN; x2 += cardWidth + MARGIN; } if(isFind) return index; else return -1; } } |
IsMyTurnは自分のターンかどうかを調べるとともに、自分の番であることを表示するプロパティです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { bool _isMyTurn = false; bool IsMyTurn { get { return _isMyTurn; } set { _isMyTurn = value; if(_isMyTurn) Text = "あなたの番です"; else Text = "しばらくお待ちください"; } } } |
ライバルのターンは次回に続きます。
失礼いたします。
PlayerFinishedメソッドが存在しない為、エラーが発生いたしますので、
記述の方をよろしくお願いいたします。
PlayerFinishedメソッドはコンピュータにカードを引かせてみる C#でババ抜きをつくるで定義しています。
以下のような内容です。
このメソッドはRivalsTurnメソッドのループ文のなかでも呼ばれるので二重に呼ばれてしまうことがあります。
そこであがったプレイヤーを格納するリストを作成して二重に格納できないようにしています。
失礼いたしました。
PlayerFinishedメソッドは次の記事に記載されていました