前回のカードゲーム スピードをつくる クリックしたらカードを出す(1)の続きです。今回はコンピュータにカードを出させます。
Contents
コンピュータがカードを出す処理
具体的な方法ですが、タイマーのTickイベントが発生するたびにカードが出せるのであればカードを出させます。また
人間であるプレイヤーは場から台札にカードを出すときは出せるカードを連続で出して後から山札のカードで場を埋めるような出し方をしてもかまわない、ただしコンピュータは場から台札にカードを出したら山札から場のカードを補うように動作させます。このような設定にしたのはコンピュータに本気を出されると人間に勝ち目がないからです。
下の動画は動作確認というよりネタ動画です。コンピュータの動作を速くしているので人間は手の出しようがありません。そこでユーザーが自分でコンピュータの動作速度を自由に設定できる機能を追加してもいいかもしれません。
ではコンピュータにカードを出させる機能を追加していきましょう。
まずはTimer.Tickイベントが発生したときの処理です。
ゲームの勝敗がついてしまった(IsGameSet == true)場合、どちらもカードを出せない(CanPutNextCard() == false)場合はタイマーをいったん止めます。
そうでない場合はコンピュータはカードを出せる(ここでは台札に出すだけでなく山札のカードを場に移動させる動作も含む)ということなのでカードを出させる、または移動させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { int timerCount = 0; private void Timer1_Tick(object sender, EventArgs e) { if (!CanPutNextCard() || IsGameSet) { Timer1.Stop(); return; } // タイマーは適切にStartしStopしているか デバッグ用 timerCount++; Text = "タイマーカウント:" + timerCount.ToString(); CompPutCard(); } } |
コンピュータの動作に関するメソッドです。
コメントにあるように山札がある場合は、場が空いているなら山札からカードを補充し、場が空いていいないなら場から出せるカードを出します。山札がすでになくなっている場合は、山札からカードを補充できないので場から出せるカードを探して出させます。
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 |
public partial class Form1 : Form { void CompPutCard() { // 山札がある場合 if (CardsDeckPlayer2.Count > 0) { // 場が空いているなら山札からカードを補充 for (int i = 0; i < CardsPutIntoPlay2.Length; i++) { if (CardsPutIntoPlay2[i] == null) { // 山札から1枚とって場に移動させる CardsPutIntoPlay2[i] = CardsDeckPlayer2[0]; CardsDeckPlayer2.RemoveAt(0); Invalidate(); return; } } // 上のif文にかからなければ場のカードはすべて詰まっているので // 場から出せるカードを出す for (int i = 0; i < CardsPutIntoPlay2.Length; i++) { Card card = CardsPutIntoPlay2[i]; List<Card> cardsLedgerPlayer = CanPutCard(card.Number); if (cardsLedgerPlayer != null) { cardsLedgerPlayer.Add(card); CardsPutIntoPlay2[i] = null; Invalidate(); return; } } } else { // この部分が実行される場合は山札がない場合である // 場が空いていないなら場から出せるカードを探してあれば出す for (int i = 0; i < CardsPutIntoPlay2.Length; i++) { Card card = CardsPutIntoPlay2[i]; if (card == null) continue; List<Card> cardsLedgerPlayer = CanPutCard(card.Number); if (cardsLedgerPlayer != null) { cardsLedgerPlayer.Add(card); CardsPutIntoPlay2[i] = null; Invalidate(); return; } } } } } |
以上で終わります。・・・では短すぎるので設定を変更できるようにします。まずはコンピュータがカードを出す処理をする間隔です。上記の動画のように速すぎると無理ゲーになり、簡単すぎてもクソゲーとなります。どれくらいの間隔がいいかはユーザーに決めてもらいましょう。
設定でコンピュータの速度を調整できるようにする
メニューに[設定]を追加します。そしてこれをクリックしたら設定のダイアログが出てくるようにします。
設定のダイアログをつくる
ここではトラックバーを表示させて、これをスライドさせるとコンピュータの着手の間隔を0.1秒から10秒の間で好きな値を設定できるようにします。
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 |
public partial class FormConfig : Form { public FormConfig() { InitializeComponent(); // 最大値と最小値を設定 trackBar1.Maximum = 100; trackBar1.Minimum = 1; // トラックバーのつまみを移動させたときのイベントに対応できるようにする trackBar1.ValueChanged += TrackBar1_ValueChanged; // 特に意味はない。フォームのタイトルを「設定」にしてサイズ変更不可にする this.FormBorderStyle = FormBorderStyle.FixedDialog; this.Text = "設定"; // OKボタンとCancelボタンをおしたらダイアログでOK、Cancelボタンを押したときと同じ動作をさせる ButtonOK.DialogResult = DialogResult.OK; ButtonCancel.DialogResult = DialogResult.Cancel; } // Form1クラスのTimer1.Intervalを設定したらトラックバーに反映させるとともに // 設定されているタイマーイベントの間隔が表示させる int _interval = 1000; public int Interval { set { _interval = value; trackBar1.Value = value / 100; // ミリ秒と普通の秒での表示で親切設計(自画自賛) LabelInterval.Text = $"{Interval} ({Interval / 1000d}秒)"; } get { return _interval; } } // トラックバーのつまみを移動させたらフィールド変数Intervalを変動させ private void TrackBar1_ValueChanged(object sender, EventArgs e) { int value = (int)trackBar1.Value; Interval = value * 100; } } |
設定された値をTimer.Intervalにセットする
Form1クラス側では以下の処理をおこないます。
メニューの[設定]が選択されたらFormConfigクラスで生成されるダイアログが表示されます。設定のダイアログは複数表示されることはないので最初にフィールド変数としてオブジェクトを生成し、これを使い回します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { FormConfig form = new FormConfig(); private void MenuItemConfig_Click(object sender, EventArgs e) { SetConfig(); } void SetConfig() { form.Interval = Timer1.Interval; if (form.ShowDialog() == DialogResult.OK) { // [OK]ボタンが表示されたときだけTimer1.Intervalを変更する Timer1.Interval = form.Interval; } } } |
設定をファイルとして保存する
一度アプリを終了してまた設定するのは面倒くさいので設定を保存します。
普通はアプリの実行ファイルが保存されている場所に保存すればいいのですが、設定によってはできない場合もあるのでユーザーアプリケーションデータのパスを取得して、そこに保存します。そのパソコンは自分ひとりしか使っていないというのであれば実行ファイルが存在するフォルダで問題ありません。
1 2 3 4 5 |
// 実行ファイルが存在するフォルダへのパス Application.StartupPath // ユーザーアプリケーションデータのパス Application.UserAppDataPath |
保存するものは1つだけなのですが、あとになって気が変わって他の設定も保存したくなるのはよくある話です。そこでDocというクラスを作成しています。
1 2 3 4 |
public class Doc { public int Interval = 0; } |
SetConfigメソッドにXMLファイルとして設定を保存するコードを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { void SetConfig() { form.Interval = Timer1.Interval; if (form.ShowDialog() == DialogResult.OK) { Timer1.Interval = form.Interval; Doc doc = new Doc(); doc.Interval = Timer1.Interval; string configFilePath = Application.StartupPath + "\\config.xml"; System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(typeof(Doc)); System.IO.StreamWriter sw = new System.IO.StreamWriter(configFilePath); xml.Serialize(sw, doc); sw.Close(); } } } |
そしてアプリが起動したときに保存しておいた設定を読み出せるようにします。そうしないとファイルとして保存した意味がないので・・・。
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 { public Form1() { InitializeComponent(); BackColor = Color.Green; DoubleBuffered = true; Timer1.Interval = 2000; Timer1.Tick += Timer1_Tick; LoadConfig(); } void LoadConfig() { string configFilePath = Application.StartupPath + "\\config.xml"; // アプリが初回起動だった場合、設定が保存されたファイルが存在しないので // ファイルが存在するかの確認が必要 if (System.IO.File.Exists(configFilePath)) { System.Xml.Serialization.XmlSerializer xml = new System.Xml.Serialization.XmlSerializer(typeof(Doc)); System.IO.StreamReader sr = new System.IO.StreamReader(configFilePath); Doc doc = (Doc)xml.Deserialize(sr); sr.Close(); Timer1.Interval = doc.Interval; } } } |
まあまあの文字数になったのでこれで終わります。