/
Laki
/
PPAGame
Обзор
Документация
Войти
/
Laki
/
PPAGame
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
Program.cs
570 строк
19 KB
Artemiy Ivanov
исправлен сдвиг индексов при стрельбе лучников
01 мар 2026, 19:38
01 мар 2026, 19:38
12f19be
Код
Авторство
О чём код?
using System; using System.Collections.Generic; using System.Linq; namespace ArmyBattleGame { // ---------------------- // Интерфейсы // ---------------------- interface IUnit { double Damage { get; } double Health { get; set; } int Shield { get; } double Cost { get; } bool IsAlive { get; } void Hit(IUnit target); } interface ISpecialAbility { // Выполнить специальную способность для юнита на позиции attackerIndex void UseSpecial(Army ownArmy, Army enemyArmy, int attackerIndex); } // ---------------------- // Базовый класс для удобства // ---------------------- abstract class BaseUnit : IUnit { public double Damage { get; protected set; } public double Health { get; set; } public int Shield { get; protected set; } public double Cost { get; protected set; } public bool IsAlive => Health > 0; public void Hit(IUnit target) { double effectiveDamage = Damage * 100 / (100 + target.Shield); target.Health -= effectiveDamage; ConsoleHelper.Write($"{GetType().Name} атакует {target.GetType().Name} и наносит ", ConsoleColor.Gray); ConsoleHelper.Write($"{effectiveDamage:F1}", ConsoleColor.Red); ConsoleHelper.WriteLine(" урона", ConsoleColor.Gray); if (!target.IsAlive) { ConsoleHelper.WriteLine($"{target.GetType().Name} погиб!", ConsoleColor.DarkRed); } else { ConsoleHelper.Write($"{target.GetType().Name} осталось HP: ", ConsoleColor.Gray); ConsoleHelper.WriteLine($"{target.Health:F1}", ConsoleColor.Yellow); } } protected double CalculateCost() { return Health + Damage * 2 + Shield * 0.5; } } // ---------------------- // Конкретные юниты // ---------------------- class HeavyUnit : BaseUnit { public HeavyUnit() { Health = 150; Damage = 20; Shield = 80; Cost = CalculateCost(); } } class LiteUnit : BaseUnit { public LiteUnit() { Health = 80; Damage = 40; Shield = 20; Cost = CalculateCost(); } } class ArcherUnit : BaseUnit, ISpecialAbility { // Range и Power задаются один раз при создании public int Range { get; } public double Power { get; } public ArcherUnit(int range, double power) { // базовые параметры ближнего боя (можно скорректировать) Health = 90; Damage = 15; // рукопашный урон Shield = 10; Range = range; Power = power; // Базовая стоимость double baseCost = CalculateCost(); // Финальная стоимость с учетом дальности и силы Cost = baseCost + Power * 1.5 // вклад силы выстрела + Range * 5; // стратегическая ценность дальности } // Специальная способность: выстрел (стрела наносит Power урона с учётом щита цели) // Специальная способность: выстрел (стрела наносит Power урона с учётом щита цели) public void UseSpecial(Army ownArmy, Army enemyArmy, int attackerIndex) { // Если юнит мёртв — ничего не делаем if (!IsAlive) return; // Вычисляем целевой индекс по формуле: distance = attackerIndex + defenderIndex // попадание если distance == Range => defenderIndex = Range - attackerIndex int targetIndex = Range - attackerIndex; if (targetIndex < 0) { Console.WriteLine($"{GetType().Name} (поз {attackerIndex}) не может достать — Range={Range}"); return; } if (!enemyArmy.HasAliveUnits) return; // Проверяем, жив ли ещё юнит на целевой позиции if (targetIndex >= enemyArmy.Units.Count || !enemyArmy.Units[targetIndex].IsAlive) { // Если целевой юнит мёртв, ищем ближайшего живого слева for (int j = enemyArmy.Units.Count - 1; j >= 0; j--) { if (enemyArmy.Units[j].IsAlive) { targetIndex = j; break; } } } IUnit target = enemyArmy.Units[targetIndex]; // Наносим урон стрелой double effectiveDamage = Power * 100 / (100 + target.Shield); target.Health -= effectiveDamage; ConsoleHelper.Write($"{GetType().Name} (поз {attackerIndex}) стреляет по {target.GetType().Name} (поз {targetIndex}) и наносит ", ConsoleColor.Gray); ConsoleHelper.Write($"{effectiveDamage:F1}", ConsoleColor.Red); ConsoleHelper.WriteLine(" урона", ConsoleColor.Gray); if (!target.IsAlive) { ConsoleHelper.WriteLine($"{target.GetType().Name} погиб!", ConsoleColor.DarkRed); // НЕ УДАЛЯЕМ ЗДЕСЬ! Только помечаем как мёртвого } else { ConsoleHelper.Write($"{target.GetType().Name} осталось HP: ", ConsoleColor.Gray); ConsoleHelper.WriteLine($"{target.Health:F1}", ConsoleColor.Yellow); } } } // ---------------------- // Армия // ---------------------- class Army { public List<IUnit> Units { get; } = new List<IUnit>(); public bool HasAliveUnits => Units.Any(u => u.IsAlive); public IUnit? FrontUnit => Units.FirstOrDefault(); public double TotalCost => Units.Sum(u => u.Cost); public void RemoveDead() { // Более эффективный способ удаления всех мёртвых за один проход Units.RemoveAll(u => !u.IsAlive); } public void PrintState(string name) { ConsoleHelper.WriteLine(name + ":", name.Contains("1") ? ConsoleColor.Cyan : ConsoleColor.Magenta); for (int i = 0; i < Units.Count; i++) { var u = Units[i]; string extra = ""; if (u is ArcherUnit a) extra = $" | Range: {a.Range} | Power: {a.Power:F1}"; Console.WriteLine($" {i + 1}. {u.GetType().Name} | HP: {u.Health:F1} | Shield: {u.Shield}{extra}"); } } } // ---------------------- // Поле битвы // ---------------------- class BattleField { public Army Army1 { get; } public Army Army2 { get; } private bool isArmy1Turn = true; private int turnCounter = 1; public BattleField(Army army1, Army army2) { Army1 = army1; Army2 = army2; } public bool MakeMove() { if (!Army1.HasAliveUnits || !Army2.HasAliveUnits) return false; ConsoleHelper.PushScreen(); ConsoleHelper.WriteLine($"===== Ход {turnCounter} =====", ConsoleColor.White); // ---------------- // ФАЗА 1: рукопашный бой (удар-ответ) // ---------------- Army attackerArmy = isArmy1Turn ? Army1 : Army2; Army defenderArmy = isArmy1Turn ? Army2 : Army1; IUnit? attacker = attackerArmy.FrontUnit; IUnit? defender = defenderArmy.FrontUnit; if (attacker != null && defender != null) { attacker.Hit(defender); defenderArmy.RemoveDead(); } // ответный удар, если защитник ещё жив if (defenderArmy.HasAliveUnits && attackerArmy.HasAliveUnits) { // пересчитываем фронты defender = defenderArmy.FrontUnit; attacker = attackerArmy.FrontUnit; if (defender != null && attacker != null) { defender.Hit(attacker); attackerArmy.RemoveDead(); } } // чередование первого удара isArmy1Turn = !isArmy1Turn; // Если после рукопашной одна армия пала — бой закончился if (!Army1.HasAliveUnits || !Army2.HasAliveUnits) return false; // ---------------- // ФАЗА 2: специальные способности (лучники) // Опрос по позиции: сначала армия1[i], потом армия2[i], увеличивая i. // Удаление мёртвых производится только после всей фазы! // ---------------- // Сначала применяем все специальные способности int i = 0; while (i < Army1.Units.Count || i < Army2.Units.Count) { // Армия 1, позиция i if (i < Army1.Units.Count) { var unit = Army1.Units[i]; if (unit is ISpecialAbility special && unit.IsAlive) { special.UseSpecial(Army1, Army2, i); } } // Армия 2, позиция i if (i < Army2.Units.Count) { var unit = Army2.Units[i]; if (unit is ISpecialAbility special && unit.IsAlive) { special.UseSpecial(Army2, Army1, i); } } i++; } // Только после всех выстрелов удаляем мёртвых Army1.RemoveDead(); Army2.RemoveDead(); turnCounter++; return Army1.HasAliveUnits && Army2.HasAliveUnits; } public void FightToEnd() { while (MakeMove()) { } DeclareWinner(); // используем общий метод } // метод для объявления победителя после окончания боя public void DeclareWinner() { Console.WriteLine(); Console.WriteLine("===== БОЙ ОКОНЧЕН ====="); if (Army1.HasAliveUnits) Console.WriteLine("Победила Армия 1!\n"); else if (Army2.HasAliveUnits) Console.WriteLine("Победила Армия 2!\n"); else Console.WriteLine("Ничья! Все погибли!\n"); } } // ---------------------- // Игра // ---------------------- class Game { private Army army1 = new Army(); private Army army2 = new Army(); private BattleField? battle; private double armyBudget; private readonly Random _rnd = new Random(); public void Run() { while (true) { Console.WriteLine("1. Начать игру"); Console.WriteLine("2. Выход"); string? input = Console.ReadLine(); if (input == "2") return; if (input == "1") StartGame(); } } private void StartGame() { army1 = new Army(); army2 = new Army(); Console.Write("Введите стоимость армий: "); if (!double.TryParse(Console.ReadLine(), out armyBudget)) { Console.WriteLine("Неверное число"); return; } CreateArmyMenu(army1, "Армия 1"); CreateArmyMenu(army2, "Армия 2"); battle = new BattleField(army1, army2); BattleMenu(); } private void CreateArmyMenu(Army army, string name) { while (true) { Console.WriteLine(); Console.WriteLine($"{name}:"); Console.WriteLine("1. Создать вручную"); Console.WriteLine("2. Создать рандомно"); string? input = Console.ReadLine(); if (input == "1") { CreateArmyManual(army); return; } if (input == "2") { CreateArmyRandom(army); return; } } } private void CreateArmyManual(Army army) { double currentCost = 0; while (currentCost < armyBudget) { Console.WriteLine($"Текущая стоимость: {currentCost:F1}/{armyBudget}"); Console.WriteLine($"1. HeavyUnit ({new HeavyUnit().Cost})"); Console.WriteLine($"2. LiteUnit ({new LiteUnit().Cost})"); Console.WriteLine($"3. Archer (рандомные Range и Power)"); Console.WriteLine("0. Завершить"); string? input = Console.ReadLine(); if (input == "0") break; IUnit? unit = null; if (input == "1") unit = new HeavyUnit(); else if (input == "2") unit = new LiteUnit(); else if (input == "3") { int range = _rnd.Next(1, 7); // от 1 до 6 включительно double power = _rnd.Next(15, 51); // от 15 до 50 включительно unit = new ArcherUnit(range, power); Console.WriteLine($"Создан лучник: Range={range}, Power={power:F1}"); } if (unit == null) continue; if (currentCost + unit.Cost > armyBudget) { Console.WriteLine("Недостаточно бюджета!"); continue; } army.Units.Add(unit); currentCost += unit.Cost; } } private void CreateArmyRandom(Army army) { double currentCost = 0; while (true) { double remaining = armyBudget - currentCost; var availableUnits = new List<IUnit>(); var heavy = new HeavyUnit(); var lite = new LiteUnit(); if (heavy.Cost <= remaining) availableUnits.Add(heavy); if (lite.Cost <= remaining) availableUnits.Add(lite); // Лучника генерируем отдельно int range = _rnd.Next(1, 7); double power = _rnd.Next(15, 51); var archer = new ArcherUnit(range, power); if (archer.Cost <= remaining) availableUnits.Add(archer); if (availableUnits.Count == 0) break; // ничего уже купить нельзя var chosen = availableUnits[_rnd.Next(availableUnits.Count)]; army.Units.Add(chosen); currentCost += chosen.Cost; } } private void BattleMenu() { while (true) { Console.WriteLine(); Console.WriteLine("1. Сделать один ход"); Console.WriteLine("2. Доиграть бой до конца"); Console.WriteLine("3. Вывести состояние"); Console.WriteLine("4. Выход"); string? input = Console.ReadLine(); if (input == "4") return; if (input == "1") { if (battle != null) { if (!battle.MakeMove()) { battle.DeclareWinner(); // показываем победителя return; // возвращаемся в главное меню } } } if (input == "2") { if (battle != null) { battle.FightToEnd(); return; } } if (input == "3") { if (battle != null) { ConsoleHelper.PushScreen(); battle.Army1.PrintState("Армия 1"); battle.Army2.PrintState("Армия 2"); } } } } } // ---------------------- // Красивый вывод в консоли // ---------------------- static class ConsoleHelper { public static void Write(string text, ConsoleColor color) { var prev = Console.ForegroundColor; Console.ForegroundColor = color; Console.Write(text); Console.ForegroundColor = prev; } public static void WriteLine(string text, ConsoleColor color) { var prev = Console.ForegroundColor; Console.ForegroundColor = color; Console.WriteLine(text); Console.ForegroundColor = prev; } public static void PushScreen() { int height = Console.WindowHeight; for (int i = 0; i < height - 2; i++) Console.WriteLine(); } } // ---------------------- // Program // ---------------------- class Program { static void Main() { Game game = new Game(); game.Run(); } } }