状態遷移の応用技術「状態キュー」

727, 2023-09-05

目次

状態遷移とは?

状態遷移とは状態変数に状態の値を保存し、その値によって処理を分岐して行うことを言います。
たとえば状態変数mがあり、これに01020といった整数が保存されるとします。
そうするとこの状態変数による分岐は

m = 10;

switch (m) {
case 0: puts("nyan!"); break;
case 10: puts("wan!"); break;
case 20: puts("pipipi!"); break;
}

という感じで分岐します。
状態変数ごとに異なる処理を行うことで、処理の単位に状態を持たせて複雑な表現を行うことができます。
この状態遷移の技術はいろいろな開発で使われます。ゲーム開発でも使われますし、普通のデスクトップアプリでも使われることがある汎用性の高い技術です。

状態遷移の応用「状態キュー」

この状態遷移の応用技術として状態キューを思いつきました。
すでに存在する技術かどうかは不明です。
ゲーム開発中にこの技術を思いつきこの技術で実装してみたところ、実装がはかどったので紹介いたします。

状態キューとは状態変数をキューにしたものです。
キューは状態を表すオブジェクトを格納し、ループの中でキューの先頭から状態を取り出し、何か状態が必要ならキューの末尾に状態を追加します。

単一の状態変数と比較してキューにすると良いところは、複数の状態をあらかじめキューに予約できるところです。
これはプリンターのジョブ管理などによく似ています。プリンターのジョブも状態ということができそうです。

ではC++で実装してみましょう。

#include <iostream>
#include <list>
#include <unistd.h>

using namespace std;

// 状態変数のタイプ
typedef enum {
    M_FIRST,
    M_SECOND,
    M_THIRD,
    M_HELLO,
} ModeType;

class Mode {
public:
    ModeType mType;  // 状態変数のタイプ
    int mData;  // 状態から他の状態に渡すデータ

    Mode(ModeType type) : mType(type), mData() {}
    Mode(ModeType type, int data) : mType(type), mData(data) {}
};

int main() {
    list<Mode> queue = {};
    queue.push_back(Mode(M_FIRST, 0));  // 開始する状態をキューに登録

    for (;;) {
        // キューが空なら何もしない
        if (queue.empty()) {
            sleep(1);
            continue;
        }

        Mode mode = queue.front();  // キューの先頭から取り出す
        queue.pop_front();  // 先頭のモードを削除

        switch (mode.mType) {
        case M_FIRST:
            printf("mode=first, data=%d\n", mode.mData);
            // キューに複数の状態を予約できる
            queue.push_back(Mode(M_SECOND, 1));
            queue.push_back(Mode(M_HELLO));
            break;
        case M_SECOND:
            printf("mode=second, data=%d\n", mode.mData);
            queue.push_back(Mode(M_THIRD, 2));
            break;
        case M_THIRD:
            printf("mode=third, data=%d\n", mode.mData);
            queue.push_back(Mode(M_FIRST, 3));
            break;
        case M_HELLO:
            puts("hello!");
            break;
        }

        sleep(1);
    }
    return 0;
}

このプログラムを実行すると以下のようになります。

mode=first, data=0
mode=second, data=1
hello!
mode=third, data=2
mode=first, data=3
mode=second, data=1
hello!
mode=third, data=2
mode=first, data=3
mode=second, data=1
... (以下、延々と続く)

状態を表すクラスがModeですが、これにはmTypemDataというメンバ変数があります。
mTypeは状態の型、具体的に何の状態なのかを表す変数です。
mDataは状態をキューにプッシュしたときに、他の状態に渡したいデータを指定します。
こうすると状態から別の状態へとデータを伝搬させて、データを加工処理したり利用したりすることが可能になります。

M_FIRSTの状態の時にキューに状態を2つプッシュしています。
このように複数の状態を1つの状態から予約することが可能になり、より複雑な状態遷移が可能になるという寸法です。
ゲーム開発などでは単一の状態変数では表現が難しい状態管理も必要ですので、このようなキューを使うと実装が捗るかもしれません。

おわりに

今回は状態キューを解説しました。
状態遷移の発展版ですが使ってみるとけっこう便利でした。
なにか参考になれば幸いです。



この記事のアンケートを送信する