코드 전체에서 일회성 플래그 및 검사를 피하려면 어떻게해야합니까?


18

하스 스톤 과 같은 카드 게임을 고려하십시오 .

다양한 작업을 수행하는 수백 개의 카드가 있으며 그 중 일부는 단일 카드에서도 고유합니다! 예를 들어, 플레이어 회전 수를 15 초로 줄이는 카드 (Nozdormu)가 있습니다!

이렇게 다양한 잠재적 효과가있을 때 코드 전체에서 마법의 숫자와 일회성 검사를 피하는 방법은 무엇입니까? PlayerTurnTime 클래스에서 "Check_Nozdormu_In_Play"메소드를 어떻게 피합니까? 그리고 더 많은 효과 를 추가 할 때 이전에 지원하지 않았던 것들을 지원하기 위해 핵심 시스템을 리팩토링 할 필요가 없도록 코드를 어떻게 구성 할 수 있습니까?


이것이 실제로 성능 문제입니까? 거의 모든 시간에 현대의 CPU로 엄청난 양의 일을 할 수 있습니다.
Jari Komppa

11
성능 문제에 대해 누가 말했습니까? 내가 볼 수있는 주요 문제는 새 카드를 만들 때마다 모든 코드를 지속적으로 조정해야한다는 것입니다.
jhocking

2
스크립팅 언어를 추가하고 각 카드를 스크립팅하십시오.
Jari Komppa

1
적절한 대답을 할 시간은 없지만 플레이어 턴을 처리하는 "PlayerTurnTime"클래스 코드 내에서 Nozdormu 확인 및 15 초 조정 대신 "PlayerTurnTime"클래스를 코딩하여 [class-, ] 특정 지점에서 외부에서 제공되는 기능. 그런 다음 Nozdormu 카드 코드 (및 동일한 플레인에 영향을 미치는 다른 모든 카드)는 해당 조정을위한 기능을 구현하고 필요할 때 PlayerTurnTime 클래스에 해당 기능을 삽입 할 수 있습니다. 고전적인 디자인 패턴 책
Peteris

2
언젠가는 임의의 코드에 애드혹 검사를 추가하는 것이 가장 간단한 해결책 인지 궁금합니다 .
user253751

답변:


12

엔터티 구성 요소 시스템 및 이벤트 메시징 전략을 살펴 보셨습니까?

상태 효과는 OnCreate () 메서드에서 영구 효과를 적용하고, OnRemoved ()에서 효과를 만료시키고, 게임 이벤트 메시지를 구독하여 발생하는 결과에 대한 반응으로 발생하는 효과를 적용 할 수있는 일종의 구성 요소 여야합니다.

효과가 지속적으로 조건부 (X 턴 동안 지속되지만 특정 상황에서만 적용됨) 인 경우 다양한 단계에서 해당 조건을 확인해야 할 수 있습니다.

그런 다음 게임에 기본 마법 번호도 없는지 확인하십시오. 변경할 수있는 모든 것이 예외에 사용되는 변수를 가진 하드 코딩 된 기본값이 아닌 데이터 기반 변수인지 확인하십시오.

이런 식으로, 회전 길이가 무엇인지 가정하지 마십시오. 그것은 항상 어떤 변수에 의해 변경 될 수 있고 나중에 만료 될 때 효과에 의해 취소 될 수있는 지속적으로 확인되는 변수입니다. 마법 번호로 기본 설정하기 전에 예외를 확인하지 마십시오.


2
"변경할 수있는 모든 것이 예외에 사용되는 변수를 가진 하드 코딩 된 기본값이 아닌 데이터 기반 변수인지 확인하십시오." -나도 그렇게 좋아. 그것은 많은 도움이된다고 생각합니다!
Sable Dreamer

"지속적인 효과 적용"에 대해 자세히 설명해 주시겠습니까? turnStarted를 구독하지 않고 길이 값을 변경하지 않으면 코드를 디버깅 할 수 없게 만들고 더 유사한 결과를 만들지 못합니다 (유사한 효과 사이에서 상호 작용할 때)?
wondra

