<-Back

Патерн «Стратегія» на прикладі гри RPG

Постановка задачі

Ми створюємо консольну гру, де герої можуть атакувати по-різному. Щоб не дублювати код та легко змінювати поведінку персонажів, застосовуємо патерн «Стратегія».

У проекті є такі класи:

Герой (Context) використовує вибрану стратегію атаки (Strategy). Легко замінювати стратегію під час гри.

Таблиця стратегій персонажів

Стратегія Опис Критичний ефект Візуальні ефекти
Меч Фізична атака ближнього бою Подвійна шкода Мерехтіння червоно-жовто-фіолетове, звук ⚔️
Магія Заклинання середньої шкоди Подвійна шкода + вибух магії Мерехтіння фіолетово-блакитно-жовте, звук ✨
Лук Дальнобійна атака Подвійна шкода + точний постріл Мерехтіння жовто-зелено-блакитне, звук 🏹

Схема паттерна Стратегія

Strategy Pattern UML

Елементи шаблону

  1. IStrategy — стратегія: описує загальний для всіх алгоритмів інтерфейс.
  2. Context — контекст, який використовує цей інтерфейс для виклику конкретного алгоритму, визначеного в ConcreteStrategy.
  3. ConcreteStrategy — конкретна стратегія: реалізує алгоритм, що описаний в інтерфейсі IStrategy.
  4. Context:
    1. налаштовується об’єктом класу ConcreteStrategy;
    2. зберігає посилання на IStrategy;
    3. може надавати інтерфейс для доступу стратегій до даних контексту.

Пояснення: Context (персонаж) використовує інтерфейс Strategy для виконання дії. Реалізації Strategy визначають різні стилі атаки.

Повна версія C# з ефектами (beep, flash)

using System;
using System.Threading;

// Інтерфейс стратегії атаки
public interface IAttackStrategy
{
    int Attack(string attackerName);
}

// Стратегія: меч
public class SwordAttack : IAttackStrategy
{
    public int Attack(string attackerName)
    {
        int baseDamage = new Random().Next(15, 26);
        bool critical = new Random().Next(1, 5) == 1;
        int damage = critical ? baseDamage * 2 : baseDamage;

        if (critical)
            SuperAttackEffect($"{attackerName} ⚔️ КРИТИЧНИЙ МЕЧ! Завдано {damage} шкоди!");
        else
            NormalAttackEffect($"{attackerName} ⚔️ атакує мечем і завдає {damage} шкоди.");

        return damage;
    }

    private void SuperAttackEffect(string message)
    {
        ConsoleColor[] colors = { ConsoleColor.Red, ConsoleColor.Yellow, ConsoleColor.Magenta };
        for (int i = 0; i < 5; i++)
        {
            Console.BackgroundColor = colors[i % colors.Length];
            Console.Clear();
            Thread.Sleep(100);
        }
        Console.ResetColor();

        foreach (char c in message)
        {
            Console.ForegroundColor = colors[new Random().Next(colors.Length)];
            Console.Write(c);
            Thread.Sleep(20);
        }
        Console.WriteLine();

        Console.Beep(800, 150);
        Console.Beep(1000, 150);
        Console.Beep(1200, 150);

        Console.BackgroundColor = ConsoleColor.Black;
        Console.Clear();
        Console.ResetColor();
    }

    private void NormalAttackEffect(string message)
    {
        Console.ForegroundColor = ConsoleColor.White;
        Console.WriteLine(message);
        Console.Beep(600, 100);
        Console.ResetColor();
    }
}

// Стратегія: магія
public class MagicAttack : IAttackStrategy
{
    public int Attack(string attackerName)
    {
        int baseDamage = new Random().Next(10, 31);
        bool critical = new Random().Next(1, 5) == 1;
        int damage = critical ? baseDamage * 2 : baseDamage;

        if (critical)
            SuperAttackEffect($"{attackerName} ✨ КРИТИЧНА МАГІЯ! Завдано {damage} шкоди!");
        else
            NormalAttackEffect($"{attackerName} ✨ атакує магією і завдає {damage} шкоди.");

        return damage;
    }

    private void SuperAttackEffect(string message)
    {
        ConsoleColor[] colors = { ConsoleColor.Magenta, ConsoleColor.Cyan, ConsoleColor.Yellow };
        for (int i = 0; i < 5; i++)
        {
            Console.BackgroundColor = colors[i % colors.Length];
            Console.Clear();
            Thread.Sleep(100);
        }
        Console.ResetColor();

        foreach (char c in message)
        {
            Console.ForegroundColor = colors[new Random().Next(colors.Length)];
            Console.Write(c);
            Thread.Sleep(20);
        }
        Console.WriteLine();

        Console.Beep(900, 150);
        Console.Beep(1200, 150);
        Console.Beep(1500, 150);

        Console.BackgroundColor = ConsoleColor.Black;
        Console.Clear();
        Console.ResetColor();
    }

    private void NormalAttackEffect(string message)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        Console.WriteLine(message);
        Console.Beep(700, 100);
        Console.ResetColor();
    }
}

