Используя наследование и полиморфизм для решения общей игровой проблемы

У меня есть два класса; давайте назовем их Людоедом и Мастером. (Все поля общедоступны для создания примера легче ввести.)

public class Ogre
{
  int weight;
  int height;
  int axeLength;
}

public class Wizard
{
  int age;
  int IQ;
  int height;
}

В каждом классе я могу создать метод, названный, скажем, борются (), который определит, кто победит, если Людоед встретится и Людоед, или Мастер встречает Мастер. Вот пример. Если Людоед встречает Людоеда, более тяжелые победы. Но если вес является тем же, тем с более длинными победами топора.

public Ogre battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else if (this.axeLength > o.axeLength) return this;
  else if (this.axeLength < o.axeLength) return o;
  else return this;    // default case
}

Мы можем сделать похожий метод для Мастеров.

Но что, если Мастер встречает Людоеда? Мы могли, конечно, сделать метод для этого, сравнив, скажем, просто высоты.

public Wizard battle(Ogre o)
{
  if (this.height > o.height) return this;
  else if (this.height < o.height) return o;
  else return this;
}

И мы сделали бы подобный для Людоедов, которые встречают Мастер. Но вещи выходят из-под контроля, если мы должны добавить больше типов символов к программе.

Это - то, где я застреваю. Одно очевидное решение состоит в том, чтобы создать Класс символов с общими чертами. Людоед и Мастер наследовались Символу и расширяют его для включения других черт, которые определяют каждого.

public class Character
{
  int height;

  public Character battle(Character c)
  {
    if (this.height > c.height) return this;
    else if (this.height < c.height) return c;
    else return this;
  }
}

Существует ли лучший способ организовать классы? Я посмотрел на стратегическую модель и шаблон посредника, но я не уверен, как любой из них (если таковые имеются) мог помочь здесь. Моя цель состоит в том, чтобы достигнуть некоторого общего метода сражения, так, чтобы, если Людоед встречает Людоеда, это использовало сражение Людоеда по сравнению с людоедом, но если Людоед встречает Мастер, это использует более универсальный. Далее, что, если символы, которые встречаются, не совместно используют общих черт? Как мы можем решить, кто выигрывает сражение?

Править: Много больших ответов! Я должен переварить их и фигуру, какой работает лучше всего на мою ситуацию.

22
задан Barry Brown 6 May 2010 в 02:25
поделиться

13 ответов

Хм, во-первых, ваш первый дизайн не очень хорош, потому что вы позволяете бойцам решать, кто победит. Если вы используете Mediator, например, что-то вроде предложенного класса Battle, вы сможете централизовать логику боя и легко изменить любое правило боя в одном месте. Представьте, что у вас много существ... Когда вы захотите изменить, как два существа сражаются вместе, где вы будете искать метод боя? В первом или во втором классе? В каком-то суперклассе? Так что медиатор - хорошая идея. Еще одна проблема - решить, какое правило использовать. Вы можете легко прийти к проблеме множественной диспетчеризации.

0
ответ дан 29 November 2019 в 04:29
поделиться

Шаблон посетителя «представляет собой способ отделения алгоритма от структуры объекта, над которой он работает».

Для вашего примера вы можете иметь

class Character {
    boolean battle(BattleVisitor visitor) {
       return visitor.visit(this);
    }
}

class Ogre extends Character {..}
class Wizard extends Character {..}
class Dwarf extends Character {..}

interface BattleVisitor {
    boolean visit(Ogre character);
    boolean visit(Wizard character);
    boolean visit(Dwarf character);
}

class OgreBattleVisitor implements BattleVisitor {
    private Ogre ogre;
    OgreBattleVisitor(Ogre ogre) { this.ogre = ogre; }
    boolean visit(Ogre ogre) {
      // define the battle 
    }

    boolean visit(Wizard wizard) {
      // define the battle 
    }
    ...
}

И всякий раз, когда происходит битва:

