Попробуйте изменить код программы, который был представлен как решение по задаче “Избавьтесь от дублирующегося кода “Босс атакует”” таким образом, чтобы можно было удобно добавлять новые виды атаки боссу, а также скройте внутри него алгоритм выбора следующей атаки.
Хорошим маркером, что всё сдизайнено качественно является то, что вы можете добавлять в одной точке атаки боссу и программа подстраивается под набор атак. При этом добавление атак соответствует следующим критериям:
- Оно очевидно
- При добавление нельзя добавить некорректную атаку
- Ошибка видна при написании кода, а не при выполнении
Решение:
using System;
using System.Collections;
using System.Collections.Generic;
namespace Boss
{
class Program
{
static void Main(string[] args)
{
bool isRandomAttack = GetRandomFromZeroTo(2) == 0;
Attack[] attacks =
{
new Attack("Босс атаковал с немыслимой яростью своими руками", ConsoleColor.DarkRed, 100),
new Attack("Босс исполнил новый альбом Ольги бузовой", ConsoleColor.DarkMagenta, 140),
new Attack("Босс приуныл и рассказал вам о своём долгом пути и дал пару советов, после выпил ритуальный стопарь боярки", ConsoleColor.DarkGray, 80)
};
Player player = new Player();
Boss boss = new Boss(isRandomAttack ? (IEnumerator<Attack>)new RandomAttackPicker(attacks) : (IEnumerator<Attack>)new SerialAttackPicker(attacks));
WriteColoredLine("Босс может атаковать в двух режимах: все атаки по очереди и случайной атакой", ConsoleColor.Yellow);
WriteColoredLine("Босс будет атаковать: " + (isRandomAttack ? "случайно" : "все атаки по очереди"), ConsoleColor.Yellow);
WriteColoredLine("Нажмите enter для начала боя", ConsoleColor.Green);
Console.ReadLine();
while (player.Health > 0)
{
Console.Clear();
WriteColoredLine("У вас здоровья: " + player.Health, ConsoleColor.Red);
boss.Attack(player);
Thread.Sleep(4000);
}
WriteColoredLine("Бой закончен, вы погибли", ConsoleColor.DarkGray);
}
public static int GetRandomFromZeroTo(int max)
{
return DateTime.Now.Millisecond % 3;
}
public static void WriteColoredLine(string message, ConsoleColor color)
{
var oldColor = Console.ForegroundColor;
Console.ForegroundColor = color;
Console.WriteLine(message);
Console.ForegroundColor = oldColor;
}
}
class Attack
{
public Attack(string message, ConsoleColor color, int damage)
{
Damage = damage;
Color = color;
Message = message;
}
public int Damage { get; private set; }
public ConsoleColor Color { get; private set; }
public string Message { get; private set; }
}
class Boss
{
private IEnumerator<Attack> _attacks;
public Boss(IEnumerator<Attack> attacks)
{
_attacks = attacks;
}
public void Attack(Player player)
{
_attacks.MoveNext();
var attack = _attacks.Current;
Program.WriteColoredLine(attack.Message, attack.Color);
player.ApplyDamage(attack.Damage);
}
}
class Player
{
public int Health { get; private set; } = 1000;
public int Armor { get; private set; } = 20;
public void ApplyDamage(int damage)
{
Health -= damage - Armor;
}
}
class RandomAttackPicker : IEnumerator<Attack>
{
private Attack[] _attacks;
private int _position = -1;
public RandomAttackPicker(Attack[] attacks)
{
_attacks = attacks;
}
public Attack Current => _attacks[_position];
object IEnumerator.Current => Current;
public void Dispose() { }
public bool MoveNext()
{
_position = Program.GetRandomFromZeroTo(_attacks.Length);
return true;
}
public void Reset()
{
_position = 0;
}
}
class SerialAttackPicker : IEnumerator<Attack>
{
private Attack[] _attacks;
private int _position = -1;
public SerialAttackPicker(Attack[] attacks)
{
_attacks = attacks;
}
public Attack Current => _attacks[_position];
object IEnumerator.Current => Current;
public void Dispose() { }
public bool MoveNext()
{
_position++;
if (_position >= _attacks.Length)
_position = -1;
return true;
}
public void Reset()
{
_position = 0;
}
}
}
Как это работает
Мы выделили 4 основных сущности системы:
- Босс
- Атака
- Последовательность атак
- Игрок
Босс отвечает за то, чтобы наносить атаку по игроку. Задачу определения последовательности он делегирует сущности, которая прячется под интерфейсом IEnumerator. С помощью него мы просто можем брать атаки по очереди, при этом эта очередь скрыта в реализации интерфейса. В одном случае у нас рандомная последовательности, а в другом просто ходим по кругу, по списку от одной атаки к другой. В самом боссе определена следующая стратегия нанесения урона (метод Attack):
- Получить следующую атаку
- Вывести в консоль информацию об атаке
- Применить атаку к игроку
Атака – это как раз те самые данные, на основе которых построена программа. Мы определили, что каждая атака состоит из:
- Урона
- Цвета
- Сообщения
И в дальнейшем все наши классы оперирует термином Аттака, а не разрозненными не структурированными данными.
Игрок тоже не сложный. Он представляется двумя значениями: здоровье и броня, а также стратегией получения урона.