Що я можу зробити, щоб уникнути одноразових прапорців та перевірок у всьому коді?


18

Розгляньте карткову гру, наприклад, Hearthstone .

Є сотні карток, які роблять найрізноманітніші речі, деякі з яких унікальні навіть для однієї карти! Наприклад, є картка (звана Nozdormu), яка зменшує кількість поворотів гравця до лише 15 секунд!

Якщо у вас є така велика кількість потенційних ефектів, як уникнути магічних чисел та одноразових перевірок у всьому коді? Як уникнути методу "Check_Nozdormu_In_Play" у класі PlayerTurnTime? І як можна організувати код таким чином, що коли ви додаєте ще більше ефектів, вам не потрібно переробляти основні системи, щоб підтримувати речі, які їм ніколи не доводилося підтримувати?


Це справді проблема продуктивності? Я маю на увазі, ви можете робити шалену кількість речей із сучасними процесорами майже за
короткий

11
Хто щось сказав про проблеми з роботою? Основна проблема, яку я бачив, - це постійно потребувати коригування всього коду кожного разу, коли ви робите нову картку.
поштовх

2
тому додайте мову сценаріїв і скрипт кожної картки.
Jari Komppa

1
Немає часу зробити належну відповідь, але замість того, щоб перевірити Nozdormu та 15-секундне регулювання всередині коду класу "PlayerTurnTime", який обробляє програвач, ви можете кодувати клас "PlayerTurnTime", щоб викликати [class-, якщо ви хочете ] функція, що подається ззовні в певні точки. Тоді код картки Nozdormu (і всі інші карти, які повинні впливати на ту саму планку) може реалізувати функцію для цього регулювання та при необхідності ввести цю функцію в клас PlayerTurnTime. Можливо, буде корисно почитати про схему стратегії та ін'єкцію залежностей із класичної книги "Шаблони дизайну"
Peteris

2
У якийсь момент мені потрібно задуматися, чи додавання спеціальних перевірок до відповідних бітів коду є найпростішим рішенням.
користувач253751

Відповіді:


12

Ви вивчали компоненти компонентних систем та стратегії обміну подіями?

Ефекти статусу повинні бути деякими компонентами, які можуть застосовувати свої стійкі ефекти в методі OnCreate (), закінчувати свої ефекти в OnRemoved () і підписуватись на повідомлення про події гри, щоб застосовувати ефекти, які виникають як реакція на щось, що відбувається.

Якщо ефект стійкий до умовного (триває Х витків, але застосовується лише за певних обставин), можливо, вам доведеться перевірити ці умови на різних фазах.

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

Таким чином, ви ніколи не припускаєте, якою буде довжина повороту. Це завжди постійно перевіряється змінна, яку можна змінити будь-яким ефектом і, можливо, відмінити пізніше ефект, коли він закінчується. Ви ніколи не перевіряєте винятки, перш ніж дефолтуватимете свій магічний номер.


2
"Переконайтеся, що все, що можна змінити, - це змінна, керована даними, а не жорсткі коди за замовчуванням із змінними, які використовуються для будь-яких винятків." - О, мені це дуже подобається. Це дуже допомагає, я думаю!
Сабо Сонник

Не могли б ви детальніше зупинитися на "застосуванні їх стійких ефектів"? Не хочете підписатися на TurnStarted, а потім змінити значення довжини, зробить код незаперечним і, що ще гірше, призведе до непослідовних результатів (при взаємодії між подібними ефектами)?
wondra

Лише для абонентів, які передбачають будь-який проміжок часу. Ви повинні ретельно моделювати. Може бути добре, щоб поточний час повороту відрізнявся від часу повороту гравця. PTT буде перевірено, щоб створити новий виток. CTT можна перевірити картками. Якщо ефект повинен збільшити поточний час, користувальницький інтерфейс таймера повинен, природно, слідувати відповідності, якщо він без стану.
RobStone

Щоб краще відповісти на питання. Ніщо інше не зберігає час обігу або щось, що базується на цьому. Завжди перевіряйте це.
RobStone

11

RobStone на вірному шляху, але я хотів доопрацювати, оскільки це саме те, що я робив, коли писав Dungeon Ho !, розбійник, який мав дуже складну систему ефектів для зброї та заклинань.

Кожна карта повинна мати доданий до неї набір ефектів, визначений таким чином, щоб він міг вказувати, що таке ефект, на що націлений, як і на який термін. Наприклад, ефект "пошкодження противника" може виглядати приблизно так;

Effect type: deal damage (enumeration, string, what-have-you)
Effect amount: 20
Source: my weapon
Target: opponent
Effect Cost: 20
Cost Type: Mana

Тоді, коли ефект спрацьовує, потрібно загальну рутину обробляти обробкою ефекту. Як ідіот, я використав величезний випадок case / switch:

switch (effect_type)
{
     case DAMAGE:

     break;
}

Але набагато кращий і більш модульний спосіб зробити це через поліморфізм. Створіть клас Effect, який охоплює всі ці дані, створіть підклас для кожного типу ефектів, а потім перекладіть цей клас на метод onExecute (), характерний для класу.

