⇒ 動作確認はこちらからどうぞ。
今回は戦闘に関する処理を実装します。
Turnメソッドのなかで城の状態に応じて処理をするわけですが、クラスをつかって処理をします。たとえば
どこからも攻め込まれていないプレイヤーの城
PlayerCastleIfNormalクラス
攻め込まれようとしているプレイヤーの城
PlayerCastleIfAttackingクラス
包囲されているプレイヤーの城
PlayerCastleIfSiegingクラス
プレイヤーに攻め込まれようとしている敵の城
EnemyCastleIfPlayerAttackingクラス
プレイヤーに包囲されている敵の城
EnemyCastleIfPlayerSiegingクラス
別の敵に攻め込まれようとしている敵の城
EnemyCastleIfOtherEnemyAttackingクラス
別の敵に包囲されている敵の城
EnemyCastleIfOtherEnemySiegingクラス
どこからも攻め込まれていない敵の城
EnemyCastleIfNormalクラス
クラスをわけないとIndexクラスが大きくなってしまうので城の挙動にかんする処理はわけました。
8個のクラスを作成したわけですが、共通しておこなわれる処理があります。そこでCastleBaseというクラスを作成して、これを継承して8個のクラスを作成します。
基底クラス CastleBase
ではCastleBaseクラスを示します。
まずフィールド変数だけ示します。コンストラクタ内でこの城を攻撃しようとしている軍団、この城を包囲している軍団、この城を支援しようとしている軍団(援軍)に関する情報をセットしてしまいます。
bool型のatCanReturn、sgCanReturn、spCanReturnは出兵元の城に戻ることができるかどうかを示すものです。出兵したものの他の敵に攻め込まれて占領されたり、包囲されているのであればその城に戻ることはできません。
城内に強行突入する場合を除くと兵が半数に減ったときに退却するのですが、出兵元の城に戻れない場合は全滅するまで戦うことになります。
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 |
public class CastleBase { protected CastleStatus _status = null; protected Index _obj = null; protected string oldOwner = ""; protected string castleName = ""; // この城を攻撃しようとしている軍団に関するフィールド変数 protected Corps at = null; protected string atCastle = ""; protected string atOwner = ""; protected int atCount = 0; // 軍団がカウンター攻撃されたとき元の城に戻れるか? public bool atCanReturn { get; private set; } = true; // この城を包囲している軍団に関するフィールド変数 protected Corps sg = null; protected string sgCastle = ""; protected string sgOwner = ""; protected int sgCount = 0; // 城を包囲している軍団がカウンター攻撃されたとき元の城に戻れるか? public bool sgCanReturn { get; private set; } = true; // この城を支援しようとしている軍団(援軍)に関するフィールド変数 protected Corps sp = null; protected string spCastle = ""; protected string spOwner = ""; protected int spCount = 0; // 包囲軍との戦いに破れたときに元の城に戻れるか? protected bool spCanReturn { get; private set; } = true; } |
CastleBaseクラスのコンストラクタを示します。
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 |
public class CastleBase { public CastleBase(Index obj, CastleStatus status) { _obj = obj; _status = status; oldOwner = status.CastleOwner; castleName = status.CastleName; at = status.AttackingCorps; if (at != null) { atCastle = at.BaseCastleName; atOwner = at.BaseCastleOwner; atCount = at.SoldierCount; // 城を敵に奪われたり包囲されていないなら戻ることができる if (at.BaseCastle == null) atCanReturn = false; else if (at.BaseCastle.SiegingCorps != null) atCanReturn = false; else atCanReturn = true; } sg = status.SiegingCorps; if (sg != null) { sgCastle = sg.BaseCastleName; sgOwner = sg.BaseCastleOwner; sgCount = sg.SoldierCount; if (sg.BaseCastle == null) sgCanReturn = false; else if (sg.BaseCastle.SiegingCorps != null) sgCanReturn = false; else sgCanReturn = true; } sp = status.SupportCorps; if (sp != null) { spCastle = sp.BaseCastleName; spOwner = sp.BaseCastleOwner; spCount = sp.SoldierCount; if (sp.BaseCastle == null) spCanReturn = false; else if (sp.BaseCastle.SiegingCorps != null) spCanReturn = false; else spCanReturn = true; } } } |
それから城が攻め込まれている状態であれば、守りの兵がいない城は占領されるし、それ以外の場合は強行突入、反撃、兵糧攻めがおこなわれるので、そのためのメソッドもここで定義してしまいましょう。
OccupyIfNoDefenseSoldiersメソッドは、守りの兵がいるかどうか調べていない場合は進軍してきた敵に城を占領されるという処理をおこなっています。占領の処理がおこなわれたら攻め方の兵と兵糧を入城させ、城主を交代してtrueを返します。そうでない場合はなにも処理はおこなわれず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 |
public class CastleBase { public bool OccupyIfNoDefenseSoldiers() { Corps enemyCorps = null; if (_status.AttackingCorps != null) enemyCorps = _status.AttackingCorps; else if(_status.SiegingCorps != null) enemyCorps = _status.SiegingCorps; if (enemyCorps == null) return false; else if(enemyCorps.SoldierCount <= 0) return false; if (_status.SoldierCount <= 0) { string oldOwner = _status.CastleOwner; // 攻め方の兵と兵糧を入城させる // 城主を交代する _status.SoldierCount = enemyCorps.SoldierCount; if (_status.MilitaryFood < 0) _status.MilitaryFood = 0; _status.MilitaryFood += enemyCorps.MilitaryFood; _status.CastleOwner = enemyCorps.BaseCastleOwner; _status.AttackingCorps = null; _status.SiegingCorps = null; CastleOccupied(_obj, _status, oldOwner); return true; } return false; } } |
Siegeメソッドは兵糧攻めをおこないます。包囲されている側は兵の2倍の兵糧を消費します。兵がたくさんいる状態で包囲されると不利になります。兵糧がなくなったら落城で、城主は交代です。
先に城を包囲している側の兵糧がなくなることも考えられます。その場合は進軍の拠点になった城に退却しますが、ほかの敵に包囲されていると退却できません。その場合は城攻め側はその場で全滅とします。
兵糧攻めは1ターンでは決着しない場合があります。・・・といっても兵糧攻めで落城する場合は城側が反撃をするし、城側に十分な兵糧があり兵糧攻めで落城する見込みがない場合は強行突入するので、敵同士の包囲戦で兵糧がなくなるということは実はありません。
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 |
public class CastleBase { public ResultSiege Siege() { Corps corps = _status.SiegingCorps; // 包囲軍に戻れる城はあるのか? bool canSgReturn = true; if (corps.BaseCastle == null || corps.BaseCastle.SiegingCorps != null) canSgReturn = false; // 兵糧攻め実行中! _status.MilitaryFood -= _status.SoldierCount * 2; corps.MilitaryFood -= corps.SoldierCount; if (corps.MilitaryFood <= 0) { // 兵糧攻め兵糧攻め失敗 // 自分の城に帰る if (canSgReturn) corps.BaseCastle.SoldierCount += corps.SoldierCount; // 交戦状態の解消 _status.AttackingCorps = null; _status.SiegingCorps = null; return ResultSiege.Protected; } else if (_status.MilitaryFood <= 0) { // 兵糧攻めで落城 // 城の状態を変える _status.SoldierCount = corps.SoldierCount; if (_status.MilitaryFood < 0) _status.MilitaryFood = 0; _status.MilitaryFood += corps.MilitaryFood; _status.CastleOwner = corps.BaseCastleOwner; _status.AttackingCorps = null; _status.SiegingCorps = null; return ResultSiege.Occupied; } else { // まだ決着がつかない return ResultSiege.Sieging; } } } |
ForcedRushメソッドは強行突入です。強行突入の場合、城側のほうが2倍有利なのですが、兵糧攻めが通用しない場合は強行突入するしかありません。
強行突入は攻め方と城側のどちらか一方が全滅するまでおこなわれます。攻め方が勝ったら生き残った兵と兵糧を城にいれます。城側が撃退したら攻め方が持っていた兵糧も城側のものになります。
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 |
public class CastleBase { public bool ForcedRush() { int defenseSoldiers = _status.SoldierCount; _obj.AddSituation(String.Format("攻撃側({0}軍):{1} 守備側({2}軍 {3}):{4}", sgOwner, sgCount * 1000, oldOwner, castleName, defenseSoldiers * 1000)); while (sgCount > 0 && defenseSoldiers > 0) { int r = _obj.random.Next(3); if (r == 0) defenseSoldiers--; else sgCount--; _obj.AddSituation(String.Format("攻撃側({0}軍):{1} 守備側({2}軍 {3}):{4}", sgOwner, sgCount * 1000, oldOwner, castleName, defenseSoldiers * 1000)); } if (sgCount > 0) { // 乗っ取り成功 攻撃側の兵を城にいれ、兵糧も自分のものに // 城主を交代する _status.SoldierCount = sgCount; if (_status.MilitaryFood < 0) _status.MilitaryFood = 0; _status.MilitaryFood += _status.SiegingCorps.MilitaryFood; _status.CastleOwner = sgOwner; // 交戦状態の解除 _status.AttackingCorps = null; _status.SiegingCorps = null; return true; } else { // 乗っ取り失敗、現在の守備側の兵数を書き換え、兵糧も守備側のものに _status.SoldierCount = defenseSoldiers; _status.MilitaryFood += _status.SiegingCorps.MilitaryFood; // 交戦状態の解除 _status.AttackingCorps = null; _status.SiegingCorps = null; return false; } } } |
城側が兵糧攻めに耐えられない場合は無理な戦いであっても反撃する以外ありません。「座して死を待つ」はありえません。この処理をおこなうのがCounterattackメソッドです。引数は城側が反撃のために動員する軍団です。
どちらか一方の兵数が半分になるまで続けます。勝った側は相手の兵糧を奪うことができます。城側が勝利した場合は兵糧攻めは失敗で、攻め方は退却します。しかし攻め方が退却できる城がない場合は全滅するまで戦います。
また兵糧攻めが始まる前であってもこの処理が実行される場合があります。基本的に同じ処理がおこなわれます。この場合は城側が負けると城は包囲され、兵糧攻めが開始されます。
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 |
public class CastleBase { public bool Counterattack(Corps counterCorps) { string str; Corps sgCorps; int sgStartCount; bool canReturn; if (sg != null) { sgCorps = sg; sgStartCount = sgCount; canReturn = this.sgCanReturn; } else { sgCorps = at; sgStartCount = atCount; canReturn = this.atCanReturn; } int ctStartCount = counterCorps.SoldierCount; bool b = sgCorps != null && counterCorps != null; if (!b) { _obj.AddSituation("エラー!迎撃戦ができません。"); return false; } while (true) { int r = _obj.random.Next(2); if (r == 0) sgCorps.SoldierCount--; else counterCorps.SoldierCount--; str = String.Format("野戦:攻撃側({0}軍):{1} 反撃側({2}軍 {3}):{4}", sgOwner, sgCorps.SoldierCount * 1000, counterCorps.BaseCastleOwner, counterCorps.BaseCastleName, counterCorps.SoldierCount * 1000); _obj.AddSituation(str); // 一方が他方の半分より少なくなったら終了 // ただし包囲側は退却先の城がない、または包囲されている場合は退却できないので全滅するまで戦う bool b1 = sgCorps.SoldierCount <= 0; bool b2 = canReturn && sgCorps.SoldierCount * 2 < sgStartCount; if (b1 || b2) { // カウンタアタック成功 // 攻撃側の生き残りは城に返される(帰る城がある場合)が、兵糧は没収される if (canReturn) sgCorps.BaseCastle.SoldierCount += sgCorps.SoldierCount; _status.MilitaryFood += sgCorps.MilitaryFood; // 当然、カウンタ側の生き残りと兵糧は返される _status.SoldierCount += counterCorps.SoldierCount; _status.MilitaryFood += counterCorps.MilitaryFood; // 交戦状態の解消 _status.SiegingCorps = null; _status.AttackingCorps = null; return true; } else if (counterCorps.SoldierCount <= 0 || counterCorps.SoldierCount * 2 < ctStartCount) { // カウンタアタック失敗 // カウンタ側の生き残りは城に返されるが兵糧は没収される _status.SoldierCount += counterCorps.SoldierCount; sgCorps.MilitaryFood += counterCorps.MilitaryFood; // 包囲していないなら包囲に切り替える if (_status.SiegingCorps == null) { _status.SiegingCorps = _status.AttackingCorps; _status.AttackingCorps = null; } return false; } } } } |
CombatForSupportメソッドは包囲されている城に援軍がかけつけ戦闘になったときの処理をおこないます。このメソッドの戻り値は生き残った援軍側の兵数です。戻り値が0なら援軍側の作戦失敗です。
この処理もどちらか一方の兵数が半分になるまで続けます。そして勝った側は相手の兵糧を奪うことができます。援軍側が勝利した場合は兵糧攻めは失敗で、攻め方は退却します。しかし攻め方が退却できる城がない場合は全滅するまで戦います。援軍側も出撃した城に戻れなくなっている場合があります。この場合は全滅するまで戦います。
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 class CastleBase { public int CombatForSupport() { if (sg == null || sp == null) { _obj.AddSituation("エラー!迎撃戦ができません。"); return 0; } int sgStartCount = sgCount; int spStartCount = spCount; _obj.AddSituation(String.Format("包囲側({0}軍):{1}人 支援側({2}軍 {3}):{4}人", sgOwner, sg.SoldierCount * 1000, spOwner, _status.CastleName, sp.SoldierCount * 1000)); while (true) { int r = _obj.random.Next(2); if (r == 0) sg.SoldierCount--; else sp.SoldierCount--; _obj.AddSituation(String.Format("包囲側({0}軍):{1}人 支援側({2}軍 {3}):{4}人", sgOwner, sg.SoldierCount * 1000, spOwner, _status.CastleName, sp.SoldierCount * 1000)); // 先に最初の半分より少なくなったら終了(ただし退却できる城がない場合は全滅するまで戦う) bool b1 = sg.SoldierCount <= 0; bool b2 = sgCanReturn && sg.SoldierCount * 2 <= sgStartCount; bool b3 = sp.SoldierCount <= 0; bool b4 = spCanReturn && sp.SoldierCount * 2 <= spStartCount; if (b1 || b2) { // 迎撃成功の場合、包囲側の生き残りは城に返される(帰る城がある場合)が兵糧は没収される if (sgCanReturn) sg.BaseCastle.SoldierCount += sg.SoldierCount; _status.MilitaryFood += sg.MilitaryFood; // 支援側の兵と兵糧を入城させる int soldierCount = sp.SoldierCount; _status.SoldierCount += soldierCount; _status.MilitaryFood += sp.MilitaryFood; // 交戦状態の解除 _status.SiegingCorps = null; _status.SupportCorps = null; return soldierCount; } else if (b3 || b4) { // 迎撃失敗 // 迎撃失敗の場合、支援側の生き残りは支援元の城に返される(帰る城がある場合)が兵糧は没収される if (spCanReturn) sp.BaseCastle.SoldierCount += sp.SoldierCount; sg.MilitaryFood += sp.MilitaryFood; _status.SupportCorps = null; return 0; } } } } |
GetEnemyCastlesメソッドは引数でわたされた城に隣接する敵の城のリストを返します。これは自分のターンになったときに進軍の選択肢を表示するときや敵の行動アルゴリズムを考えるときに必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class CastleBase { public List<CastleStatus> GetEnemyCastles(CastleStatus status) { List<CastleStatus> enemyCastles = new List<CastleStatus>(); foreach (int i in status.NextCastles) { CastleStatus otherStatus = _obj.CastleStatusList.First(x => x.CastleID == i); if (otherStatus.CastleOwner != status.CastleOwner) { enemyCastles.Add(otherStatus); } } return enemyCastles; } } |
GetFriendlyCastlesメソッドはは引数でわたされた城に隣接する味方の城のリストを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class CastleBase { public List<CastleStatus> GetFriendlyCastles(CastleStatus status) { List<CastleStatus> friendlyCastles = new List<CastleStatus>(); foreach (int i in status.NextCastles) { CastleStatus otherStatus = _obj.CastleStatusList.First(x => x.CastleID == i); if (otherStatus.CastleOwner == status.CastleOwner) { friendlyCastles.Add(otherStatus); } } return friendlyCastles; } } |
CastleOccupiedメソッドは城を占領されたときに呼び出されます。その城を出撃拠点にしていた場合、CastleStatus.AttackingCorps.BaseCastle、CastleStatus.SiegingCorps.BaseCastle、CastleStatus.SupportCorps.BaseCastleに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 32 33 34 35 36 37 38 39 40 41 |
public class CastleBase { public void CastleOccupied(string oldCastleOwner) { // このときにはCastleStatus.CastleOwnerには新しい城主が入っている // 占領された城を拠点に別の城に攻め込もうとしたり包囲したり援軍を送っていないか調べてみる string castleName = _status.CastleName; foreach (CastleStatus status1 in _obj.CastleStatusList) { // 出撃拠点を乗っ取られたので負けた場合は戻る城がなくなる string attackBaseCastle; string attackBaseOwner; if (status1.AttackingCorps != null && status1.AttackingCorps.BaseCastleName == castleName) { status1.AttackingCorps.BaseCastle = null; attackBaseCastle = status1.AttackingCorps.BaseCastleName; attackBaseOwner = status1.AttackingCorps.BaseCastleOwner; _obj.AddSituation(String.Format("{0}から出撃した{1}軍は戻る城がなくなりました・・", attackBaseCastle, attackBaseOwner)); } if (status1.SiegingCorps != null && status1.SiegingCorps.BaseCastleName == castleName) { status1.SiegingCorps.BaseCastle = null; attackBaseCastle = status1.SiegingCorps.BaseCastleName; attackBaseOwner = status1.SiegingCorps.BaseCastleOwner; _obj.AddSituation(String.Format("{0}から出撃した{1}軍は戻る城がなくなりました・・", attackBaseCastle, attackBaseOwner)); } if (status1.SupportCorps != null && status1.SupportCorps.BaseCastleName == castleName) { status1.SupportCorps.BaseCastle = null; attackBaseCastle = status1.SupportCorps.BaseCastleName; attackBaseOwner = status1.SupportCorps.BaseCastleOwner; _obj.AddSituation(String.Format("{0}から出撃した{1}軍は戻る城がなくなりました・・", attackBaseCastle, attackBaseOwner)); } } } } |
引数で指定した武将が滅亡したかどうかを調べるメソッドです。所有するすべての城を失い、どこにも攻め込んでいない、包囲もしていないのであれば完全に滅亡したといえます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class CastleBase { public bool RuinCheck(string name) { if (_obj.CastleStatusList.Any(x => x.CastleOwner == name)) return false; foreach (CastleStatus status1 in _obj.CastleStatusList) { if (status1.AttackingCorps != null && status1.AttackingCorps.BaseCastleOwner == name) return false; if (status1.SiegingCorps != null && status1.SiegingCorps.BaseCastleOwner == name) return false; if (status1.SupportCorps != null && status1.SupportCorps.BaseCastleOwner == name) return false; } return true; } } |
プレイヤーが城を奪い取ったときや突入してきた敵の軍団を壊滅させたとき、これによって敵は滅亡しているかもしれません。またすべての敵を滅亡させれば天下統一が実現したことになります。
CheckGameClearIfEnemyRuinメソッドは引数の武将が滅亡したかどうかを調べるとともにゲームクリアのときはその表示の処理もおこなっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public class CastleBase { public bool CheckGameClearIfEnemyRuin(string oldOwner) { if (RuinCheck(oldOwner)) { _obj.AddSituation(String.Format("{0}家は滅亡しました♪", oldOwner)); if (_obj.IsGameClear()) { _obj.AddSituation("天下統一を達成しました♪ ゲームクリア!"); _obj.ShowSituation(); return true; } } return false; } } |
敵に城を奪われたときや突入して全滅したとき、プレイヤーは滅亡しているかもしれません。CheckGameOverIfRuinメソッドはプレイヤーが滅亡してしまったかを調べてその場合はゲームオーバー表示の処理もおこなっています。
1 2 3 4 5 6 7 8 9 10 11 |
public class CastleBase { public void CheckGameOverIfRuin() { if (_obj.IsGameOver()) { _obj.AddSituation(String.Format("{0}家は滅亡しました・・・ゲームオーバーです・・・", _obj.PlayerName)); _obj.ShowSituation(); } } } |
以下はゲームクリアとゲームオーバーを判定するためのメソッドです。
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 Index { public bool IsGameClear() { int allCount = CastleStatusList.Count; int count1 = CastleStatusList.Count(x => x.CastleOwner == PlayerName); int count2 = CastleStatusList.Count(x => x.AttackingCorps == null || x.AttackingCorps.BaseCastleOwner == PlayerName); int count3 = CastleStatusList.Count(x => x.SiegingCorps == null || x.SiegingCorps.BaseCastleOwner == PlayerName); int count4 = CastleStatusList.Count(x => x.SupportCorps == null || x.SupportCorps.BaseCastleOwner == PlayerName); return count1 == allCount && count2 == allCount && count3 == allCount && count4 == allCount; } bool IsGameOver() { int count1 = CastleStatusList.Count(x => x.CastleOwner == PlayerName); int count2 = CastleStatusList.Count(x => x.AttackingCorps != null && x.AttackingCorps.BaseCastleOwner == PlayerName); int count3 = CastleStatusList.Count(x => x.SiegingCorps != null && x.SiegingCorps.BaseCastleOwner == PlayerName); int count4 = CastleStatusList.Count(x => x.SupportCorps != null && x.SupportCorps.BaseCastleOwner == PlayerName); return count1 == 0 && count2 == 0 && count3 == 0 && count4 == 0; } } |