Основной боевой интерфейс симулятор РПГ игры


Я создала игру, которая позволяет сражаться с врагом, пока не победил. Это моя первая попытка в написании игры, как это в Java, и я использую его как средство в практике ООП и другие темы, как я узнаю их.

Текущие значения перемещения каждого персонажа есть базовая атака, бросить кристаллов льда(заморозить цель на 2 хода), выпить зелье(восстанавливает 15 здоровья), и бросить огненный шар(гарантированные 20 урона).

Я имею трудное время, выясняя, какие добавки я могу добавить здесь или как я могу улучшить/ развить мой код таким образом, что я до сих пор понять будучи довольно новыми для языка.

import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

public class GameFile extends JFrame
{
    public static void main(String[] args)
    {
        int playAgain;
        Random rng = new Random();
        do
        {
            GameCharacter player = generatePlayer();
            GameCharacter enemy = generateEnemy();
            int playerFrozen = 0;
            int enemyFrozen = 0;
            do
            {
                //player's turn
                if (player.isFrozenStatus() == false)
                {
                    int choice = menuSelect(player);
                    if (choice == 0)
                        basicAttack(player, enemy);
                    else if (choice == 1)
                        drinkPotion(player);
                    else if (choice == 2)
                    useFireBall(player, enemy);
                    else if (choice == 3)
                        useIceCrystal(player, enemy);
                    else
                        System.exit(0);
            }
            else
            {
                playerFrozen += 1;
                if (playerFrozen % 2 == 0)
                    player.setFrozenStatus(false);
            }

            //enemy's turn
            if (enemy.getHealthPoints() > 0 && !enemy.isFrozenStatus())
            {
                int enemyChoice = rng.nextInt(4);
                if ((enemy.getHealthPoints() <= 10) && (enemy.getPotions() > 0))
                    drinkPotion(enemy);
                else if (enemyChoice == 0 && enemy.getFireBalls() > 0)
                    useFireBall(enemy, player);
                else if (enemyChoice == 1 && enemy.getIceCrystals() > 0 && !player.isFrozenStatus())
                    useIceCrystal(enemy, player);
                else
                    basicAttack(enemy, player);
            }
            else if (enemy.getHealthPoints() > 0)
            {
                enemyFrozen += 1;
                if (enemyFrozen % 2 == 0)
                    enemy.setFrozenStatus(false);
            }
        } while (player.getHealthPoints() > 0 && enemy.getHealthPoints() > 0);

        playAgain = displayWinner(player, enemy);
    } while (playAgain == 0);
}

// OTHER METHODS

public static GameCharacter generateEnemy()
{
    GameCharacter enemy = new GameCharacter(10, "the Evil Wizard", CharacterType.ENEMY);
    return enemy;
}

public static GameCharacter generatePlayer()
{
    String name = JOptionPane.showInputDialog("Welcome! What is your name?");
    if (name == null)
        System.exit(0);
    GameCharacter player = new GameCharacter(10, name, CharacterType.PLAYER);
    simpleMessage("Hi " + player.getName() + "! Get ready to battle your opponent!");
    return player;
}

public static void basicAttack(GameCharacter attacker, GameCharacter target)
{
    attacker.attack(target);
    simpleMessage((attacker.getType() == CharacterType.ENEMY
            ? attacker.getName() + " attacked you! You have " + target.getHealthPoints() + " health left."
            : "You attacked " + target.getName() + "! They have " + target.getHealthPoints() + " health left."));
}

public static void useIceCrystal(GameCharacter attacker, GameCharacter target)
{
    if (attacker.throwIceCrystals(target))
    {
        target.setFrozenStatus(true);
        simpleMessage((attacker.getType() == CharacterType.ENEMY
                ? attacker.getName() + " threw an Ice Crystal!\nYou are frozen for the next 2 rounds!"
                : "You threw an Ice Crystal!\n" + target.getName() + " is frozen for the next 2 rounds!"));
    }
    else if (attacker.getType() == CharacterType.PLAYER)
        simpleMessage("You are out of Ice Crystals....");
}

public static void useFireBall(GameCharacter attacker, GameCharacter target)
{
    if (attacker.throwFireBall(target))
        simpleMessage((attacker.getType() == CharacterType.ENEMY
                ? attacker.getName() + " threw a FireBall at you! You have " + target.getHealthPoints() + " health left."
                : "You threw a FireBall at " + target.getName() + "! They have " + target.getHealthPoints() + " health left."));
    else if (attacker.getType() == CharacterType.PLAYER)
        simpleMessage("You are out of FireBalls...");

}

public static void drinkPotion(GameCharacter drinker)
{
    if (drinker.usePotion())
        simpleMessage((drinker.getType() == CharacterType.ENEMY
                ? drinker.getName() + " used a potion! They have " + drinker.getHealthPoints() + " health."
                : "You used a potion! You have " + drinker.getHealthPoints() + " health."));
    else if (drinker.getType() == CharacterType.PLAYER)
        simpleMessage("You are out of potions!");
}

public static int displayWinner(GameCharacter player, GameCharacter enemy)
{
    if (player.getHealthPoints() <= 0)
        JOptionPane.showMessageDialog(null, "Sorry, you lose!\n" + enemy.getName() + " has defeated you...");
    else
        JOptionPane.showMessageDialog(null, "Great job " + player.getName() + ", you win!" + "\nYou have defeated " + enemy.getName() + "!");
    return JOptionPane.showConfirmDialog(null, "Would you like to play again?", " ", JOptionPane.YES_NO_OPTION);
}

public static int menuSelect(GameCharacter player)
{
    String[] moveChoices = { "Attack", "Use Potion(" + player.getPotions() + ")", "Throw FireBall(" + player.getFireBalls() + ")",
            "Throw Ice Crystal(" + player.getIceCrystals() + ")" };
    int choice = JOptionPane.showOptionDialog(null, "What would you like to do?", " ", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE,
            null, moveChoices, moveChoices[0]);
    return choice;
}

public static void simpleMessage(String boxMessage)
{
    if (JOptionPane.showConfirmDialog(null, boxMessage, " ", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE) != 0)
        System.exit(0);
}

}


