ゲーム開始
カードを配り終えたらゲーム開始です。
ドボンのルールと遊びかたによると、
親は積み札の1番上のカードを1枚めくって、積み札の横に表向きにして置いて台札にします。
親の左となりの人は、自分の手札の中から台札と同じ数字か、同じマークのカード1枚を台札の上に表向きにかさねます。
手札の中に出せるカードがない場合は、積み札の上から順番に出せるカードが出るまで手札に加えていきます。
とあります。
ここで必要な処理は
最初のカードを中央に置く
各プレイヤーが順番にカードを出していく
なのですが、ライバルがカードを出すときは
出せるカードはあるのか?
どのカードを出すのか?
出せない場合はカードを取り続ける
という処理をする必要があります。
カードを中央に表示させる
ShowCardCenterメソッドは名前のとおり、カードを真ん中のPanelに表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { void ShowCardCenter(Graphics g) { if(CenterCard == null) return; int baseX = (panelCenter.Width - CenterCard.Size.Width) / 2; Rectangle rect = new Rectangle(new Point(baseX, 0), CenterCard.Size); g.DrawImage(CenterCard.Bitmap, rect); } } |
プロパティCenterCardはプレイヤーによって中央に出されたカードを表示させるとともに、そのカードがなにであるか情報を提供します。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { Card centerCard = null; Card CenterCard { get { return centerCard; } set { centerCard = value; panelCenter.Invalidate(); } } } |
最初のカードを出す
ゲームを開始するためには最初の台札を表示させる必要があります。この処理をおこなうのがPutFirstCardメソッドです。
最初の台札はシャッフルされたカードの先頭を使えばよいので、これをCenterCardにセットします。
1 2 3 4 5 6 7 8 9 |
public partial class Form1 : Form { void PutFirstCard() { Card firstCard = ShuffledCards[0]; ShuffledCards.RemoveAt(0); CenterCard = firstCard; } } |
ゲームが開始されてからの流れはこんな感じです。
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() { CenterCard = null; ShuffleCard(); HandoutCards(); panelSouth.Invalidate(); panelWest.Invalidate(); panelNorth.Invalidate(); panelEast.Invalidate(); PutFirstCard(); RivalsTurnAsync(); // ライバルがカードを出す(後述) } } |
順番にカードを出す
つぎに時計回りで順番にカードをだしていきます。
カードを出す処理、出せなかったときの処理はPlayerクラスのメソッドでおこなうことにします。
カードは台札と同じスートか番号でなければなりません。そこで台札を引数にする必要があります。あとカードが出せない場合はカードを積み札からとり続けることになるので、これも引数として渡すことになります。
出せるカードが複数あるときは乱数で適当に決めます。
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 |
public class Player { Random Random = new Random(); public Card PutCard(Card card, List<Card> shuffledCards) { // 出せるカードを求める List<Card> cards1 = Cards.Where(x => x.Number == card.Number || x.Suit == card.Suit).ToList(); Card ret = null; if(cards1.Count == 1) { Cards.Remove(cards1[0]); ret = cards1[0]; // カードを出したことがわかるようにパネルの表示をさせる // ただしその前に少し止める System.Threading.Thread.Sleep(1000); PlayerPanel.Invalidate(); } else if(cards1.Count > 1) { // 複数ある場合 int index = Random.Next(cards1.Count); Card card1 = cards1[index]; Cards.Remove(card1); ret = card1; // カードを出したことがわかるようにパネルの表示をさせる // ただしその前に少し止める System.Threading.Thread.Sleep(1000); PlayerPanel.Invalidate(); } else { // 出せるカードがない場合、積み札からとり続ける while(true) { // 積み札がなくなったらすでに出されているカードを使う。 if(shuffledCards.Count == 0) _form1.ShuffleCard(card); Card addCard = shuffledCards[0]; shuffledCards.RemoveAt(0); if(addCard.Suit == card.Suit || addCard.Number == card.Number) { ret = addCard; break; } else { // カードが追加されたことがわかるようにパネルの表示をさせる // ただしその前に少し止める Cards.Add(addCard); System.Threading.Thread.Sleep(1000); PlayerPanel.Invalidate(); } } } // カードを出したことがわかるようにパネルの表示をさせる // ただしその前に少し止める System.Threading.Thread.Sleep(1000); PlayerPanel.Invalidate(); return ret; } } |
これは積み札がなくなったときに出されているカードをシャッフルして再利用するためのメソッドです。
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 { public void ShuffleCard(Card lastCard) { List<Card> playersCards = new List<Card>(); foreach(Player player in Players) { playersCards.AddRange(player.Cards); } playersCards.Add(lastCard); List<Card> tempCards = Cards.Except(playersCards).ToList(); int cnt = tempCards.Count(); for(int i = 0; i < cnt; i++) { int index = random.Next(0, tempCards.Count); Card card = tempCards[index]; tempCards.RemoveAt(index); ShuffledCards.Add(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 |
public partial class Form1 : Form { async void RivalsTurnAsync() { IsMyTurn = false; Task<bool> task = Task.Run<bool>(() => RivalsTurn()); bool result = await task; // result == true ならゲーム続行 if(result == true) IsMyTurn = true; else { DialogResult dr = MessageBox.Show("ゲーム終了\nもう一度やりますか?", "確認", MessageBoxButtons.YesNo); if(dr == DialogResult.Yes) GameStart(); } } bool RivalsTurn() { // 0 は自分なのでライバルではない List<Player> rivals = Players.Skip(1).ToList(); foreach(var player in rivals) { // カードを出すときにプレイヤーごとに少し止める System.Threading.Thread.Sleep(1000); CenterCard = player.PutCard(CenterCard, ShuffledCards); panelCenter.Invalidate(); if(player.Cards.Count == 0) { MessageBox.Show("あがりました"); return false; } } // 自分の番だが出せるカードはあるのか? // ない場合は出せるカードを引くまでカードを取り続ける if(!AddCardIfCanNotPutCard()) return true; // 出せるカードがあるので次はあなたの番です。 else return RivalsTurn(); // 自動処理がおこなわれたのでライバルのターンです。 } } |
これは順番がまわってきたときに出せるカードがあるかどうか調べて、ない場合は出せるカードがみつかるまでカードを引き続けるメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { bool AddCardIfCanNotPutCard() { if(!Players[0].Cards.Any(x => x.Suit == CenterCard.Suit || x.Number == CenterCard.Number)) { IsMyTurn = false; Text = "出せるカードがありません"; System.Threading.Thread.Sleep(500); CenterCard = Players[0].PutCard(CenterCard, ShuffledCards); Text = CenterCard.GetCardString() + " を出しました"; System.Threading.Thread.Sleep(500); IsMyTurn = false; return true; } return false; } } |
ライバルがカードを出し終わったら自分のカードをだします。七並べのときと同様、カードをクリックします。
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 |
public partial class Form1 : Form { private void panelSouth_MouseDown(object sender, MouseEventArgs e) { Card card = GetClickedCard(e); if(!IsMyTurn) { MessageBox.Show("まだあなたの番ではありません"); return; } if(card == null) { panelSouth.Invalidate(); MessageBox.Show("カードがある位置をクリックしてください"); return; } if(CenterCard.Suit == card.Suit || CenterCard.Number == card.Number) { Players[0].Cards.Remove(card); CenterCard = card; panelCenter.Invalidate(); panelSouth.Invalidate(); if(Players[0].Cards.Count == 0) { MessageBox.Show("あなたはあがりました"); return; } RivalsTurn(); } else { MessageBox.Show("このカードは出せません"); } } } |
GetClickedCardはクリックされた場所にあるカードが何かを取得するメソッドです。
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 { Card GetClickedCard(MouseEventArgs e) { List<Rectangle> rects = GetRectanleMyCard(); int i = 0; bool isFind = false; foreach(Rectangle rect in rects) { if(rect.Left <= e.X && e.X <= rect.Right && rect.Top <= e.Y && e.Y <= rect.Bottom) { isFind = true; break; } i++; } if(isFind) return Players[0].Cards[i]; else return null; } } |
これでカードゲームらしくなりました。次回に続きます。
Playerクラスにて下記の
Program.form1.ShuffleCard(card);
ですが、form1がProgramに存在しない為、エラーになります。
Form1 form1 = new Form1();
PlayerクラスにForm1 のインスタンスを追加して、下記に修正するのが正解でしょうか?
Program.form1.ShuffleCard(card) → form1.ShuffleCard(card);
Programクラス内にForm1 のインスタンスをコピーするというかなり無茶をしています。
PlayerクラスからForm1クラス内のメソッドにアクセスするためなのですが・・・
Programクラスは以下のようになっています。記事に書いていない。申し訳ない。
ただ無理矢理感が強すぎるコードなので、Playerクラスのコンストラクタを変更したほうがいいかもしれません。この部分は書き直します。
追記:
Playerクラスのコンストラクタを書き直しました。
コンストラクタの引数を3つにしました。
それからPlayerクラスのコンストラクタを変更したため、以下の部分も変更です。
Program.form1.ShuffleCard(card);なんて変なことをしなくても _form1.ShuffleCard(card);で呼び出し可能になります。
お疲れ様です。修正ありがとうございます。
この時点でデバッグを実行した所
panelSouth_Paintメソッド内にて、GetRectanleMyCardメソッドを呼びたし時、
下記の部分にて、
int cardCount = Players[0].Cards.Count;
インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。
という例外がスローされてしまいます。
コメントへの返信
>panelSouth_Paintメソッド内にて、GetRectanleMyCardメソッドを呼びたし時、
>下記の部分にて、
>int cardCount = Players[0].Cards.Count;
>インデックスが範囲を超えています。負でない値で、コレクションのサイズよりも小さくなければなりません。
>という例外がスローされてしまいます。
GetRectanleMyCardメソッドを呼び出したときにPlayersリストにPlayerオブジェクトは格納されているでしょうか?
おそらくこれが原因だと思います。
C#でドボンをつくる Cardクラスの作成でForm1クラスのコンストラクタはああするのがいいとかこれはよくないというわかりにくい書き方をしてしまい、質問者様を混乱させてしまったようです。
Form1クラスのコンストラクタはとりあえず現段階では以下で問題ないと思われます。
あとは[ゲームスタート]といったメニューを作成してこれがクリックされたら以下のメソッドが実行されるようにします。
これで問題はないはずなのですが・・・