주어진 시간 범위를 가정하는 가입자에게만 해당됩니다. 신중하게 모델링해야합니다. 현재의 회전 시간을 플레이어의 회전 시간과 다르게하는 것이 좋습니다. 새로운 턴을 만들기 위해 PTT를 점검합니다. CTT는 카드로 확인할 수 있습니다. 효과가 현재 시간을 늘려야하는 경우 타이머 UI는 상태가없는 경우 자연스럽게 따라야합니다.
RobStone

질문에 더 잘 대답하십시오. 다른 어떤 것도 턴 타임이나 그에 근거한 어떤 것도 저장하지 않습니다. 항상 확인하십시오.
RobStone

11

RobStone은 올바른 길을 가고 있지만, 무기와 주문에 대한 매우 복잡한 효과 시스템을 가진 Roguelike 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

그런 다음 효과가 발생할 때 효과 처리를 일반 루틴으로 처리하십시오. 바보처럼, 나는 큰 사건 / 스위치 진술을 사용했습니다 :

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 클래스와 onExecute () 메소드가있는 DamageEffect 클래스가 있으므로 처리 코드에서 방금 진행했습니다.

Effect effect = card.getActiveEffect();

effect.onExecute();

플레이중인 것을 아는 방법은 Vector / Array / linked list / etc를 만드는 것입니다. 특정 효과 (플레이 필드 / "게임"포함)에 부착 된 활성 효과 (기본 유형의 기본 효과)를 사용하므로 특정 효과가 재생 중인지 확인하지 않고 첨부 된 모든 효과를 반복합니다. 객체를 실행시킵니다. 효과가 개체에 연결되어 있지 않으면 재생되지 않습니다.

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에 계속 추가 할 수 있습니다. 또는 { "isAThis", "isAThat"} 또는 {IS_A_THIS, IS_A_THAT}와 같은 문자열 또는 열거 형 요소 목록을 가질 수 있습니다. 이렇게하면 필드를 추가하지 않고 열거 (또는 문자열 const)에 새로운 것을 추가 할 수 있습니다. 필드 추가에 실제로 문제가있는 것은 아닙니다 ...

함수 포인터 고려

플래그 또는 열거 목록 대신 다른 컨텍스트에서 해당 항목에 대해 실행할 조치 목록이있을 수 있습니다. (엔티티 -ish…)

객체를 고려

일부 사람들은 데이터 중심, 스크립트 또는 구성 요소 개체 접근 방식을 선호합니다. 그러나 구식 개체 계층도 고려할 가치가 있습니다. 기본 수업은“턴 페이즈 B를 위해이 카드를 사용하십시오”와 같은 행동을 받아 들여야합니다. 그런 다음 각 종류의 카드를 무시하고 적절하게 응답 할 수 있습니다. 플레이어 객체와 게임 객체도있을 수 있으므로 게임은 if (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
}

보시다시피, 게임 (또는 플레이어, 카드, & c)에 대한 최상위 API 호출을 안정적으로 유지하는 것이 단위 테스트 전략의 핵심입니다.


0

각 카드를 개별적으로 생각하는 대신 효과 범주 측면에서 생각을 시작하십시오. 카드에는 이러한 범주 중 하나 이상이 포함됩니다. 예를 들어, 한 턴의 시간을 계산하기 위해 사용중인 모든 카드를 반복하고 해당 카테고리를 포함하는 각 카드의 "턴 지속 시간 조작"카테고리를 확인할 수 있습니다. 그런 다음 각 카드는 결정한 규칙에 따라 턴 지속 시간을 늘리거나 덮어 씁니다.

이것은 본질적으로 미니 컴포넌트 시스템으로, 각 "카드"오브젝트는 단순히 여러 이펙트 컴포넌트를위한 컨테이너입니다.


카드와 미래의 카드도 거의 모든 작업을 수행 할 수 있기 때문에 각 카드에 스크립트가 있어야합니다. 아직도 이것이 실제 성능 문제가 아니라고 확신합니다 ..
Jari Komppa

4
주요 의견에 따르면 : (당신 이외의) 아무도 성능 문제에 대해 언급하지 않았습니다. 대안으로 전체 스크립팅에 대해서는 답변에서 자세히 설명하십시오.
jhocking
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.