Код IT
← Каталог

Объектно-ориентированное программирование в Java — Пример простейшей игры на Java

Фрагмент из «Объектно-ориентированное программирование в Java»: Пример простейшей игры на Java.

Java main.java
package com.test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BattleGame extends JFrame {
    private Unit player;
    private Unit enemy;
    private Random random = new Random();

    private JLabel playerHealthLabel;
    private JLabel enemyHealthLabel;
    private JLabel playerManaLabel;
    private JLabel enemyManaLabel;
    private JLabel playerExpLabel;
    private JLabel enemyNameLabel;

    private JTextArea battleLog;
    private JPanel abilitiesPanel;

    public BattleGame() {
        setTitle("Битва героев: Фэнтези РПГ");
        setSize(850, 650);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        player = createUnitByClass("Воин", Unit.ClassType.WARRIOR);
        enemy = createUnitByClass("Тёмный Маг", Unit.ClassType.MAGE);

        JPanel statsPanel = new JPanel(new GridLayout(1, 2, 20, 10));
        statsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        statsPanel.add(createPlayerStatsPanel());
        statsPanel.add(createEnemyStatsPanel());
        add(statsPanel, BorderLayout.NORTH);

        battleLog = new JTextArea();
        battleLog.setEditable(false);
        battleLog.setFont(new Font("Monospaced", Font.PLAIN, 13));
        battleLog.setBackground(new Color(240, 240, 240));
        JScrollPane logScrollPane = new JScrollPane(battleLog);
        logScrollPane.setBorder(BorderFactory.createTitledBorder("Хроники битвы"));
        add(logScrollPane, BorderLayout.CENTER);

        JPanel controlPanel = new JPanel(new BorderLayout(10, 10));
        controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        abilitiesPanel = new JPanel();
        abilitiesPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        updateAbilitiesPanel();
        controlPanel.add(abilitiesPanel, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        JButton newBattleBtn = new JButton("Новая битва");
        newBattleBtn.addActionListener(e -> startNewBattle());
        buttonPanel.add(newBattleBtn);

        controlPanel.add(buttonPanel, BorderLayout.SOUTH);
        add(controlPanel, BorderLayout.SOUTH);

        logMessage("⚔️ Битва началась! " + player.name + " (" + player.classType.getName() + ") против " + enemy.name);
        logMessage("💡 Совет: Используйте способности для тактического преимущества!");
    }

    private JPanel createPlayerStatsPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Игрок: " + player.name + " (" + player.classType.getName() + ")"));

        JPanel statsGrid = new JPanel(new GridLayout(5, 2, 5, 5));
        statsGrid.add(new JLabel("Уровень:"));
        statsGrid.add(new JLabel(String.valueOf(player.level)));

        statsGrid.add(new JLabel("Здоровье:"));
        playerHealthLabel = new JLabel(player.health + "/" + player.maxHealth);
        statsGrid.add(playerHealthLabel);

        statsGrid.add(new JLabel("Мана:"));
        playerManaLabel = new JLabel(player.mana + "/" + player.maxMana);
        statsGrid.add(playerManaLabel);

        statsGrid.add(new JLabel("Опыт:"));
        playerExpLabel = new JLabel(player.experience + "/" + player.nextLevelExp);
        statsGrid.add(playerExpLabel);

        statsGrid.add(new JLabel("Экипировка:"));
        statsGrid.add(new JLabel(getEquipmentString(player)));

        panel.add(statsGrid, BorderLayout.CENTER);
        return panel;
    }

    private JPanel createEnemyStatsPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Противник"));

        JPanel statsGrid = new JPanel(new GridLayout(4, 2, 5, 5));
        enemyNameLabel = new JLabel(enemy.name + " (" + enemy.classType.getName() + ")");
        statsGrid.add(new JLabel("Имя:"));
        statsGrid.add(enemyNameLabel);

        statsGrid.add(new JLabel("Здоровье:"));
        enemyHealthLabel = new JLabel(enemy.health + "/" + enemy.maxHealth);
        statsGrid.add(enemyHealthLabel);

        statsGrid.add(new JLabel("Мана:"));
        enemyManaLabel = new JLabel(enemy.mana + "/" + enemy.maxMana);
        statsGrid.add(enemyManaLabel);

        statsGrid.add(new JLabel("Экипировка:"));
        statsGrid.add(new JLabel(getEquipmentString(enemy)));

        panel.add(statsGrid, BorderLayout.CENTER);
        return panel;
    }

    private String getEquipmentString(Unit unit) {
        String weapon = unit.weapon != null ? unit.weapon.name : "— ";
        String armor = unit.armor != null ? unit.armor.name : "— ";
        return "Оружие: " + weapon + " | Доспехи: " + armor;
    }

    private void updateAbilitiesPanel() {
        abilitiesPanel.removeAll();

        for (Ability ability : player.abilities) {
            JButton btn = new JButton(ability.name + " (" + ability.manaCost + " MP)");
            btn.setEnabled(player.mana >= ability.manaCost && player.health > 0 && enemy.health > 0);
            final Ability selectedAbility = ability;
            btn.addActionListener(e -> executePlayerTurn(selectedAbility));
            abilitiesPanel.add(btn);
        }

        JButton basicAttackBtn = new JButton("Базовая атака");
        basicAttackBtn.setEnabled(player.health > 0 && enemy.health > 0);
        basicAttackBtn.addActionListener(e -> executePlayerTurn(null));
        abilitiesPanel.add(basicAttackBtn);

        abilitiesPanel.revalidate();
        abilitiesPanel.repaint();
    }

    private void executePlayerTurn(Ability ability) {
        if (player.health <= 0 || enemy.health <= 0) return;

        int damage = ability != null ? calculateAbilityDamage(player, enemy, ability) : calculatePhysicalDamage(player, enemy);
        String actionName = ability != null ? ability.name : "базовая атака";

        if (calculateDodgeChance(enemy)) {
            logMessage(String.format("💨 %s уклонился от %s %s!", enemy.name, actionName, player.name));
        } else {
            if (calculateBlockChance(enemy)) {
                int blocked = (int)(damage * 0.6);
                damage -= blocked;
                logMessage(String.format("🛡️ %s заблокировал %d урона!", enemy.name, blocked));
            }
            enemy.health = Math.max(0, enemy.health - damage);
            logMessage(String.format("💥 %s нанёс %d урона %s (%s)", player.name, damage, enemy.name, actionName));
        }

        if (ability != null) {
            player.mana -= ability.manaCost;
        }

        updateUI();

        if (enemy.health <= 0) {
            enemy.health = 0;
            int expGain = 50 + enemy.level * 25;
            player.gainExperience(expGain);
            logMessage(String.format("🎉 %s победил! Получено %d опыта", player.name, expGain));
            if (player.checkLevelUp()) {
                logMessage(String.format("✨ %s достиг %d уровня! Характеристики улучшены.", player.name, player.level));
            }
            disableControls();
            return;
        }

        Timer timer = new Timer(800, e -> {
            executeEnemyTurn();
            updateUI();
        });
        timer.setRepeats(false);
        timer.start();
    }

    private void executeEnemyTurn() {
        if (player.health <= 0 || enemy.health <= 0) return;

        Ability enemyAbility = null;
        if (enemy.classType == Unit.ClassType.MAGE && enemy.mana >= 20 && random.nextInt(100) < 60) {
            enemyAbility = enemy.abilities.get(0);
        }

        int damage = enemyAbility != null ? calculateAbilityDamage(enemy, player, enemyAbility) : calculatePhysicalDamage(enemy, player);
        String actionName = enemyAbility != null ? enemyAbility.name : "базовая атака";

        if (calculateDodgeChance(player)) {
            logMessage(String.format("💨 %s уклонился от %s %s!", player.name, actionName, enemy.name));
        } else {
            if (calculateBlockChance(player)) {
                int blocked = (int)(damage * 0.6);
                damage -= blocked;
                logMessage(String.format("🛡️ %s заблокировал %d урона!", player.name, blocked));
            }
            player.health = Math.max(0, player.health - damage);
            logMessage(String.format("💥 %s нанёс %d урона %s (%s)", enemy.name, damage, player.name, actionName));
        }

        if (enemyAbility != null) {
            enemy.mana -= enemyAbility.manaCost;
        }

        if (player.health <= 0) {
            player.health = 0;
            logMessage(String.format("☠️ %s пал в бою...", player.name));
            disableControls();
        }
    }

    private int calculatePhysicalDamage(Unit attacker, Unit defender) {
        int baseDamage = attacker.strength;
        if (attacker.weapon != null) baseDamage += attacker.weapon.damageBonus;
        int reduction = defender.armor != null ? defender.armor.damageReduction : 0;
        int finalDamage = (int)(baseDamage * (1 - reduction / 100.0)) + attacker.level;
        return Math.max(1, finalDamage); // Минимум 1 урона
    }

    private int calculateAbilityDamage(Unit caster, Unit target, Ability ability) {
        if (ability.type == Ability.Type.HEAL) {
            return Math.min(ability.power, caster.maxHealth - caster.health);
        }
        return caster.intel * 2 + ability.power + caster.level;
    }

    private boolean calculateDodgeChance(Unit unit) {
        int chance = Math.min(50, unit.agility / 2);
        return random.nextInt(100) < chance;
    }

    private boolean calculateBlockChance(Unit unit) {
        int chance = Math.min(40, (unit.strength + unit.agility) / 4);
        return random.nextInt(100) < chance;
    }

    private void updateUI() {
        playerHealthLabel.setText(player.health + "/" + player.maxHealth);
        enemyHealthLabel.setText(enemy.health + "/" + enemy.maxHealth);
        playerManaLabel.setText(player.mana + "/" + player.maxMana);
        enemyManaLabel.setText(enemy.mana + "/" + enemy.maxMana);
        playerExpLabel.setText(player.experience + "/" + player.nextLevelExp);
        enemyNameLabel.setText(enemy.name + " (" + enemy.classType.getName() + ")");
        updateAbilitiesPanel();
    }

    private void disableControls() {
        for (Component c : abilitiesPanel.getComponents()) {
            if (c instanceof JButton) ((JButton) c).setEnabled(false);
        }
    }

    private void startNewBattle() {
        Unit.ClassType[] classes = Unit.ClassType.values();
        Unit.ClassType playerClass = classes[random.nextInt(classes.length)];
        Unit.ClassType enemyClass = classes[random.nextInt(classes.length)];

        player = createUnitByClass("Герой", playerClass);
        enemy = createUnitByClass("Враг", enemyClass);

        battleLog.setText("");
        logMessage(String.format("⚔️ Новая битва! %s (%s) против %s (%s)",
                player.name, player.classType.getName(),
                enemy.name, enemy.classType.getName()));

        updateUI();
        for (Component c : abilitiesPanel.getComponents()) {
            if (c instanceof JButton) ((JButton) c).setEnabled(true);
        }
    }

    private Unit createUnitByClass(String name, Unit.ClassType type) {
        Unit unit = new Unit(name, type);

        switch (type) {
            case WARRIOR:
                unit.equipWeapon(new Weapon("Стальной меч", 10));
                unit.equipArmor(new Armor("Латы", 20));
                break;
            case MAGE:
                unit.equipWeapon(new Weapon("Волшебный посох", 4));
                unit.equipArmor(new Armor("Магическая роба", 8));
                break;
            case ARCHER:
                unit.equipWeapon(new Weapon("Композитный лук", 8));
                unit.equipArmor(new Armor("Кожаный доспех", 12));
                break;
            case ASSASSIN:
                unit.equipWeapon(new Weapon("Серебряные кинжалы", 7));
                unit.equipArmor(new Armor("Теневая мантия", 10));
                break;
        }

        return unit;
    }

    private void logMessage(String message) {
        battleLog.append(message + "\n");
        battleLog.setCaretPosition(battleLog.getDocument().getLength());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            BattleGame game = new BattleGame();
            game.setVisible(true);
        });
    }

    // ========== ВНУТРЕННИЕ КЛАССЫ ==========
    static class Unit {
        enum ClassType {
            WARRIOR("Воин"), MAGE("Маг"), ARCHER("Лучник"), ASSASSIN("Ассасин");
            private final String name;
            ClassType(String name) { this.name = name; }
            public String getName() { return name; }
        }

        String name;
        ClassType classType;
        int intel, agility, strength;
        int health, maxHealth;
        int mana, maxMana;
        int level;
        int experience, nextLevelExp;
        Weapon weapon;
        Armor armor;
        List<Ability> abilities = new ArrayList<>();

        public Unit(String name, ClassType classType) {
            this.name = name;
            this.classType = classType;
            this.level = 1;
            this.experience = 0;
            this.nextLevelExp = 100;

            switch (classType) {
                case WARRIOR:
                    this.strength = 28; this.agility = 14; this.intel = 8;
                    this.maxHealth = 130; this.maxMana = 45;
                    this.abilities.add(new Ability("Щитовой удар", Ability.Type.DAMAGE, 18, 20));
                    break;
                case MAGE:
                    this.strength = 7; this.agility = 12; this.intel = 32;
                    this.maxHealth = 85; this.maxMana = 110;
                    this.abilities.add(new Ability("Фаербол", Ability.Type.DAMAGE, 28, 25));
                    this.abilities.add(new Ability("Исцеление", Ability.Type.HEAL, 25, 35));
                    break;
                case ARCHER:
                    this.strength = 14; this.agility = 30; this.intel = 11;
                    this.maxHealth = 100; this.maxMana = 65;
                    this.abilities.add(new Ability("Прицельный выстрел", Ability.Type.DAMAGE, 24, 22));
                    break;
                case ASSASSIN:
                    this.strength = 20; this.agility = 35; this.intel = 13;
                    this.maxHealth = 95; this.maxMana = 55;
                    this.abilities.add(new Ability("Рывок из тени", Ability.Type.DAMAGE, 26, 28));
                    break;
            }

            this.health = maxHealth;
            this.mana = maxMana;
        }

        public void equipWeapon(Weapon weapon) { this.weapon = weapon; }
        public void equipArmor(Armor armor) { this.armor = armor; }

        public void gainExperience(int amount) { this.experience += amount; }

        public boolean checkLevelUp() {
            if (this.experience < this.nextLevelExp) return false;

            this.level++;
            this.strength += 2 + (classType == ClassType.WARRIOR ? 2 : 0);
            this.agility += 2 + (classType == ClassType.ASSASSIN || classType == ClassType.ARCHER ? 2 : 0);
            this.intel += 2 + (classType == ClassType.MAGE ? 3 : 0);
            this.maxHealth += 18;
            this.maxMana += 12;
            this.health = this.maxHealth;
            this.mana = this.maxMana;
            this.experience -= this.nextLevelExp;
            this.nextLevelExp = (int)(this.nextLevelExp * 1.6);

            return true;
        }
    }

    static class Weapon {
        String name; int damageBonus;
        Weapon(String name, int damageBonus) { this.name = name; this.damageBonus = damageBonus; }
    }

    static class Armor {
        String name; int damageReduction;
        Armor(String name, int damageReduction) { this.name = name; this.damageReduction = damageReduction; }
    }

    static class Ability {
        enum Type { DAMAGE, HEAL }
        String name; Type type; int power; int manaCost;
        Ability(String name, Type type, int power, int manaCost) {
            this.name = name; this.type = type; this.power = power; this.manaCost = manaCost;
        }
    }
}
package com.test;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class BattleGame extends JFrame {
    private Unit player;
    private Unit enemy;
    private Random random = new Random();

    private JLabel playerHealthLabel;
    private JLabel enemyHealthLabel;
    private JLabel playerManaLabel;
    private JLabel enemyManaLabel;
    private JLabel playerExpLabel;
    private JLabel enemyNameLabel;

    private JTextArea battleLog;
    private JPanel abilitiesPanel;

    public BattleGame() {
        setTitle("Битва героев: Фэнтези РПГ");
        setSize(850, 650);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setLayout(new BorderLayout());

        player = createUnitByClass("Воин", Unit.ClassType.WARRIOR);
        enemy = createUnitByClass("Тёмный Маг", Unit.ClassType.MAGE);

        JPanel statsPanel = new JPanel(new GridLayout(1, 2, 20, 10));
        statsPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        statsPanel.add(createPlayerStatsPanel());
        statsPanel.add(createEnemyStatsPanel());
        add(statsPanel, BorderLayout.NORTH);

        battleLog = new JTextArea();
        battleLog.setEditable(false);
        battleLog.setFont(new Font("Monospaced", Font.PLAIN, 13));
        battleLog.setBackground(new Color(240, 240, 240));
        JScrollPane logScrollPane = new JScrollPane(battleLog);
        logScrollPane.setBorder(BorderFactory.createTitledBorder("Хроники битвы"));
        add(logScrollPane, BorderLayout.CENTER);

        JPanel controlPanel = new JPanel(new BorderLayout(10, 10));
        controlPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));

        abilitiesPanel = new JPanel();
        abilitiesPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        updateAbilitiesPanel();
        controlPanel.add(abilitiesPanel, BorderLayout.CENTER);

        JPanel buttonPanel = new JPanel();
        JButton newBattleBtn = new JButton("Новая битва");
        newBattleBtn.addActionListener(e -> startNewBattle());
        buttonPanel.add(newBattleBtn);

        controlPanel.add(buttonPanel, BorderLayout.SOUTH);
        add(controlPanel, BorderLayout.SOUTH);

        logMessage("⚔️ Битва началась! " + player.name + " (" + player.classType.getName() + ") против " + enemy.name);
        logMessage("💡 Совет: Используйте способности для тактического преимущества!");
    }

    private JPanel createPlayerStatsPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Игрок: " + player.name + " (" + player.classType.getName() + ")"));

        JPanel statsGrid = new JPanel(new GridLayout(5, 2, 5, 5));
        statsGrid.add(new JLabel("Уровень:"));
        statsGrid.add(new JLabel(String.valueOf(player.level)));

        statsGrid.add(new JLabel("Здоровье:"));
        playerHealthLabel = new JLabel(player.health + "/" + player.maxHealth);
        statsGrid.add(playerHealthLabel);

        statsGrid.add(new JLabel("Мана:"));
        playerManaLabel = new JLabel(player.mana + "/" + player.maxMana);
        statsGrid.add(playerManaLabel);

        statsGrid.add(new JLabel("Опыт:"));
        playerExpLabel = new JLabel(player.experience + "/" + player.nextLevelExp);
        statsGrid.add(playerExpLabel);

        statsGrid.add(new JLabel("Экипировка:"));
        statsGrid.add(new JLabel(getEquipmentString(player)));

        panel.add(statsGrid, BorderLayout.CENTER);
        return panel;
    }

    private JPanel createEnemyStatsPanel() {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder("Противник"));

        JPanel statsGrid = new JPanel(new GridLayout(4, 2, 5, 5));
        enemyNameLabel = new JLabel(enemy.name + " (" + enemy.classType.getName() + ")");
        statsGrid.add(new JLabel("Имя:"));
        statsGrid.add(enemyNameLabel);

        statsGrid.add(new JLabel("Здоровье:"));
        enemyHealthLabel = new JLabel(enemy.health + "/" + enemy.maxHealth);
        statsGrid.add(enemyHealthLabel);

        statsGrid.add(new JLabel("Мана:"));
        enemyManaLabel = new JLabel(enemy.mana + "/" + enemy.maxMana);
        statsGrid.add(enemyManaLabel);

        statsGrid.add(new JLabel("Экипировка:"));
        statsGrid.add(new JLabel(getEquipmentString(enemy)));

        panel.add(statsGrid, BorderLayout.CENTER);
        return panel;
    }

    private String getEquipmentString(Unit unit) {
        String weapon = unit.weapon != null ? unit.weapon.name : "— ";
        String armor = unit.armor != null ? unit.armor.name : "— ";
        return "Оружие: " + weapon + " | Доспехи: " + armor;
    }

    private void updateAbilitiesPanel() {
        abilitiesPanel.removeAll();

        for (Ability ability : player.abilities) {
            JButton btn = new JButton(ability.name + " (" + ability.manaCost + " MP)");
            btn.setEnabled(player.mana >= ability.manaCost && player.health > 0 && enemy.health > 0);
            final Ability selectedAbility = ability;
            btn.addActionListener(e -> executePlayerTurn(selectedAbility));
            abilitiesPanel.add(btn);
        }

        JButton basicAttackBtn = new JButton("Базовая атака");
        basicAttackBtn.setEnabled(player.health > 0 && enemy.health > 0);
        basicAttackBtn.addActionListener(e -> executePlayerTurn(null));
        abilitiesPanel.add(basicAttackBtn);

        abilitiesPanel.revalidate();
        abilitiesPanel.repaint();
    }

    private void executePlayerTurn(Ability ability) {
        if (player.health <= 0 || enemy.health <= 0) return;

        int damage = ability != null ? calculateAbilityDamage(player, enemy, ability) : calculatePhysicalDamage(player, enemy);
        String actionName = ability != null ? ability.name : "базовая атака";

        if (calculateDodgeChance(enemy)) {
            logMessage(String.format("💨 %s уклонился от %s %s!", enemy.name, actionName, player.name));
        } else {
            if (calculateBlockChance(enemy)) {
                int blocked = (int)(damage * 0.6);
                damage -= blocked;
                logMessage(String.format("🛡️ %s заблокировал %d урона!", enemy.name, blocked));
            }
            enemy.health = Math.max(0, enemy.health - damage);
            logMessage(String.format("💥 %s нанёс %d урона %s (%s)", player.name, damage, enemy.name, actionName));
        }

        if (ability != null) {
            player.mana -= ability.manaCost;
        }

        updateUI();

        if (enemy.health <= 0) {
            enemy.health = 0;
            int expGain = 50 + enemy.level * 25;
            player.gainExperience(expGain);
            logMessage(String.format("🎉 %s победил! Получено %d опыта", player.name, expGain));
            if (player.checkLevelUp()) {
                logMessage(String.format("✨ %s достиг %d уровня! Характеристики улучшены.", player.name, player.level));
            }
            disableControls();
            return;
        }

        Timer timer = new Timer(800, e -> {
            executeEnemyTurn();
            updateUI();
        });
        timer.setRepeats(false);
        timer.start();
    }

    private void executeEnemyTurn() {
        if (player.health <= 0 || enemy.health <= 0) return;

        Ability enemyAbility = null;
        if (enemy.classType == Unit.ClassType.MAGE && enemy.mana >= 20 && random.nextInt(100) < 60) {
            enemyAbility = enemy.abilities.get(0);
        }

        int damage = enemyAbility != null ? calculateAbilityDamage(enemy, player, enemyAbility) : calculatePhysicalDamage(enemy, player);
        String actionName = enemyAbility != null ? enemyAbility.name : "базовая атака";

        if (calculateDodgeChance(player)) {
            logMessage(String.format("💨 %s уклонился от %s %s!", player.name, actionName, enemy.name));
        } else {
            if (calculateBlockChance(player)) {
                int blocked = (int)(damage * 0.6);
                damage -= blocked;
                logMessage(String.format("🛡️ %s заблокировал %d урона!", player.name, blocked));
            }
            player.health = Math.max(0, player.health - damage);
            logMessage(String.format("💥 %s нанёс %d урона %s (%s)", enemy.name, damage, player.name, actionName));
        }

        if (enemyAbility != null) {
            enemy.mana -= enemyAbility.manaCost;
        }

        if (player.health <= 0) {
            player.health = 0;
            logMessage(String.format("☠️ %s пал в бою...", player.name));
            disableControls();
        }
    }

    private int calculatePhysicalDamage(Unit attacker, Unit defender) {
        int baseDamage = attacker.strength;
        if (attacker.weapon != null) baseDamage += attacker.weapon.damageBonus;
        int reduction = defender.armor != null ? defender.armor.damageReduction : 0;
        int finalDamage = (int)(baseDamage * (1 - reduction / 100.0)) + attacker.level;
        return Math.max(1, finalDamage); // Минимум 1 урона
    }

    private int calculateAbilityDamage(Unit caster, Unit target, Ability ability) {
        if (ability.type == Ability.Type.HEAL) {
            return Math.min(ability.power, caster.maxHealth - caster.health);
        }
        return caster.intel * 2 + ability.power + caster.level;
    }

    private boolean calculateDodgeChance(Unit unit) {
        int chance = Math.min(50, unit.agility / 2);
        return random.nextInt(100) < chance;
    }

    private boolean calculateBlockChance(Unit unit) {
        int chance = Math.min(40, (unit.strength + unit.agility) / 4);
        return random.nextInt(100) < chance;
    }

    private void updateUI() {
        playerHealthLabel.setText(player.health + "/" + player.maxHealth);
        enemyHealthLabel.setText(enemy.health + "/" + enemy.maxHealth);
        playerManaLabel.setText(player.mana + "/" + player.maxMana);
        enemyManaLabel.setText(enemy.mana + "/" + enemy.maxMana);
        playerExpLabel.setText(player.experience + "/" + player.nextLevelExp);
        enemyNameLabel.setText(enemy.name + " (" + enemy.classType.getName() + ")");
        updateAbilitiesPanel();
    }

    private void disableControls() {
        for (Component c : abilitiesPanel.getComponents()) {
            if (c instanceof JButton) ((JButton) c).setEnabled(false);
        }
    }

    private void startNewBattle() {
        Unit.ClassType[] classes = Unit.ClassType.values();
        Unit.ClassType playerClass = classes[random.nextInt(classes.length)];
        Unit.ClassType enemyClass = classes[random.nextInt(classes.length)];

        player = createUnitByClass("Герой", playerClass);
        enemy = createUnitByClass("Враг", enemyClass);

        battleLog.setText("");
        logMessage(String.format("⚔️ Новая битва! %s (%s) против %s (%s)",
                player.name, player.classType.getName(),
                enemy.name, enemy.classType.getName()));

        updateUI();
        for (Component c : abilitiesPanel.getComponents()) {
            if (c instanceof JButton) ((JButton) c).setEnabled(true);
        }
    }

    private Unit createUnitByClass(String name, Unit.ClassType type) {
        Unit unit = new Unit(name, type);

        switch (type) {
            case WARRIOR:
                unit.equipWeapon(new Weapon("Стальной меч", 10));
                unit.equipArmor(new Armor("Латы", 20));
                break;
            case MAGE:
                unit.equipWeapon(new Weapon("Волшебный посох", 4));
                unit.equipArmor(new Armor("Магическая роба", 8));
                break;
            case ARCHER:
                unit.equipWeapon(new Weapon("Композитный лук", 8));
                unit.equipArmor(new Armor("Кожаный доспех", 12));
                break;
            case ASSASSIN:
                unit.equipWeapon(new Weapon("Серебряные кинжалы", 7));
                unit.equipArmor(new Armor("Теневая мантия", 10));
                break;
        }

        return unit;
    }

    private void logMessage(String message) {
        battleLog.append(message + "\n");
        battleLog.setCaretPosition(battleLog.getDocument().getLength());
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            BattleGame game = new BattleGame();
            game.setVisible(true);
        });
    }

    // ========== ВНУТРЕННИЕ КЛАССЫ ==========
    static class Unit {
        enum ClassType {
            WARRIOR("Воин"), MAGE("Маг"), ARCHER("Лучник"), ASSASSIN("Ассасин");
            private final String name;
            ClassType(String name) { this.name = name; }
            public String getName() { return name; }
        }

        String name;
        ClassType classType;
        int intel, agility, strength;
        int health, maxHealth;
        int mana, maxMana;
        int level;
        int experience, nextLevelExp;
        Weapon weapon;
        Armor armor;
        List<Ability> abilities = new ArrayList<>();

        public Unit(String name, ClassType classType) {
            this.name = name;
            this.classType = classType;
            this.level = 1;
            this.experience = 0;
            this.nextLevelExp = 100;

            switch (classType) {
                case WARRIOR:
                    this.strength = 28; this.agility = 14; this.intel = 8;
                    this.maxHealth = 130; this.maxMana = 45;
                    this.abilities.add(new Ability("Щитовой удар", Ability.Type.DAMAGE, 18, 20));
                    break;
                case MAGE:
                    this.strength = 7; this.agility = 12; this.intel = 32;
                    this.maxHealth = 85; this.maxMana = 110;
                    this.abilities.add(new Ability("Фаербол", Ability.Type.DAMAGE, 28, 25));
                    this.abilities.add(new Ability("Исцеление", Ability.Type.HEAL, 25, 35));
                    break;
                case ARCHER:
                    this.strength = 14; this.agility = 30; this.intel = 11;
                    this.maxHealth = 100; this.maxMana = 65;
                    this.abilities.add(new Ability("Прицельный выстрел", Ability.Type.DAMAGE, 24, 22));
                    break;
                case ASSASSIN:
                    this.strength = 20; this.agility = 35; this.intel = 13;
                    this.maxHealth = 95; this.maxMana = 55;
                    this.abilities.add(new Ability("Рывок из тени", Ability.Type.DAMAGE, 26, 28));
                    break;
            }

            this.health = maxHealth;
            this.mana = maxMana;
        }

        public void equipWeapon(Weapon weapon) { this.weapon = weapon; }
        public void equipArmor(Armor armor) { this.armor = armor; }

        public void gainExperience(int amount) { this.experience += amount; }

        public boolean checkLevelUp() {
            if (this.experience < this.nextLevelExp) return false;

            this.level++;
            this.strength += 2 + (classType == ClassType.WARRIOR ? 2 : 0);
            this.agility += 2 + (classType == ClassType.ASSASSIN || classType == ClassType.ARCHER ? 2 : 0);
            this.intel += 2 + (classType == ClassType.MAGE ? 3 : 0);
            this.maxHealth += 18;
            this.maxMana += 12;
            this.health = this.maxHealth;
            this.mana = this.maxMana;
            this.experience -= this.nextLevelExp;
            this.nextLevelExp = (int)(this.nextLevelExp * 1.6);

            return true;
        }
    }

    static class Weapon {
        String name; int damageBonus;
        Weapon(String name, int damageBonus) { this.name = name; this.damageBonus = damageBonus; }
    }

    static class Armor {
        String name; int damageReduction;
        Armor(String name, int damageReduction) { this.name = name; this.damageReduction = damageReduction; }
    }

    static class Ability {
        enum Type { DAMAGE, HEAL }
        String name; Type type; int power; int manaCost;
        Ability(String name, Type type, int power, int manaCost) {
            this.name = name; this.type = type; this.power = power; this.manaCost = manaCost;
        }
    }
}