targetChar.battle(new OgreBattleVisitor(ogre));

Определите реализацию Посетителя для Волшебника, Гнома и всего, что появится. Также обратите внимание, что я определяю результат метода visit как логическое (выиграно или проиграно), а не возвращаю победителя.

Таким образом, при добавлении новых типов вам нужно будет добавить:

  • метод посетителю для обработки борьбы с новым типом.
  • реализация для обработки сражений для нового типа

Теперь, здесь оказывается, что у вас будет некоторое дублирование кода в случае "Ogre vs Wizard" == "Wizard vs Ogre". Я не знаю, так ли это - например, может быть разница в зависимости от того, кто наносит удар первым. Кроме того, вы можете захотеть предоставить совершенно другой алгоритм, скажем, для «Болотной битвы с людоедом» по сравнению с «деревенской битвой с огра». Таким образом, вы можете создать нового посетителя (или иерархию посетителей) и при необходимости применить подходящего.

19
ответ дан 29 November 2019 в 04:29
поделиться

Что-то вроде этого?

class Trait
{
    enum Type
    {
        HEIGHT,
        WEIGHT,
        IQ
    }
    protected Type type;
    protected int value;

    public Trait(int value, Type type)
    {
        this.type = type;
        this.value = value;
    }

    public boolean compareTo(Trait trait)
    {
        if(trait.type != this.type)
            throw new IllegalArgumentException(trait.type+" and "+this.type+" are not comparable traits");
        else
            return this.value - trait.value;
    }
}

class Character
{
    protected Trait[] traits;

    protected Character(Trait[] traits)
    {
        this.traits = traits;
    }

    public Trait getTrait(Trait.Type type)
    {
        for(Trait t : traits)
            if(t.type == type) return t;
        return null;
    }

    public Character doBattleWith(Character that)
    {
        for(Trait thisTrait : traits)
        {
            otherTrait = that.getTrait(thisTrait.type);
            if(otherTrait != null)
            {
                int comp = thisTrait.compareTo(otherTrait);

                if(comp > 0)
                    return this;
                else if (comp < 0)
                    return that;
            }
        }
        return null;
    }
}

class Ogre extends Character
{
    public Ogre(int height, int weight)
    {
        super(new Trait[]{
            new Trait(Type.HEIGHT,height),
            new Trait(Type.WEIGHT,height)});
    }
}
0
ответ дан 29 November 2019 в 04:29
поделиться

Я думаю, вам следует переосмыслить все это.

Давайте просто возьмем World of Warcraft в качестве примера того, как можно вести битву, просто потому, что это хорошо известная игра.

У вас есть несколько разных классов, которые способны делать разные вещи и имеют свои сильные и слабые стороны. Однако все они имеют общие типы статистики. Например, у мага больше интеллекта, чем у воина, но у воина будет намного больше силы, чем у мага.

Так как же они на самом деле сражаются? Что ж, независимо от класса, каждый персонаж имеет в своем распоряжении ряд способностей. Каждая способность наносит определенный урон, и как только HP одного из персонажей падает до 0, этот персонаж умирает - он проигрывает бой.

Вам следует использовать аналогичный подход: определить общий базовый класс с общими атрибутами, которые применимы ко всем - такими, как сила, сила заклинаний, защита и выносливость.Затем, когда каждый тип персонажа вступает в бой, он может использовать любую серию атак или заклинаний - урон, нанесенный каждой атакой или заклинанием, будет зависеть от характеристик как атакующего, так и защищающегося, с использованием некоторой подходящей формулы (с некоторой случайностью для интереса, вероятно, будет неинтересно, если Волшебник не сможет когда-либо победить Огра, или наоборот).

