エンジニア

State Machine Compilerで状態遷移プログラムを出力しよう

投稿日:2014年12月22日 更新日:

今回のエンジニアブログを担当する大原です。

アプリ開発を行っていると状態遷移設計は幾度と無く出てくると思います。
そこで状態遷移プログラミングを手助けしてくれるSMC(State Machine Compiler)を紹介したいと思います。

状態遷移のプログラムを出力してくれるツールは
http://smc.sourceforge.net/
で手に入れることができます。

手に入れてきたzipファイルを適当なフォルダ(今回は[ユーザー]/work)に解凍します。
"work/smc/bin/Smc.jar"が実行ファイルです。
拡張子を見てお気づきかと思いますが。
Javaがインストールされていないと実行することができません。
もしPCにインストールされていない方が居ましたら、インストールしてください。

状態遷移ファイルの作成

状態遷移プログラムを出力するためには、元となるsmファイルをが必要です。

その中身に状態遷移フォーマットを書いていきます。

状態
{
  イベント  遷移先状態  {実行したい関数}
}  

今回は仕組みを理解するためにターン制のゲームの状態遷移を考えてみたいと思います。

まずテキストファイルでGameClass.smを作ります。
その中身に以下内容を書いてください。

//開始時の遷移状態
%start StateMap::Start
//状態遷移で実行するクラス
%class GameClass
//状態遷移で実行するクラスヘッダ(呼ばれるメソッドを記載)
%header   GameClass.h

//状態管理マップ
%map StateMap
%%
Start
{
    Init        PlayerTurn      {Init();}
}
//プレイヤーの操作可能状態
PlayerTurn
{
    Attack      EnemyTurn       {PlayerAttack();}
    Skill       EnemyTurn       {PlayerSkill();}
    Death       GameOver        {ViewGameOver();}
}
//敵の動作可能状態
EnemyTurn
{
    Attack      PlayerTurn      {EnemyAttack();}
    Skill       PlayerTurn      {EnemySkill();}
    Kill        GameClear       {ViewGameClear();}
}
//ゲームクリア状態
GameClear
{
    //状態遷移の最後の為イベント無し
}
//ゲームオーバー状態
GameOver
{
    //状態遷移の最後の為イベント無し
}

%%  
プログラム出力方法

"GameClass.sm"をworkフォルダに移動してください。
出力するためにターミナルで実行していきます。

cd ~/work/
java -jar smc/bin/Smc.jar -c++  GameClass.sm  

"GameClass_sm.h"ファイルと"GameClass_sm.cpp"が出力されたと思います。

プログラムへの組み込み方

今回はCocos2d-xを使って、簡単なテキストゲームを作りながらプログラムを書いていきます。

まずGameClass.hを書いていきます。

#include <stdio.h>
#include "cocos2d.h"
#include "GameClass_sm.h"

出力されたGameClass_sm.hをインクルードします。

class GameClass
{
    GameClassContext m_StateManager;
    long m_PlayerHp;
    long m_EnemyHp;

GameClassContextで状態を管理します。

public:
    GameClass();

    void Init();
    void PlayerAttack();
    void PlayerSkill();
    void EnemyAttack();
    void EnemySkill();
    void ViewGameOver();
    void ViewGameClear();
};

GameClass.cpp側の実装です。

#include "GameClass.h"

GameClass::GameClass()
:m_StateManager(*this)
,m_PlayerHp(2)
,m_EnemyHp(3)
{
    m_StateManager.enterStartState();   //最初の状態に初期化  
    m_StateManager.Init();  //初期化  
    while(true)
    {
        if(this->m_EnemyHp <= 0)
        {
            m_StateManager.Kill();
            break;
        }
        if(this->m_PlayerHp <= 0)
        {
            m_StateManager.Death();
            break;
        }
        if(&m_StateManager.getState() == &StateMap::PlayerTurn)
        {
            if(rand()%2)m_StateManager.Skill();
            else m_StateManager.Attack();
        }
        else if(&m_StateManager.getState() == &StateMap::EnemyTurn)
        {
            if(rand()%5)m_StateManager.Skill();
            else m_StateManager.Attack();
        }
    }
}

m_StateManagerは状態を管理することしかできません。
その為、状態の遷移判定式は自分で書く必要があります。
(※状態遷移で呼ばれた先の関数内でm_StateManagerを呼び出すと、エラーになります。)

void GameClass::Init()
{
    CCLOG("初期化");
}
void GameClass::PlayerAttack()
{
    CCLOG("プレイヤー攻撃!!(%d)",2);
    this->m_EnemyHp -= 2;
}
void GameClass::PlayerSkill()
{
    int heal = rand()%3;
    CCLOG("プレイヤースキル!!(%d)",heal);
    this->m_PlayerHp+=heal;
}
void GameClass::EnemyAttack()
{
    CCLOG("敵攻撃!!(%d)",1);
    this->m_PlayerHp -= 1;
}
void GameClass::EnemySkill()
{
    int heal = rand()%3;
    CCLOG("敵スキル!!(%d)",(heal));
    this->m_EnemyHp += heal;
}
void GameClass::ViewGameOver()
{
    CCLOG("ゲームオーバー!!");
}
void GameClass::ViewGameClear()
{
    CCLOG("ゲームクリアー!!");
}

このクラスをインスタンス化すると、LOGにゲーム結果が現れると思います。

State Machine Compilerを使えばsmファイルを更新すれば状態遷移の繋がりや、
イベントの追加が簡単に出来るため作る手間がかなり省けるんではないでしょうか。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.