⇒ 動作確認はこちらからどうぞ。
前回はとりあえずBlazor WebAssemblyで「野望」もどきを作ってみましたが、これだけでは面白くないので改良を加えます。城を攻めるときに兵糧攻めのような方法も付け加えたいものです。
そこで城のステータスに兵糧(MilitaryFood)という概念を付け加えます。兵を動かすには兵糧が必要で、敵に包囲され兵糧も完全になくなってしまえば降伏する以外ありません。
兵糧という要素を追加
前回の試作品では隣でない城を攻めることもできました。今回は隣り合った城でないと攻撃できないように作りかえます。敵の城を攻撃するためには当然兵糧も用意しなければなりません。なくなれば兵数では優位でも撤収するしかありません。
これまでは1ターンが1年でした。もっと短くして1年=4ターンにします。1ターンで出兵、徴兵、徴税、開発のどれかができるようにします。城を包囲されているときはなにもできません。収穫の季節になっても兵糧米は増えません。これは城を包囲されてしまうと厳しい条件でゲームを進めるしかないことを意味しています。
ゲーム開始時の城における兵は5とし、兵糧は20とします。出兵をする場合は兵の3倍の兵糧が必要であるとします。
包囲と迎撃・強行突入
戦争になった場合、包囲される前に野戦で敵を蹴散らすことができるようにします。この場合、どちらが有利というのはなしで運次第です。先に兵力が攻め込んできた敵の半分以下になったら自分の城に撤退することにします。この部分は完全にネタ元の真似です。ただ真似ばかりでは面白くないのでオリジナルの要素として、勝った側は敵の兵糧を奪うことができるものとします。
敵の城に出撃して迎撃がなかった場合、迎撃されたけど敵を城に撤退させた場合、攻撃側は城を「包囲」します。いわゆる兵糧攻めの開始です。この場合、攻め込まれている城にターンが回ってきたら、攻め方は「強行突入」と「包囲継続」のふたつの選択ができるものとします。
「強行突入」が選択された場合は守備側が2倍有利であるという条件で戦闘がおこなわれます。これは1ターンで決着です。これまでどおり乱数を発生させて城側が2倍有利な条件で戦います。ここで攻め側が負けたら全滅、兵糧はとられてしまいます。城側が負けたら城と兵糧を奪われることにします。
「包囲継続」をする場合は攻撃側は兵の数だけ兵糧米が消費され、守備側は城内の兵の数の2倍の兵糧米が消費されていきます。とくに守備側に兵が多く、兵糧米が少ない場合、厳しい攻めとなります。だからこのような場合、守備側は包囲されないように迎撃したほうが得策であるように思われます。
城側の兵糧が尽きたら兵は全滅、城を明け渡すことになります。攻め側の兵糧がなくなったら自分の城に撤収です。
兵糧攻めは各陣営の兵糧米の量によっては1ターンでは終わらない場合があります。その場合、次のターンがまわってきたときに攻撃側は総攻撃をかけるかそのまま兵糧攻めを続けるか選択します。兵糧攻め継続の場合は守備側に「包囲を突破するための出撃」をするかどうかの選択権が与えられます。
また兵糧攻めされている側は「包囲を突破するための出撃」ができます(包囲されている状態でできるのはこれだけ)。「包囲を突破するための出撃」には兵糧攻めで消費させられる兵糧米に加えて、出撃する兵の数だけ兵糧米が必要です。
援軍の派遣
自分の城が複数あるときは包囲されている城に援軍を派遣することができます。他の城を攻撃するときに兵が足りないときも後方の城から兵を派遣して戦力をアップさせることができます。
自分の城に兵を送った場合は、すぐに反映されずに兵を送った先のターンになるまで反映させない仕様にします。また包囲されている城に味方の兵をおくった場合、包囲している敵と戦闘が起きるようにします。
攻撃されようとしている城とすでに包囲されている城の違いは、支援に来た兵が城にすぐに入城できるかどうかです。その城が攻撃されようとしている場合は先に入城させてそのあと戦闘をするかどうかを決めます。すでに包囲されている城であれば先に戦闘がおきるようにします。
とにかく攻めれば勝てる?
毎年9月になれば収穫がおこなわれ、兵糧が増えます。1年は4ターンなので城攻めするだけではなく、「徴兵」をして兵数を増やしたり、城攻めや兵糧攻めに耐えるために「徴税」をして必要な兵糧米を確保する、「開発」で少しでも多くの兵糧米が確保できるようにする必要があり、とにかく攻めれば勝てるという単純なゲームにはならないのではないかと思われます。
それから敵のアルゴリズムも考えます。敵(敵にとっての敵)と隣接している場合、兵力差があるのであれば攻め込み、そうでないのであれば兵力を増強させる行動をとります。敵と隣接していないのであれば隣接している味方の城に兵をおくります。
敵のアルゴリズムは攻撃目標の選定と迎撃をするしないは、城の兵と兵糧米の状態から判断させることにします。城側の兵が少なく兵糧米が潤沢にあるのに兵糧攻めをするのは得策ではありません。短期決戦で勝負をつけるべきでしょう。
また他の城を兵糧攻めをしている間に出撃拠点になった城を奪われてしまった場合は兵糧攻めの継続は不能となり、強行突入をさせます。それで城を占領できればそれでよし、できなければ全滅です。
ではやってみましょう。
CorpsクラスとCastleStatusへのプロパティの追加
最初にCastleStatusクラスにプロパティを追加します。隣の城や保有する兵糧米がわかるようにして、この城に攻撃や兵糧攻めをしかけている軍団もわかるようにしました。
軍団をあらわすCorpsクラスはこんな感じです。兵力や兵糧米、撤退するしかなくなったときにどこへ撤退するかをコンストラクタでセットします。
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 class Corps { public Corps(int soldierCount, int militaryFood, CastleStatus baseCastle) { SoldierCount = soldierCount; MilitaryFood = militaryFood; BaseCastle = baseCastle; _baseCastleName = baseCastle.CastleName; _baseCastleOwner = baseCastle.CastleOwner; } // 兵力 public int SoldierCount { get; set; } // 兵糧米 public int MilitaryFood { get; set; } // 出撃拠点 public CastleStatus BaseCastle { get; set; } // 出撃拠点の名称 string _baseCastleName = ""; public string BaseCastleName { get { return _baseCastleName; } } // 出撃拠点の城主 string _baseCastleOwner = ""; public string BaseCastleOwner { get { return _baseCastleOwner; } } } |
新しいプロパティを追加したCastleStatusクラスは以下のとおりです。城から出撃する軍団を生成し、攻め込まれたときもプロパティを参照すればわかるようにします。
城を攻撃するためには軍団オブジェクトをつくって攻撃目標のCastleStatus.AttackingCorpsにセットします。そしてその城にターンがまわってきたら城を包囲したり迎撃するための戦闘がおきるようにします。
城を包囲したらCastleStatus.AttackingCorpsにセットしている軍団オブジェクトをCastleStatus.SiegingCorpsにつけかえます(CastleStatus.AttackingCorpsはnullにする)。援軍を派遣するときはCastleStatus.SupportCorpsに軍団オブジェクトをセットします。その城にターンがまわってくると合流したり包囲軍との戦闘がおきるようになります。
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
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; } // 隣の城 public int[] NextCastles { get; set; } // 兵糧米 public int MilitaryFood { get; set; } // 収穫量 public int Yield { get; set; } = 20; // 開発 public void Develop() { Yield += 5; } // この城に攻撃をしかけている軍団 public Corps AttackingCorps { get; set; } // この城に兵糧攻めをしかけている軍団 public Corps SiegingCorps { get; set; } // この城の支援に来た軍団 public Corps SupportCorps { get; set; } // 攻撃・支援のための出兵 public Corps SendingForAttack(int soldierCount) { SoldierCount -= soldierCount; MilitaryFood -= soldierCount * 3; return new Corps(soldierCount, soldierCount * 3, this); } // 攻撃のための出兵ができる兵数 // 兵糧攻めをする場合のことを考えて、全兵糧の3分の1まで public int GetSoldierCountForAttack() { return MilitaryFood / 3; } // 反撃のための出兵 public Corps SendingForDefense(int soldierCount) { SoldierCount -= soldierCount; MilitaryFood -= soldierCount; return new Corps(soldierCount, soldierCount, this); } // 反撃のための出兵ができる兵数 // 反撃のための出兵は短期間なので兵糧と同じ量までと制限を緩和する public int GetSoldierCountForDefense() { return MilitaryFood; } } |
以下は兵糧攻めをしたときの結果をしめす列挙体です。1ターンでは解決しないので、結果は占領、防衛、そのまま継続の3とおりを想定しています。
1 2 3 4 5 6 |
public enum ResultSiege { Occupied, Protected, Sieging, } |
状況を表示するためのSituationクラスを示します。CastleStatusクラスのプロパティが増えたのでそのぶんコンストラクタでの処理が増えています。
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 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.AttackingCorps = status.AttackingCorps; newStatus.SiegingCorps = status.SiegingCorps; newStatus.Yield = status.Yield; newStatus.MilitaryFood = status.MilitaryFood; newStatus.Done = status.Done; castleStatuses.Add(newStatus); } CastleStatusList = castleStatuses; } public string Message { private set; get; } public List<CastleStatus> CastleStatusList { private set; get; } } |
続きは次回考えます。