Но вот еще кое-что, о чем следует подумать: возможно, вам не следует использовать класс по типу. Было бы предпочтительнее, если бы вы могли использовать одну и ту же формулу для всех, даже если у них разный набор способностей. Вместо того, чтобы кодировать каждую способность, у вас будет просто список способностей и их параметров в файле, и класс Character будет использовать его для выполнения всех этих вычислений. Это упрощает настройку формулы (только одно место для поиска) и упрощает настройку способностей (просто измените файл). Это немного сложнее написать эту формулу, потому что вы можете дать Огру бонус за высокую силу, в то время как Волшебник получит бонус за высокий интеллект, но это лучше, чем иметь X почти идентичных формул, по одной для каждой. stat, который может повлиять на результат.

3
ответ дан 29 November 2019 в 04:29
поделиться

Именно такие проблемы призвана решать Стратегия.

Давайте рассмотрим части Стратегии

  • Контекст: борьба
  • Стратегия: Как вы определите, кто победит
  • Конкретная стратегия: Где борьба происходит и принимается решение.

Итак, вместо того, чтобы оставлять эту ответственность персонажам (потому что они всегда будут говорить: "Я победил!!!, нет я победил, нет я..."), вы можете создать RefereeStrategy.

Конкретная реализация будет решать, кто победил.

стратегия в действии http://bit.ly/cvvglb

диаграмма, созданная с помощью http://yuml.me

Вы можете либо определить общие методы, на которые все персонажи согласны отвечать (что не похоже на то, что вы хотите, это полезно, когда все персонажи имеют одинаковые "атрибуты" или методы типа defense():int, attack():int, heal():int ), либо сделать "слепую" стратегию.

Я делаю второе ("слепая" стратегия)

// All the contenders will implement this.
interface Character {
    public String getName();    
}
// The context
class FightArena {
    Character home;
    Character visitor;

    // The strategy 
    Referee referee;

    Character fight(){
        this.referee = RefereeFactory.getReferee( home.getName(), visitor.getName() );
        Character winner = referee.decideFightBetween( home, visitor );
        out.println(" And the winner iiiiss...... " + winner.getName() );
    }
}

interface Referee {
    Character decideFightBetween( Character one, Character two );
}

class RefereeFactory {

        static Referee getReferee( Character one, Character two ) {
             .... return the appropiate Refereee... 
        }    
}

// Concrete Referee implementation 
// Wizard biased referee, dont' trust him
class OgreWizardReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        if( one instanceof Wizard ){
            return one;
        }else{
            return two;
        }
    }
}
class OgreReferee implements Referee {
    Character decideFightBetween( Character one, Character two ) {
        Ogre a = ( Ogre ) one;
        Ogre b = ( Ogre ) two;

        if( a.height > b.height || a.axeLength > a.axeLength ) {
            return a;
        }
        return b;
    }

}

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

Это сохраняет контекст (арену вашего боя) свободным от конструкций "if/elseif/else/if/else", передавая судье решение о победителе, и изолирует ваших различных персонажей друг от друга.

3
ответ дан 29 November 2019 в 04:29
поделиться

А как насчет разделения боевой логики на отдельный класс с помощью таких методов, как

Battle(Ogre ogre, Wizard wizard)

, которые вернут объект, содержащий победителя (или самого победителя, что угодно ). Это отделит логику битвы от сражающихся, а также позволит вам генерировать, например:

Battle(Creature creat1, Creature creat2)

Будет запасным методом для любой пары существ (при условии, что Волшебник / Огр / и т. Д. Все имеют 'Существо' в качестве базового класса), который не У меня нет конкретной логики. Это позволит вам добавлять / редактировать / удалять логику боя без изменения самих существ.

4
ответ дан 29 November 2019 в 04:29
поделиться

Что должно произойти в случае, если два огра имеют одинаковый рост / вес и одинаковую длину топора? Согласно вашему примеру, тот, кому посчастливилось получить звонок первым , выиграет.

Я не знаю, подходит ли это альтернатива, но что, если бы вы выбрали совершенно другую схему и приписали «боевой счет» каждому персонажу вместо того, чтобы полагаться на сравнение индивидуальных черт. Вы можете использовать атрибуты символа в формуле, чтобы дать некоторое целое число, которое можно сравнить с другим символом. Затем вы можете использовать общий метод battle , чтобы сравнить две оценки и вернуть персонажа с более высоким значением.