// Стратегія: лук
public class BowAttack : IAttackStrategy
{
    public int Attack(string attackerName)
    {
        int baseDamage = new Random().Next(12, 28);
        bool critical = new Random().Next(1, 5) == 1;
        int damage = critical ? baseDamage * 2 : baseDamage;

        if (critical)
            SuperAttackEffect($"{attackerName} 🏹 КРИТИЧНИЙ УДАР ЛУКОМ! Завдано {damage} шкоди!");
        else
            NormalAttackEffect($"{attackerName} 🏹 стріляє з лука і завдає {damage} шкоди.");

        return damage;
    }

    private void SuperAttackEffect(string message)
    {
        ConsoleColor[] colors = { ConsoleColor.Yellow, ConsoleColor.Green, ConsoleColor.Cyan };
        for (int i = 0; i < 5; i++)
        {
            Console.BackgroundColor = colors[i % colors.Length];
            Console.Clear();
            Thread.Sleep(100);
        }
        Console.ResetColor();

        foreach (char c in message)
        {
            Console.ForegroundColor = colors[new Random().Next(colors.Length)];
            Console.Write(c);
            Thread.Sleep(20);
        }
        Console.WriteLine();

        Console.Beep(800, 120);
        Console.Beep(1000, 120);
        Console.Beep(1200, 120);

        Console.BackgroundColor = ConsoleColor.Black;
        Console.Clear();
        Console.ResetColor();
    }

    private void NormalAttackEffect(string message)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine(message);
        Console.Beep(650, 100);
        Console.ResetColor();
    }
}

// Клас персонажа
public class GameCharacter
{
    public string Name { get; set; }
    public int HP { get; set; } = 100;
    private IAttackStrategy attackStrategy;

    public GameCharacter(string name, IAttackStrategy strategy)
    {
        Name = name;
        attackStrategy = strategy;
    }

    public void SetStrategy(IAttackStrategy strategy)
    {
        attackStrategy = strategy;
        Console.WriteLine($"\n{Name} змінив стиль атаки!\n");
    }

    public void Attack(GameCharacter target)
    {
        int damage = attackStrategy.Attack(Name);
        target.HP -= damage;
        if (target.HP <0) target.HP = 0;

        AnimateHP(target);
        CheckSpecial(target);
    }

    public bool IsAlive() => HP > 0;

    private void AnimateHP(GameCharacter target)
    {
        int totalBars = 20;
        int filledBars = (int)((target.HP / 100.0) * totalBars);

        Console.Write($"{target.Name} HP: [");
        for (int i = 0; i < totalBars; i++)
        {
            if (i < filledBars)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.Write("█");
            }
            else
            {
                Console.ForegroundColor = ConsoleColor.DarkRed;
                Console.Write("░");
            }
        }
        Console.ResetColor();
        Console.WriteLine($"] {target.HP}/100\n");
        Thread.Sleep(300);
    }

    private void CheckSpecial(GameCharacter target)
    {
        if (target.HP > 0 && target.HP <= 20)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"⚠️ {target.Name} в небезпечному стані! ⚠️\n");
            Console.ResetColor();
        }
    }
}

// Програма
class Program
{
    static void Main()
    {
        GameCharacter hero = new GameCharacter("Герой", new SwordAttack());
        GameCharacter enemy = new GameCharacter("Монстр", new MagicAttack());

        Console.WriteLine("=== Інтерактивна RPG гра з суперефектами та звуком ===\n");

        int round = 1;
        while (hero.IsAlive() && enemy.IsAlive())
        {
            Console.WriteLine($"--- Раунд {round} ---\n");

            // Вибір стратегії для Героя
            Console.WriteLine("Виберіть стратегію для Героя: 1-Меч, 2-Магія, 3-Лук, 0-Вихід з гри");
            string choiceHero = Console.ReadLine();
            if (choiceHero == "0") break;
            hero.SetStrategy(GetStrategy(choiceHero));

            // Вибір стратегії для Монстра
            Console.WriteLine("Виберіть стратегію для Монстра: 1-Меч, 2-Магія, 3-Лук");
            string choiceEnemy = Console.ReadLine();
            enemy.SetStrategy(GetStrategy(choiceEnemy));

            // Атаки
            hero.Attack(enemy);
            if (!enemy.IsAlive()) break;

            enemy.Attack(hero);
            if (!hero.IsAlive()) break;

            round++;
        }

        Console.ForegroundColor = ConsoleColor.Yellow;
        if (hero.IsAlive() && enemy.IsAlive())
            Console.WriteLine("\nГра завершена достроково!");
        else if (hero.IsAlive())
            Console.WriteLine("\n🎉 Герой переміг!");
        else
            Console.WriteLine("\n💀 Монстр переміг!");
        Console.ResetColor();

        Console.WriteLine("\nДякуємо за гру!");
    }

    static IAttackStrategy GetStrategy(string choice)
    {
        switch (choice)
        {
            case "1": return new SwordAttack();
            case "2": return new MagicAttack();
            case "3": return new BowAttack();
            default: return new SwordAttack();
        }
    }
}

Демонстрація JS: двоє героїв

Обирайте стратегію для Героя 1 і Героя 2, щоб атакувати один одного:

100
100