Комментарии
2 ответа

Спасибо за код!

Тимоти раболепства уже есть хороший отзыв, но я постараюсь добавить что-то еще.

Одна вещь, я всегда думаю о том, что должно измениться, если этот проект получил намного больше

В настоящее время, как пользователя, так и врагом всего 4 разных варианта, все это хорошо и хорошо, когда масштаб проекта достаточно мала, но что происходит, когда вы начинаете добавлять в ThunderBolt, PoisonCloud, Inferno и т. д. Вы ходите добавить соответствующий метод в любом соответствующем классе, а затем добавить новое условие в нескольких частях кода? Это должно произойти с учетом текущей реализации, и будет очень unmaintainable , а также очень подвержена ошибкам.

Что было бы неплохо увидеть один метод, который будет обрабатывать через любой произвольной Ability. Уже есть несколько кандидатов на Ability подкласс. Те существа, Fireball, Potion и IceCrystal. Я бы тоже еще подкину Attack здесь в качестве возможного варианта.

И вместо того, чтобы basicAttack, useFireBall, drinkPotion методы, мы могли бы что-то вроде этого.

GameCharacter player = ...;
GameCharacter target = ...;
player.useAbility(new Fireball(), target);
player.useAbility(new Heal(50), player); // self heal
player.useAbility(new PoisonCloud(), target);

Теперь, конечно, нам нужно как-то сделать эту работу!

public void useAbility(Ability ability, GameCharacter target){
target.dealDamage(ability.spellPower()); // we're not calling target.setHp()

for(StatusEffect effect : ability.statusEffects()){
effect.apply(target); // Burn, Poisoned, Regeneration etc.
}

// the ability could also be a friendly one
if(ability.isBuff()){
target.applyBuff(ability.getBuff());
}
}

Каждый Ability бы его собственные атрибуты и методы для достижения желаемого результата. Это позволит эффективно устранить необходимость в новый способ в новый навык, вместо этого необходимо создать новый подкласс Ability и просто передать его в этот метод!

Мы ввели несколько новых объектов, которые мы могли бы использовать. У нас есть StatusEffect класс. На данный момент Frozen это единственный статус эффект, который в данный момент в вашей игре. Подумайте о том, сколько придется менять, если мы хотим изменить то, как ваш заморозить эффект работает, все, что использует его нужно изменить. Вместо этого мы могли бы инкапсулировать, что в StatusEffect класса или перечисления. Это также позволит нам легко создавать новые как Burn, Regenerate, AutoRevive и т. д. Один из возможных способов это можно реализовать просто добавив Collection статус эффектов GameCharacter класс.

public class GameCharacter {

private Collection<StatusEffect> statusEffects = new HashSet<>();

// ... constructors/rest of class

public void addStatusEffect(StatusEffect se){
statusEffects.add(se);
}

public void startOfTurn(){
for(StatusEffect se : statusEffects){
se.apply(this); // heal with regen, take damage with burn, prevent turn with freeze etc.
}
}

}

Эта строка кода повторяется во многих местах

gameCharacter.getHealthPoints() > 0;

это нарушение , сказать не задать принципе. Здесь, вы задаете объекту, чтобы сказать вам, что его очков здоровья, так что вы можете выполнить некоторые вычисления с тем же результатом. Вместо этого, вы должны сообщить объекту, что вы хотите знать. Например

gameCharacter.isAlive();

Это не выглядит как большая разница, ведь это все-таки одна строка кода, но давайте изображения введут новую механику в игре. А StatusEffect что мешает смерть за 3 хода, даже если ты ниже 0лошадиная сила. Это похоже на то, что может быть в РПГ. Если вы хотите сделать это сейчас, вам придется пройти через все вхождения проверки для данного > 0 и добавить дополнительное условие, это будет очень неудобно и опять же, очень подвержен ошибкам.

с isAlive способ, мы можем сделать это так

// GameCharacter class
public boolean isAlive(){
return statusEffects.contains(new HolyShield()) || this.hp > 0;
}

(этот пример предполагает, что .равен и .хэш-код в HolyShield классе будем рассматривать все экземпляры HolyShield быть равны)

сейчас в нашей телефонный код, нас не волнует, как GameCharacter жив, все мы заботимся о том, что они есть!

Есть аналогичная проблема с ваш проверка если gameCharacter заморожен. Вы сейчас отслеживаю это состояние с целыми числами, которые находятся вне GameCharacter класс. Это государство должно быть обработано внутри GameCharacter классов (или подклассов).

В настоящее время у вас есть это

else if (enemy.getHealthPoints() > 0)
{
enemyFrozen += 1;
if (enemyFrozen % 2 == 0)
enemy.setFrozenStatus(false);
}

Я бы предпочел увидеть что-то вроде

else if (enemy.isAlive()){
enemy.chill(); // may freeze them, internally this has the enemyFrozen int
}

Надеюсь, этот обзор был полезным для вас, так держать!

2
ответ дан 8 апреля 2018 в 02:04 Источник Поделиться

Добро пожаловать в код комментарий ЮВ!
Спасибо за ваш код.
Это выглядит довольно хорошо для первой попытки.

ООП

ООП не означает "разделить" код в случайных классов.

Конечной целью ООП является, чтобы уменьшить дублирование кода, улучшают читаемость и использовать поддержку, а также продление кода.

Делаем ООП означает, что вы будете следовать определенным принципам, которые (среди прочих):


  • сокрытие информации / заключения

  • единая ответственность

  • разделение

  • Поцелуй (сохранить его простым (и) глупо.)

  • Dry (не повторяй себя.)

  • "Скажи! Не спрашивай".

  • Закон Деметры ("не разговаривай с незнакомцами!")

Наследование

В ООП мы наследуем от супер класса, если мы выражаем свое поведение. Это: мы переопределить метод, чтобы сделать что-то больше и/или что-то другое, то один и тот же метод в супер классе.

Ваш класс расширяет JFrame но не изменить JFrames поведения.
Это даже не доступ к любой метод JFrame. Так что ваш класс должен даже не знать, что есть JFrame класс там.

Скажу, не спрашивайте!

В определенных местах ваш код использует некоторые сведения из GameCharacter объект для расчета какой-либо информацией. Е. Г.:

    if (attacker.throwFireBall(target))
simpleMessage((attacker.getType() == CharacterType.ENEMY
? attacker.getName() + " threw a FireBall at you! You have " + target.getHealthPoints()
+ " health left."
: "You threw a FireBall at " + target.getName() + "! They have " + target.getHealthPoints()
+ " health left."));
else if (attacker.getType() == CharacterType.PLAYER)
simpleMessage("You are out of FireBalls...");

Вам лучше сделать этот расчет в GameCharacter класс, где у вас есть вся информация под рукой, поэтому весь этот код должен быть в методе throwFireBall(target) в этот класс.

заменить ветвления с полиморфизмом

Ваш класс GameCharacter имеет свойства тип CharacterType и вам выбирать различные поведения , основанный на этом свойстве. Это признак того, что вам не хватает иерархии классов.

Вы должны иметь два и более классов Player и Enemy что продлить GameCharacter которые проводят специальные поведения одного:

abstract class  GameCharacter{
public abstract void throwFireBallAt(GameCharacter target);
}

class Player extends GameCharacter{
@override
public void throwFireBallAt(GameCharacter target){
if (hasMoreFireballs)
target.defeatFireball();
simpleMessage("You threw a FireBall at " + target.getName() + "! They have " + target.getHealthPoints() + " health left.");
else
simpleMessage("You are out of FireBalls...");
}
}

class Enemy extends GameCharacter{
@override
public void throwFireBallAt(GameCharacter target){
if (hasMoreFireballs) {
target.defeatFireball();
simpleMessage(attacker.getName() + " threw a FireBall at you! You have " + target.getHealthPoints() + " health left.");
}
}

Если по некоторым причинам вы получаете другой тип GameCharacter вы просто создаете новый дочерний класс GameCharacter и изменять свое поведение в том, что новый класс файл вместо скимминг всем протяжении вашего существующего кода искал места, чтобы добавить новое поведение.

1
ответ дан 8 апреля 2018 в 10:04 Источник Поделиться