Например, что, если «боевой счет» огра был рассчитан по его росту плюс его вес, умноженный на длину топора, а оценка волшебника была рассчитана по его возрасту, умноженному на его IQ?

abstract class Character {
    public abstract int battleScore();

    public Character battle(Character c1, Character c2) {
        (c1.battleScore() > c2.battleScore()) ? return c1 : c2;
    }
}

class Ogre extends Character {
    public int battleScore() {
        return (height + weight) * axeLength;
    }
 }

 class Wizard extends Character {
    public int battleScore() {
        return height + (age * IQ);
    }
 }
7
ответ дан 29 November 2019 в 04:29
поделиться

Похоже, вам нужна двойная отправка .

В основном ваш Огр и Волшебник (вероятно) имеют общую основу. Когда вы вызываете метод битвы из базы Ogre , он берет базу Wizard и вызывает другую функцию на этой базе, которая принимает Ogre в качестве аргумента. Полиморфное поведение обоих вызовов функций эффективно дает вам полиморфизм двух типов одновременно.

5
ответ дан 29 November 2019 в 04:29
поделиться

Вам все равно придется определять уникальную логику (предполагая, что логика является уникальной) для каждой комбинации боя - не имеет значения, какой шаблон проектирования вы решите использовать. Единственное требование - отделить эту логику от класса Ogre и Wizard и создать методы битвы в другом классе. Я думаю, то, что вы делаете сейчас, совершенно нормально (как только вы перенесете логику сражений в другое место), не требуя шаблона посетителя, который я бы использовал, если бы это была какая-то корпоративная игра :)

Не слушайте весь этот пух о шаблонах проектирования...

1
ответ дан 29 November 2019 в 04:29
поделиться

Один из способов сделать это - создать новый интерфейс для всех типов символов, таких как

public interface Fightable
{
    public Fightable doBattle(Fightable b);
}

И затем оттуда вы должны реализовать doBattle в каждом классе. Например, в классе Ogre вы можете проверить, был ли b экземпляром Ogre (в этом случае выполните одно действие), Wizard (в этом случае другое) и т. Д.

Проблема в том, что каждый раз вы добавляете новый тип, вам нужно добавить код для каждого отдельного класса символов для каждого другого символа, который не особенно удобен в обслуживании. Кроме того, вам придется позаботиться о том, чтобы операции выполнялись должным образом, т.е. если вы изменили метод doBattle в классе Ogre по отношению к Wizards, но не в классе Wizard по отношению к Ogres, у вас может возникнуть ситуация, когда результат различается, вызываете ли вы anOgre.doBattle (aWizard) или aWizard.doBattle (anOgre).

Что может быть лучше, так это создать боевой класс, который принимает двух персонажей и содержит логику ведения боя, видя, какие два типа классов были переданы ему: таким образом вам нужно будет только менять свой боевой класс каждый раз, когда добавлен новый тип персонажа! Вы хотите инкапсулировать поведение, которое, скорее всего, будет меняться чаще всего.

2
ответ дан 29 November 2019 в 04:29
поделиться

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

Если его ценность выше, чем у того, с кем он сражается, он побеждает.

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

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

Ценность монстра может быть рассчитана на основе его атаки и соответствующей брони врага, а также HP и т.д. и т.п.

Таким образом, вы не беспокоитесь о каждом конкретном случае.

1
ответ дан 29 November 2019 в 04:29
поделиться

Я знаю, что это немного поздно, но Стив Йегге написал статью несколько лет назад. почти по этой же задаче (он даже использовал игровой пример!).

0
ответ дан 29 November 2019 в 04:29
поделиться

попробуйте тройной диспетчер дяди Боба. см. мой ответ на: Managing inter-object relationships

0
ответ дан 29 November 2019 в 04:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: