ООП вариант “Босс атаукет”

Попробуйте изменить код программы, который был представлен как решение по задаче “Избавьтесь от дублирующегося кода “Босс атакует”” таким образом, чтобы можно было удобно добавлять новые виды атаки боссу, а также скройте внутри него алгоритм выбора следующей атаки.

При создание ООП варианта думайте о том, что вам нужно создать структуру данных, с которой можно будет целостно работать. А именно, чтобы нельзя было задать урон атаки, но забыть разместить текст или какое-то другое свойство атаки.

Хорошим маркером, что всё сдизайнено качественно является то, что вы можете добавлять в одной точке атаки боссу и программа подстраивается под набор атак. При этом добавление атак соответствует следующим критериям:

  • Оно очевидно
  • При добавление нельзя добавить некорректную атаку
  • Ошибка видна при написании кода, а не при выполнении

Решение:

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):

  • Получить следующую атаку
  • Вывести в консоль информацию об атаке
  • Применить атаку к игроку

Атака – это как раз те самые данные, на основе которых построена программа. Мы определили, что каждая атака состоит из:

  • Урона
  • Цвета
  • Сообщения

И в дальнейшем все наши классы оперирует термином Аттака, а не разрозненными не структурированными данными.

Игрок тоже не сложный. Он представляется двумя значениями: здоровье и броня, а также стратегией получения урона.

Если вы нашли ошибку, пожалуйста выделите её и нажмите Ctrl+Enter.


Leave a Reply

Your email address will not be published. Required fields are marked *