using System; // МОДУЛЬ БОЕВОЙ СИСТЕМЫ: урон, потери, исход. Документ: Docs/Modules/Battle.md // Без Unity и без float: этот же код без изменений сможет считать бои на сервере. // // Правила (предложение, утверждает команда -- открытый вопрос этапа 6 плана): // - сила стороны = сумма по типам: воины x сила типа x (100% + контры + осадный бонус) x (100% + бонусы стороны); // - контры: пехота сильнее кавалерии, кавалерия сильнее лучников, лучники сильнее пехоты (+25%); // бонус типа против врага взвешивается по составу врага; // - осадные: против стен +50%, в поле -25%; стены дают защитникам +20%; // - побеждает тот, у кого сила больше; ничья -- защитнику; // - проигравший теряет половину каждого типа; победитель -- до половины, тем больше, чем ровнее был бой. public sealed class BattleSystem : IBattleSystem { // Сила одного воина каждого типа. Берётся из GameData.Troops (App/GameModules), здесь -- запасные значения. public static readonly int[] DefaultPower = { 4, 5, 9, 16 }; public const int CounterPercent = 25; public const int SiegeVsWallsPercent = 50; public const int SiegeInFieldPercent = -25; public const int WallsPercent = 20; public const int LoserLossPercent = 50; private readonly int[] power; public BattleSystem() : this(DefaultPower) { } public BattleSystem(int[] unitPower) { power = new int[TroopType.Count]; for (int i = 0; i < TroopType.Count; i++) power[i] = unitPower != null && i < unitPower.Length && unitPower[i] > 0 ? unitPower[i] : DefaultPower[i]; } // Контра: насколько тип attackerType сильнее против defenderType, в процентах public static int Counter(int attackerType, int defenderType) { if (attackerType == TroopType.Infantry && defenderType == TroopType.Cavalry) return CounterPercent; if (attackerType == TroopType.Cavalry && defenderType == TroopType.Archers) return CounterPercent; if (attackerType == TroopType.Archers && defenderType == TroopType.Infantry) return CounterPercent; return 0; } public BattleOutcome Resolve(BattleRequest request) { int[] att = Units(request.AttackerUnits, request.Attackers); int[] def = Units(request.DefenderUnits, request.Defenders); long attPower = SidePower(att, def, attacking: true, request.DefenderFortified, request.AttackerBonusPercent); long defPower = SidePower(def, att, attacking: false, request.DefenderFortified, request.DefenderBonusPercent + (request.DefenderFortified ? WallsPercent : 0)); bool victory = attPower > defPower; // ничья -- в пользу защитника // Потери в процентах: проигравший -- половина; победитель -- половина x (сила проигравшего / сила победителя) long stronger = Math.Max(attPower, defPower); long weaker = Math.Min(attPower, defPower); int[] attLost = Losses(att, weaker, stronger, isLoser: !victory); int[] defLost = Losses(def, weaker, stronger, isLoser: victory); var survivors = new int[TroopType.Count]; for (int i = 0; i < TroopType.Count; i++) survivors[i] = att[i] - attLost[i]; return new BattleOutcome { Victory = victory, AttackerLosses = Sum(attLost), DefenderLosses = Sum(defLost), Survivors = Sum(survivors), AttackerPower = attPower, DefenderPower = defPower, AttackerSurvivorUnits = survivors, DefenderLossUnits = defLost }; } // Сила стороны в сотых долях (x100), чтобы проценты считались в целых числах long SidePower(int[] units, int[] enemy, bool attacking, bool fortified, int bonusPercent) { int enemyTotal = Sum(enemy); long total = 0; for (int i = 0; i < TroopType.Count; i++) { if (units[i] <= 0) continue; long counterSum = 0; for (int j = 0; j < TroopType.Count; j++) counterSum += (long)Counter(i, j) * enemy[j]; long counterPercent = enemyTotal > 0 ? counterSum / enemyTotal : 0; int siegePercent = 0; if (i == TroopType.Siege) siegePercent = attacking && fortified ? SiegeVsWallsPercent : SiegeInFieldPercent; total += (long)units[i] * power[i] * (100 + counterPercent + siegePercent); } return Math.Max(0, total * (100 + bonusPercent) / 100); } // Потери каждого типа: проигравший -- LoserLossPercent; победитель -- LoserLossPercent x ratioNum / ratioDen static int[] Losses(int[] units, long ratioNum, long ratioDen, bool isLoser) { var lost = new int[TroopType.Count]; for (int i = 0; i < TroopType.Count; i++) { if (units[i] <= 0) continue; long l = isLoser ? (long)units[i] * LoserLossPercent / 100 : ratioDen > 0 ? (long)units[i] * LoserLossPercent * ratioNum / (100 * ratioDen) : 0; lost[i] = (int)Math.Min(units[i], l); } return lost; } static int[] Units(int[] units, int count) { var result = new int[TroopType.Count]; if (units != null) { for (int i = 0; i < TroopType.Count && i < units.Length; i++) result[i] = Math.Max(0, units[i]); } else { result[TroopType.Infantry] = Math.Max(0, count); // состав неизвестен -- считаем пехотой } return result; } static int Sum(int[] values) { int n = 0; foreach (var v in values) n += v; return n; } }