KI-Programmierung Teil 03: Pattern (Handlungsschablonen)

Computergegner, die stets auf dieselbe Art und Weise reagieren, rauben dem Spieler bereits nach kurzer Spieldauer sämtlichen Spielspaß. Das gilt sowohl für das Verhalten wie auch für den Bewegungsablauf. In beiden Fällen bieten Pattern – so genannte Handlungsschablonen – einen einfachen Ausweg. Bereits in den allerersten Arcade-Games wie Space Invaders konnten Dank ihrer Hilfe interessante Flugmanöver realisiert werden.

Schritt 1 bei der Arbeit mit Handlungsschablonen besteht in der Definition der möglichen Pattern-Aktionen:

// Pattern Aktionen:

#define NumPatternActions 5

#define PatternAction_WAIT   0
#define PatternAction_UP     1
#define PatternAction_DOWN   2
#define PatternAction_LEFT   3
#define PatternAction_RIGHT  4

// wird nicht zu den eigentlichen Aktionen hinzugezählt
#define PatternAction_END   -1

Die CPatternStep-Klasse speichert eine einzelne Pattern-Aktion sowie den Zeitpunkt (bezogen auf den Beginn der Pattern-Aktivierung), ab dem die Aktion ausgeführt werden soll:

class CPatternStep
{
public:

    long          Action;
    unsigned long ActivationTime;

    CPatternStep()
    {}

    ~CPatternStep()
    {}
};

Jede Handlungsschablone setzt sich nun aus einer Reihe im Voraus definierter Aktionen zusammen. Diese werden in Form einer Liste von CPatternStep-Instanzen innerhalb der CPattern-Klasse verwaltet. Darüber hinaus bietet diese Klasse die Möglichkeit, mithilfe der Generate_RandomPattern()-Methode zufällige Bewegungsabläufe zu generieren, bzw. unter Verwendung der Init_Pattern()-Methode ein vordefiniertes Pattern einzulesen.

class CPattern
{
public:

    long NumPatternSteps;
    long NumPatternStepsMinusOne;

    CPatternStep* PatternStep;

    CPattern()
    {
        PatternStep = NULL;
    }

    ~CPattern()
    {
        delete[] PatternStep;
        PatternStep = NULL;
    }

    // Vordefiniertes Pattern initialisieren, das letzte Element
    // muss das Pattern mit PatternAction_END abchließen:
   
void Init_Pattern(long numPatternSteps, long* pListOfActions,
                      unsigned long* pListOfActivationTimes)
    {
        delete[] PatternStep;
        PatternStep = NULL;

        NumPatternSteps = numPatternSteps;
        NumPatternStepsMinusOne = NumPatternSteps-1;

        PatternStep = new CPatternStep[NumPatternSteps];

        for(long i = 0; i < NumPatternSteps; i++)
        {
            PatternStep[i].Action = pListOfActions[i];
            PatternStep[i].ActivationTime = pListOfActivationTimes[i];
        }
    }

    // Zufallspattern erzeugen:
    void Generate_RandomPattern(long numPatternSteps,
                                long MeanStepDuration,
                                long MinStepDurationBorder,
                                long MaxStepDurationBorder)
    {
        delete[] PatternStep;
        PatternStep = NULL;

        NumPatternSteps = numPatternSteps;
        NumPatternStepsMinusOne = NumPatternSteps-1;

        PatternStep = new CPatternStep[NumPatternSteps];

        for(long i = 0; i < NumPatternStepsMinusOne; i++)
        {
            // zufällige Aktion festlegen:
            PatternStep[i].Action = lrnd(0, NumPatternActions);

            // zufälligen Zeitpunkt festlegen, ab dem die Aktion
            // asugeführt werden soll:

            PatternStep[i].ActivationTime = (i+1)*MeanStepDuration +
            lrnd(-MinStepDurationBorder, MaxStepDurationBorder);
        }

        // Pattern abschließen (beenden):
        PatternStep[NumPatternStepsMinusOne].Action = PatternAction_END;

        PatternStep[NumPatternStepsMinusOne].ActivationTime =
                    NumPatternSteps*MeanStepDuration +
                    lrnd(-MinStepDurationBorder, MaxStepDurationBorder);
    }
};

Für die Verarbeitung der Handlungsschablonen ist die CPatternReader-Klasse verantwortlich. Soll nun ein neues Pattern abgearbeitet werden, muss im ersten Schritt mittels der Activate_Pattern()-Methode der Startzeitpunkt festgelegt werden, ab dem die einzelnen Pattern-Aktionen ausgeführt werden sollen. Im weiteren Verlauf kann mithilfe der Read_Actual_Element()-Methode die momentan durchzuführende Pattern-Aktion ausgelesen werden. Liefert diese Methode schließlich den Wert PatternAction_END zurück, ist es an der Zeit, den Startzeitpunkt für ein neues Pattern festzulegen.

class CPatternReader
{
public:

    long ActualElement;
    unsigned long InitialTime;

    CPatternReader()
    {}

    ~CPatternReader()
    {}

    void Activate_Pattern(unsigned long initialTime)
    {
        InitialTime = initialTime;
        ActualElement = 0;
    }

    long Read_Actual_Element(CPattern* Pattern)
    {
        if(ActualElement < Pattern->NumPatternStepsMinusOne)
        {
            if(GetTickCount()-InitialTime >
               Pattern->PatternStep[ActualElement].ActivationTime)
                ActualElement++;
        }

        return  Pattern->PatternStep[ActualElement].Action;
    }
};