class Effect
{
    Object source;
    int amount;

    public void onExecute(Object target)
    {
          // Do nothing
    }
}

class DamageEffect extends Effect
{
    public void onExecute(Object target)
    {
          target.health -= amount;
    }
}

Отже, у нас був би базовий клас Effect, потім клас DamageEffect методом onExecute (), тому в нашому коді обробки ми просто переходимо;

Effect effect = card.getActiveEffect();

effect.onExecute();

Спосіб вирішити питання про те, що грає, це створити вектор / масив / пов'язаний список / тощо. активних ефектів (типу Effect, базовий клас), прикріплених до будь-якого об'єкта (включаючи ігрове поле / "гру"), тому замість того, щоб перевіряти, чи є певний ефект у грі, ви просто перегляньте всі ефекти, що додаються до об'єкт (-ів) і нехай вони виконують. Якщо ефект не додається до об'єкта, він не відтворюється.

Effect effect;

for (int o = 0; o < objects.length; o++)
{
    for (int e = 0; e < objects[o].effects.length; e++)
    {
         effect = objects[o].effects[e];

         effect.onExecute();
    }
}

Саме так я це зробив. У цьому полягає краса в тому, що у вас є система, керована даними, і ви можете легко регулювати логіку на основі ефекту. Зазвичай вам доведеться провести певну перевірку умов у логіці виконання ефекту, але це все ще набагато узгодженіше, оскільки ці перевірки призначені саме для відповідного ефекту.
manabreak

1

Я запропоную кілька пропозицій. Деякі з них суперечать один одному. Але, можливо, деякі корисні.

Розгляньте списки проти прапорів

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

Розгляньте списки та перерахування

Ви можете продовжувати додавати булеві поля до класу позицій, це ТАК і це. Або у вас може бути список рядків або елементів перерахунків, наприклад {"isAThis", "isAThat"} або {IS_A_THIS, IS_A_THAT}. Таким чином ви можете додавати нові в перерахунку (або рядкові строки) без додавання полів. Не те, що насправді щось не так з додаванням полів ...

Розглянемо функціональні покажчики

Замість списку прапорів або перерахунків може бути список дій, які потрібно виконати для цього елемента в різних контекстах. (Entity-ish…)

Розгляньте об’єкти

Деякі люди вважають за краще підходи, керовані даними, або сценарії, або компоненти компонентів. Але також варто врахувати старомодні ієрархії об'єктів. Базовий клас повинен прийняти такі дії, як "грати в цю карту для покрокової фази B" або будь-що інше. Тоді кожен тип карт може переосмислити та відповісти, якщо це доречно. Напевно, є також об’єкт гравця та ігровий об’єкт, тому гра може робити такі речі, як, якщо (player-> isAllowedToPlay ()) {do play ...}.

Розглянемо можливість налагодження

Колись приємна річ із купою полів прапора полягає в тому, що ви можете вивчити та роздрукувати стан кожного елемента однаково. Якщо стан представлений різними типами, або мішками компонентів, або покажчиками функцій, або перебуває в різних списках, можливо, недостатньо просто переглянути поля предмета. Це все компроміси.

Зрештою, рефакторинг: Розгляньте одиничні тести

Скільки б ви не узагальнили свою архітектуру, ви зможете уявити речі, які вона не охоплює. Тоді вам доведеться зробити рефактор. Може, небагато, може, багато.

Спосіб зробити це безпечнішим - це тест одиничних тестів. Таким чином, ви можете бути впевнені, що навіть якщо ви переставляли речі під ними (можливо, багато!), Існуюча функціональність все ще працює. Кожен блок тесту виглядає, як правило, так:

void test1()
{
   Game game;
   game.addThis();
   game.setupThat(); // use primary or backdoor API to get game to known state

   game.playCard(something something).

   int x = game.getSomeInternalState;
   assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}

Як бачимо, для забезпечення стабільності цих викликів API вищого рівня щодо гри (або гравця, картки та ін.) Є ключовим у стратегії тестування одиниць.


0

Замість того, щоб думати про кожну карту окремо, починайте роздумувати над категоріями ефектів, а картки містять одну або декілька з цих категорій. Наприклад, щоб обчислити кількість часу по черзі, ви можете переглядати всі карти, що граються, і перевіряти категорію "маніпулювати тривалістю повороту" кожної карти, що містить цю категорію. Потім кожна картка збільшує або перезаписує тривалість обороту відповідно до визначених вами правил.

Це, по суті, міні-складова система, де кожен об’єкт «картки» є просто контейнером для групи компонентів ефекту.


Оскільки картки - і майбутні картки - можуть робити практично все, я очікую, що кожна карта має сценарій. І все ж я впевнений, що це не реальна проблема продуктивності ..
Jari Komppa

4
відповідно до основних коментарів: Ніхто (крім вас) нічого не говорив про проблеми з роботою. Щодо повного сценарію як альтернативи, детальніше про це детальніше.
поштовх
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.