前回は「信長の野望」もどきでもなんでもなかったので、今回は最低でも「もどき」といえるものを作ることにします。
まずユーザーコントロールを作成します。クラス名は城を表示するためのものなのでCastleにします。
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 |
public partial class Castle : UserControl { public Castle() { InitializeComponent(); } public int CastleID { get; set; } public string CastleName { get { return label1.Text; } set { label1.Text = value; } } string castleOwner = ""; public string CastleOwner { get { return castleOwner; } set { castleOwner = value; label2.Text = castleOwner + " " + Soldiers.ToString(); } } int soldiers = 0; public int Soldiers { get { return soldiers; } set { soldiers = value; label2.Text = castleOwner + " " + Soldiers.ToString(); } } } |
これをフォーム上に配置します。
それから表示させたい城の名称と城主の名前、兵力を管理するために以下のようなクラスを作成します。
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 |
public class CastleStatus { // 城のID(0以上の通し番号の整数) public int CastleID { get; set; } // 城の名称 public string CastleName { get; set; } // 城主 public string CastleOwner { get; set; } // 兵力 public int SoldierCount { get; set; } // 各ターンで行動したか? public bool Done { get; set; } } |
次にこれに城の名称と城主の名前、兵力を表示させるために以下のような処理をおこないます。
まず自作メソッド CreateCastleList()でCreateオブジェクトのリストを作成するとともに、0 ~ 9 の通し番号をつけます。次に自作 InitCastleStatusList()で城のステータスを表すCastleStatusのリストを生成し、ここに城の名称と城主の名前、兵力をセットします。最後に自作メソッド UpdateCastlesStatusで反映させます。
コンストラクタの実行はこんな感じになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { List<CastleStatus> CastleStatusList = new List<CastleStatus>(); List<Castle> Castles = new List<Castle>(); List<Situation> Situations = new List<Situation>(); // 後述 CastleStatus PlayerCastleStatus = null; // 後述 public Form1() { InitializeComponent(); CreateCastleList(); InitCastleStatusList(); } } |
Createオブジェクトのリストを作成するとともに、0 ~ 9 の通し番号をつけるCreateCastleListメソッドを示します。
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 |
public partial class Form1 : Form { void CreateCastleList() { userControl0.CastleID = 0; userControl1.CastleID = 1; userControl2.CastleID = 2; userControl3.CastleID = 3; userControl4.CastleID = 4; userControl5.CastleID = 5; userControl6.CastleID = 6; userControl7.CastleID = 7; userControl8.CastleID = 8; userControl9.CastleID = 9; Castles.Add(userControl0); Castles.Add(userControl1); Castles.Add(userControl2); Castles.Add(userControl3); Castles.Add(userControl4); Castles.Add(userControl5); Castles.Add(userControl6); Castles.Add(userControl7); Castles.Add(userControl8); Castles.Add(userControl9); } } |
城のステータスを表すCastleStatusのリストを生成し、ここに城の名称と城主の名前、兵力をセットするInitCastleStatusListメソッドを示します。城の名称と城主はネタ元に合わせています。
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 |
public partial class Form1 : Form { void InitCastleStatusList() { CastleStatusList.Clear(); int castlesCount = Castles.Count; for (int i = 0; i < castlesCount; i++) CastleStatusList.Add(new CastleStatus()); CastleStatus status = CastleStatusList[0]; status.CastleID = 0; status.CastleName = "米沢城"; status.CastleOwner = "伊達"; status.SoldierCount = 5; status = CastleStatusList[1]; status.CastleID = 1; status.CastleName = "春日山城"; status.CastleOwner = "上杉"; status.SoldierCount = 5; status = CastleStatusList[2]; status.CastleID = 2; status.CastleName = "躑躅ヶ崎館"; status.CastleOwner = "武田"; status.SoldierCount = 5; status = CastleStatusList[3]; status.CastleID = 3; status.CastleName = "小田原城"; status.CastleOwner = "北条"; status.SoldierCount = 5; status = CastleStatusList[4]; status.CastleID = 4; status.CastleName = "岡崎城"; status.CastleOwner = "徳川"; status.SoldierCount = 5; status = CastleStatusList[5]; status.CastleID = 5; status.CastleName = "岐阜城"; status.CastleOwner = "織田"; status.SoldierCount = 5; status = CastleStatusList[6]; status.CastleID = 6; status.CastleName = "二条城"; status.CastleOwner = "足利"; status.SoldierCount = 5; status = CastleStatusList[7]; status.CastleID = 7; status.CastleName = "吉田郡山城"; status.CastleOwner = "毛利"; status.SoldierCount = 5; status = CastleStatusList[8]; status.CastleID = 8; status.CastleName = "岡豊城"; status.CastleOwner = "長宗我部"; status.SoldierCount = 5; status = CastleStatusList[9]; status.CastleID = 9; status.CastleName = "内城"; status.CastleOwner = "島津"; status.SoldierCount = 5; UpdateCastlesStatus(CastleStatusList); } } |
CastleStatusのリストを実際に表示されている城に反映させるUpdateCastlesStatusメソッドを示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { void UpdateCastlesStatus(List<CastleStatus> newStatuses) { foreach (CastleStatus status in newStatuses) { Castle castle = Castles.First(x => x.CastleID == status.CastleID); castle.CastleID = status.CastleID; castle.CastleName = status.CastleName; castle.CastleOwner = status.CastleOwner; castle.Soldiers = status.SoldierCount; } } } |
スタートボタンがクリックされたらゲームスタートです。前回のゲームデータが表示されているかもしれないのでInitCastleStatusListメソッドを実行します。そしてランダムに順番を決めます。そのためにCastleStatusListの順番を入れ替えるGetShuffledCastlesメソッドを呼び出します。Turnメソッドについてはこのあと解説します。
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 |
public partial class Form1 : Form { Random random = new Random(); private void button1_Click(object sender, EventArgs e) { // ゲーム中はゲーム開始のボタンをクリックできないように非表示にする button1.Visible = false; InitCastleStatusList(); CastleStatusList = GetShuffledCastles(); Turn(); } List<CastleStatus> GetShuffledCastles() { List<CastleStatus> ret = new List<CastleStatus>(); // とりあえずリストのコピーをつくる。 // 乱数を発生させてコピーからひとつずつ抜き出してretに追加していく。 var castlesCopy = this.CastleStatusList.ToList(); while (castlesCopy.Count > 0) { int r = random.Next(castlesCopy.Count); ret.Add(castlesCopy[r]); castlesCopy.RemoveAt(r); } // なにもしていないので status.Done には false を代入する。 foreach (CastleStatus status in ret) status.Done = false; return ret; } } |
Turnメソッドですが、そのターンでなにもしていない城のリストを取得したあと、foreach文で処理を回すわけですが、そのなかで城主が「織田」かどうかを調べます。この場合はプレイヤーのターンです。複数の城がある場合、どれがどれなのかわからなくならないようにPlayerCastleStatusにCastleStatusを保存します。
それ以外は敵のターンなのでEnemyTurnメソッドで処理をします。このときゲームオーバーになる場合があるので、処理を継続する必要があるのかを戻り値で判断できるようにしています。
表示させるメッセージはSituationsのなかに追加していき、ShowSituationメソッドで表示させます。表示させるメッセージが存在する場合はつぎのメッセージを見る以外の動作はできないようにします(後述)。
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 |
public partial class Form1 : Form { void Turn() { // 「決定」ボタンなどをクリックできないように非表示にする buttonDecision.Visible = false; buttonWait.Visible = false; // 次のメッセージを表示させるためのボタンをクリック可能にする buttonShowSituation.Visible = true; // まだそのターンでなにもしていない城のリストを取得する List<CastleStatus> Statuses = CastleStatusList.Where(x => !x.Done).ToList(); foreach (var status in Statuses) { string owner = status.CastleOwner; int id = status.CastleID; // 城主が「織田」ならプレイヤーのターンである if (owner == "織田") { PlayerCastleStatus = status; string str1 = "順番がきました。\nここは " + status.CastleName + " です。"; Situations.Add(new Situation(str1, CastleStatusList)); ShowSituation(); return; } bool doesContinue = EnemyTurn(status); if (!doesContinue) return; } PlayerCastleStatus = null; ShowSituation(); } } |
Situationsですが、Form1クラスのなかで以下のように宣言されています。
1 2 3 4 |
public partial class Form1 : Form { List<Situation> Situations = new List<Situation>(); } |
そしてSituationクラスは以下のようになっています。
敵のターンにかんする処理はすぐに終わりますが、その結果は1行ずつ表示させるために表示させたいメッセージとそのときの城の状態をSituationクラスに格納します。
コンストラクタ内でそのときの城の状態をコピーして記憶させています。あとはメッセージが表示されるタイミングでUpdateCastlesStatusメソッドで城の状態をフォームに反映させればよいのです。
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 Situation { public Situation(string message, List<CastleStatus> statuses) { Message = message; List<CastleStatus> castleStatuses = new List<CastleStatus>(); foreach (CastleStatus status in statuses) { CastleStatus newStatus = new CastleStatus(); newStatus.CastleID = status.CastleID; newStatus.CastleName = status.CastleName; newStatus.CastleOwner = status.CastleOwner; newStatus.SoldierCount = status.SoldierCount; newStatus.Done = status.Done; castleStatuses.Add(newStatus); } CastleStatusList = castleStatuses; } public string Message { private set; get; } public List<CastleStatus> CastleStatusList { private set; get; } } |
EnemyTurnメソッドでは敵にとって自分の城と敵の城をわけてリストを作成します。そして兵が存在する場合は戦争を仕掛けます。
戦争を仕掛けるときは動員する人数を乱数で決めます。このとき0人になったら戦争はやめます。0人より多い人数を動員する場合はどの城に仕掛けるかを乱数で決めて実行します。戦争の結果は別のメソッド、Warで求めます。
戦争の結果、織田側が負けた場合で、残りの城が存在しない場合はゲームオーバーです。
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 |
public partial class Form1 : Form { bool EnemyTurn(CastleStatus status) { string owner = status.CastleOwner; int id = status.CastleID; status.Done = true; // 敵にとって自分の城 List<CastleStatus> myCastles = CastleStatusList.Where(x => x.CastleID != id && x.CastleOwner == owner).ToList(); // 敵にとって敵の城 List<CastleStatus> enemyCastles = CastleStatusList.Where(x => x.CastleID != id && x.CastleOwner != owner).ToList(); // 兵の人数を調べて0の場合はなにもしない、兵が存在する場合は戦争を仕掛ける if (status.SoldierCount > 0) { // 戦争に動員する兵の人数を求める。これが0のときはなにもしない int r1 = random.Next(status.SoldierCount + 1); if (r1 == 0) { string str1 = status.CastleName + " は静観します。"; Situations.Add(new Situation(str1, CastleStatusList)); return true; } // 戦争はどこにしかけるか? // 敵が所有する城を適当に選ぶ int r = random.Next(enemyCastles.Count); CastleStatus atackTarget = enemyCastles[r]; string oldOwner = atackTarget.CastleOwner; string str = status.CastleOwner + " が " + atackTarget.CastleName + " に攻め込みました。\n"; str += status.CastleName + " " + r1.ToString() + " → " + atackTarget.CastleName; Situations.Add(new Situation(str, CastleStatusList)); // 戦争の結果を求める(勝ったらtrue、負けたらfalse) if (War(status, r1, atackTarget)) { string str1 = status.CastleOwner + " が " + atackTarget.CastleName + " を攻略しました。"; Situations.Add(new Situation(str1, CastleStatusList)); // 「織田」が負け、織田が所有する城が0になったらゲームオーバー if (CastleStatusList.Count(x => x.CastleOwner == oldOwner) == 0) { Situations.Add(new Situation(String.Format("{0}は滅亡しました!!!", oldOwner), CastleStatusList)); if (oldOwner == "織田") { Situations.Add(new Situation("ゲームオーバー", CastleStatusList)); ShowSituation(); return false; } } } else { string str1 = status.CastleOwner + " の攻撃は失敗しました。\n"; str1 += oldOwner + " は" + atackTarget.CastleName + " を守り抜きました。"; Situations.Add(new Situation(str1, CastleStatusList)); } } else { string str1 = status.CastleName + " には兵がいません。"; Situations.Add(new Situation(str1, CastleStatusList)); } return true; } } |
戦争をした結果を求めるメソッド Warを示します。ネタ元と同じで守備側が有利にします。乱数を生成して0なら守備側の兵数を1減らし、1と2のときは攻撃側の兵数を1減らします。2倍の差がありますが、実際に城を落とすためには守備側の3倍の兵力が必要であるといわれています。
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 |
public partial class Form1 : Form { bool War(CastleStatus attackingside, int attackingSoldiers, CastleStatus attackedside) { attackingside.SoldierCount -= attackingSoldiers; int attackedSoldiers = attackedside.SoldierCount; string str = String.Format("攻撃側:{0}と守備側:{1}の戦いです。", attackingSoldiers * 1000, attackedSoldiers * 1000); Situations.Add(new Situation(str, CastleStatusList)); while (attackingSoldiers > 0 && attackedSoldiers > 0) { int r = random.Next(3); if (r == 0) { attackedSoldiers--; } else { attackingSoldiers--; } string str1 = String.Format("攻撃側:{0} 守備側:{1}", attackingSoldiers * 1000, attackedSoldiers * 1000); Situations.Add(new Situation(str1, CastleStatusList)); } if (attackingSoldiers > 0) { attackedside.SoldierCount = attackingSoldiers; attackedside.CastleOwner = attackingside.CastleOwner; return true; } else { attackedside.SoldierCount = attackedSoldiers; return false; } } } |
ボタンをおすとメッセージが表示されます。ShowSituationメソッドになかでメッセージをすべて表示してしまった場合はボタンを押せなくしています。
1 2 3 4 5 6 7 |
public partial class Form1 : Form { private void buttonShowSituation_Click(object sender, EventArgs e) { ShowSituation(); } } |
実際にメッセージを表示するためのShowSituationメソッドを示します。TurnメソッドやEnemyTurnメソッド、Warメソッドが実行されるとSituationsのなかにメッセージが格納されていきます。このなかで一番最初に格納されているものを表示させるとともにUpdateCastlesStatusメソッドを呼び出してそのときの城の状態を反映させています。
メッセージがなくなったときとはプレイヤーに順番が回ってきたとき、プレイヤーがすべての城を獲得したとき又は失ったとき、すべての城主の行動が終了したときです。
すべての城主の行動が終了かどうかはCastleStatus.Doneプロパティがすべてtrueになっているかどうかでわかります。このときは1年が経過したとして、翌年のターンにはいります。この場合はTurnメソッドを呼び出します。
プレイヤーに順番が回ってきたときは自作メソッド PlayerAction(後述)を呼び出します。すでにゲームクリアまたはゲームオーバーになったときは次へボタンがクリックできないように非表示にして、ゲーム開始ボタンをクリックできるように表示させます。
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 |
public partial class Form1 : Form { void ShowSituation() { if (Situations.Count > 0) { string mes = Situations[0].Message; label1.Text = mes.Replace("\n", "\r\n"); UpdateCastlesStatus(Situations[0].CastleStatusList); Situations.RemoveAt(0); if (Situations.Count == 0) { bool gameClear = CastleStatusList.Count(x => x.CastleOwner != "織田") == 0; bool gameOver = CastleStatusList.Count(x => x.CastleOwner == "織田") == 0; if (gameClear || gameOver) { button1.Visible = true; buttonShowSituation.Visible = false; return; } if (PlayerCastleStatus != null && !PlayerCastleStatus.Done) { button1.Visible = false; buttonShowSituation.Visible = true; // PlayerCastleStatusがnullではなく.Doneがtrueでないのであれば // プレイヤーに順番が回ってきたことになる PlayerAction(); } } } else if (Situations.Count == 0) { if (!CastleStatusList.Any(x => !x.Done)) { Situations.Add(new Situation("次の年になりました。", CastleStatusList)); CastleStatusList = GetShuffledCastles(); Turn(); } } } } |
プレイヤーに順番がまわってきたときは移動先の城をコンボボックスに表示させます。そして移動させたい人数を選択して[決定]ボタンをおせば移動先が味方であるときは移動し、敵の城であるときは戦争になります。
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 |
public partial class Form1 : Form { void PlayerAction() { buttonShowSituation.Visible = false; buttonDecision.Visible = true; buttonWait.Visible = true; if (PlayerCastleStatus.SoldierCount <= 0) { buttonDecision.Visible = false; return; } List<CastleStatus> statuses = this.CastleStatusList.Where(x => x.CastleName != PlayerCastleStatus.CastleName).ToList(); statuses = statuses.OrderBy(x => x.CastleID).ToList(); this.comboBox1.Items.Clear(); foreach (CastleStatus status in statuses) { comboBox1.Items.Add(status.CastleName); } comboBox1.SelectedIndex = 0; numericUpDown1.Maximum = PlayerCastleStatus.SoldierCount; numericUpDown1.Minimum = 1; } } |
[決定]ボタンを押した場合、移動先としてどこが選択されているかを調べます。移動先のstatus.CastleOwnerがプレイヤーと同じ場合は「移動」で移動元の城の兵力が減り、移動先が増えます。移動先のstatus.CastleOwnerが敵の場合は「戦争」でありWarメソッドが呼び出されます。
戦争に勝った場合は敵勢力は自分の城をすべてなくしているかもしれません。このときは「家は滅亡しました」と表示されます。またすべての城がすべてプレイヤーのものになっている場合は天下統一となりゲームクリアです。
ゲームクリアの場合は最後のメッセージを表示させるために、ShowSituationメソッドが実行されます。それ以外のときはほかの敵のターンになるため、Turnメソッドが呼び出されます。
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 { private void buttonDecision_Click(object sender, EventArgs e) { int soldierCount = (int)numericUpDown1.Value; int index = comboBox1.SelectedIndex; if (index != -1) { string castleName = comboBox1.Items[index].ToString(); CastleStatus status = CastleStatusList.First(x => x.CastleName == castleName); if (status.CastleOwner == "織田") { // 移動の場合 PlayerCastleStatus.SoldierCount -= soldierCount; status.SoldierCount += soldierCount; } else { // 戦争の場合(自動的に攻め方の城の人数は差し引かれる) string oldOwner = status.CastleOwner; if (War(PlayerCastleStatus, soldierCount, status)) { string str = "勝ったぞ。えいえいおー\n" + status.CastleName + " は織田家の城になりました。"; Situations.Add(new Situation(str, CastleStatusList)); if (CastleStatusList.Count(x => x.CastleOwner == oldOwner) == 0) Situations.Add(new Situation(String.Format("{0}家は滅亡しました!!!", oldOwner), CastleStatusList)); if(CastleStatusList.Count(x => x.CastleOwner != "織田") == 0) Situations.Add(new Situation("織田家が天下統一に成功しました!!!", CastleStatusList)); } else { string str = "残念ながら闘いに敗れました。\n"; Situations.Add(new Situation(str, CastleStatusList)); } } } PlayerCastleStatus.Done = true; UpdateCastlesStatus(CastleStatusList); bool gameClear = CastleStatusList.Count(x => x.CastleOwner != "織田") == 0; if (gameClear) { buttonDecision.Visible = false; buttonWait.Visible = false; button1.Visible = false; buttonShowSituation.Visible = true; ShowSituation(); } else Turn(); } } |
待機の場合は兵力が6になります(プレイヤーだけの特権)。このようなプレイヤーだけの特権があるのはデバッグのときに結果を自由に操作できるようにするためです。
1 2 3 4 5 6 7 8 9 10 |
public partial class Form1 : Form { private void buttonWait_Click(object sender, EventArgs e) { PlayerCastleStatus.Done = true; PlayerCastleStatus.SoldierCount = 6; UpdateCastlesStatus(CastleStatusList); Turn(); } } |
このゲームはネタ元とちがって隣の城でなくても攻撃できてしまいます。ブラウザアプリでも作りたいので、あまりこのあたりは深入りしないようにしています。