遊戲人工智能編程案例精粹(修訂版) (Mat Buckland 著)

https://www.jblearning.com/catalog/productdetails/9781556220784前端

第1章 數學和物理學初探 (已看)ios

第2章 狀態驅動智能體設計 (已看)程序員

第3章 如何建立自治的可移動遊戲智能體算法

第4章 體育模擬(簡單足球)編程

第5章 圖的祕密生命設計模式

第6章 用腳本,仍是不用?這是一個問題api

第7章 概覽<<掠奪者>>遊戲網絡

第8章 實用路徑規劃架構

第9章 目標驅動智能體行爲less

第10章 模糊邏輯

參考文獻

 

第1章 數學和物理學初探

  1.1 數學

    1.1.1 笛卡爾座標系

    1.1.2 函數和方程

    1.1.3 三角學

一條射線是一條只有一個端點的直線.它是無限長的而且用一個方向(經常表示成一個歸一化的矢量)和一個原點來定義

一個角定義爲有公共原點的兩條射線的分散度

    1.1.4 矢量

struct Vector2D {
    double x;
    double y;
    Vector2D() :x(0.0), y(0.0) {}
    Vector2D(double a, double b) : x(a), y(b) {}
    // 置x和y爲0
    inline void Zero();
    // 若是x和y都爲0的話返回TRUE
    inline bool isZero() const;
    // 返回矢量的長度
    inline double Length() const;
    // 返回矢量長度的平方(從而能夠避免開方運算
    inline double LengthSq() const;
    inline void Normalize();
    // 返回this和v2的點乘值
    inline double Dot(const Vector2D & v2) const;
    // 若是v2在this矢量的順時針方向返回正值
    // 若是在逆時針方向返回負值(假設Y軸的箭頭是指向下面的,X軸指向右邊就像一個窗戶應用)
    inline int Sign(const Vector2D & v2) const;
    // 返回與this矢量正交的向量
    inline Vector2D Perp() const;
    // 調整x和y使矢量的長度不會超過最大值
    inline void Truncate(double max);
    // 返回this矢量與一個做爲參數被傳遞的矢量之間的距離
    inline double Distance(const Vector2D & v2) const;
    // 上面的平方
    inline double DistanceSq(const Vector2D & v2) const;
    // 返回與this矢量相反的矢量
    inline Vector2D GetReverse() const;
    // 咱們須要的一些操做
    const Vector2D & operator+=(const Vector2D & rhs);
    const Vector2D & operator-=(const Vector2D & rhs);
    const Vector2D & operator*=(const double & rhs);
    const Vector2D & operator/=(const double & rhs);
    bool operator==(const Vector2D & rhs) const;
    bool operator!=(const Vector2D & rhs) const;
};
Vector2D

    1.1.5 局部空間和世界空間

  1.2 物理學

    1.2.1 時間

時間是一個標量(用它的大小就能夠徹底肯定,沒有方向).今天1s的度量爲:對應於銫133原子在基態的兩個超精細級別之間轉換的9,192,631,770個輻射週期的持續時間

在計算機遊戲中時間的度量能夠是兩種方法中的一個,或者用秒(就如同真實世界中同樣),或者使用更新之間的時間間隔做爲一種虛擬秒.後一種度量方法能夠簡化許多公式,可是你不得不當心,由於除非更新速率是鎖定的,不然在速度不一樣的機器之間這個物理量是不一樣的!

    1.2.2 距離

距離(一個標量)的標準單位是m(米)

    1.2.3 質量

質量是一個標量,用千克度量,簡寫成kg.

    1.2.4 位置

你可能認爲一個對象的位置是一個容易度量的屬性,可是你要從哪裏來確切地度量它的位置呢?

物理學家經過採用物體質量的中心做爲它的位置解決了這個問題.質量的中心是物體的平衡點.假想在這一點上系一根繩子懸掛物體,物體能夠在任何一個位置都保持平衡.另外一個想象質心的好方法是把它看做物體內全部質量的平衡位置

    1.2.5 速度

速度是一個矢量(一個具備大小和方向的量),表達了距離相對於時間的變化率.度量速度的標準單位是m/s(米每秒).v = Δx/Δt

class Vehicle {
    // 在空間中用一個矢量表現車的位置
    vector m_vPosition;
    // 一個矢量表現車的速度
    vector m_vVelocity;
public:
    // 調用每一幀更新車的位置
    void Update(float TimeElapsedSinceLastUpdate) {
        m_vPosition += m_vVelocity * TimeElapsedSinceLastUpdate;
    }
};

// 使用一個恆定的更新步長進行模擬更新
void Vehicle::Update() {
    m_vPosition += m_vVelocity;
}
View Code

    1.2.6 加速度

加速度是一個矢量,表達的是在時間段上速度的變化率,用米每二次方秒來度量,寫做m/s2.a = Δv/Δt

    1.2.7 力

根據英國物理學家牛頓的學說: 外力是爲了改變物體的靜止或勻速直線運動的狀態而施加在物體上的做用.所以,力(Force)是能夠改變物體的速度或者運動線路的量.

力的單位是是牛頓,簡寫做N,被定義爲:在1s內使1kg的質量從靜止到以1m/s的速度運動所須要的力

  1.3 總結

第2章 狀態驅動智能體設計

有限狀態機,經常稱做FSM(Finite State Machine),多年來已經做爲人工智能編程者們選用的工具用於設計具備智能幻覺的遊戲智能體.你會發現從視頻遊戲的早期開始,這種或那種FSM正是每一個遊戲所選中的架構;儘管更專業的智能體結構愈來愈普及,但FSM架構還將在從此很長時間內無處不在.爲什麼會這樣?緣由以下

  [編程快速簡單]  有不少方法編碼一個有限狀態機,而且幾乎全部的有限狀態機實現都至關的簡單

  [易於調試]  由於一個遊戲智能體的行爲被分解成簡單的易於管理的塊,若是一個智能體開始變得行動怪異,會經過對每個狀態增長跟蹤代碼l來調試它.用這種方法,人工智能程序員能夠很容易跟蹤錯誤行爲出現前的事件序列,而且採起相應的行動

  [不多的計算開銷]  有限狀態機幾乎不佔用珍貴的處理器時間,由於它們本質上遵照硬件編碼的規則.除了if-this-then-that類型的思考處理以外,是不存在真正的"思考"的

  [直覺性]  人們老是天然地把事物思考爲處在一種或另外一種狀態.而且咱們也經常提到咱們本身處在這樣那樣的狀態中.有多少次你"使本身進入一種狀態"或者發現本身處於"頭腦的正確狀態".固然人類並非像有限狀態機同樣工做,可是有時候咱們發如今這種方式下考慮咱們的行爲是有用的.類似地,將一個遊戲智能體的行爲分解成一些狀態而且建立須要的規則來操做它們是至關容易的.出於一樣的緣由,有限狀態機可以使你很容易地與非程序員(例如與遊戲製片人和關卡設計師)來討論你的人工智能的設計,可以更好地進行設計概念的溝通和交流

  [靈活性]  一個遊戲智能體的有限狀態機能夠很容易地由程序員j進行調整,來達到遊戲設計者所要求的行爲.一樣經過增添新的狀態和規則也很容易擴展一個智能體的行爲的範圍.此外,當你的人工智能技術提升了,你會發現有限狀態機提供了一個堅固的支柱,使你能夠用它來組合其餘的技術,例如模糊邏輯和神經網絡.

  2.1 什麼是有限狀態機

做爲一我的工智能程序員,咱們能夠放棄有限狀態機的正式的數學定義:一個描述性的定義就足夠了:

  一個有限狀態機是一個設備,或是一個設備模型,具備有限數量的狀態,它能夠在任何給定的時間根據輸入進行操做,使得從一個狀態變換到另外一個狀態,或者是促使一個輸出或者一種行爲的發生.一個有限狀態機在任何瞬間只能處在一種狀態

  2.2 有限狀態機的實現

enum StateType{ RuanAway, Patrol, Attack };
void Agent::UpdateState(StateType CurrentState) {
    switch(CurrentState) {
    case state_RunAway:
        EvadeEnemy();
        if (Safe()) {
            ChangeState(state_Patrol);
        }
        break;
    case state_Patrol:
        FollowPatrolPath();
        if (Theatened()) {
            if (StrongerThanEnemy()) {
                ChangeState(state_Attack);
            } else {
                ChangeState(state_RunAway);
            }
        }
        break;
    case state_Attack:
        if (WeakerThanEnemy()) {
            ChangeState(state_RunAway);
        } else {
            BashEnemyOverHead();
        }
        break;
    }
}
View Code

雖然初看之下,這個方法是合理的,但當實際應用到任何一個比最簡單的遊戲稍複雜的狀況,switch/if-then 解決方法就變成了一個怪物

此外,做爲一我的工智能編程者,當處於初始進入狀態或者退出狀態時,你會經常須要一個狀態完成一個指定的行動(或多個行動).例如,當一個智能體進入逃跑狀態,你可能會但願它向空中揮動着胳膊而且喊道"啊!",當它最後逃脫了而且改變成巡邏狀態,你可能但願它發出一聲嘆息,擦去額頭的汗水,而且說"喲!".這些行爲只能是在j進入或退出逃跑狀態時出現的,而不會發生在一般的更新步驟中.所以,這個附加的函數必須被理想地創建在你的狀態機架構中.要想在switch或if-then架構中作到這些,你一定會咬牙切齒,噁心反胃,並寫出實在是很是糟糕的代碼

    2.2.1 狀態轉換表

一個用於組織狀態和影響狀態變換的更好的機制是一個狀態變換表

這個表能夠被一個智能體在規則的間隔內詢問,使得它能基於從遊戲環境中接受到的刺激進行必須的狀態轉換.每一個狀態能夠模型化爲一個分離的對象或者存在於智能體外部的函數,提供了一個清楚的和靈活的結構,這個表於前面討論的if-then/switch方法相比,將少有出現意大利麪條式的失控狀況.

    2.2.2 內置的規則

另外一種方法就是將狀態轉換規則嵌入到狀態自己的內部.

雖然每一個模塊能夠意識到任何其餘模塊的存在,但每個模塊是一個獨立的單位而且不依賴任何外部的邏輯來決定它是否應該容許本身交換到一個替代狀態所以增長狀態或者用一個徹底新的集合來交換整個的模塊集是簡單易行的

class State {
public:
    virtual void Execute(Troll * troll) = 0;
};

class Troll {
    State * m_pCurrentState;
public:
    void Update() {
        m_pCurrentState->Execute(this);
    }
    
    void ChangeState(const State * pNewState) {
        delete m_pCurrentState;
        m_pCurrentState = pNewState;
    }
};

class State_RunAway : public State {
public:
    void Execute(Troll * troll) {
        if (troll->isSafe()) {
            troll->ChangeState(new State);
        } else {
            troll->MoveAwayFromEnemy();
        }
    }
};

class State_Sleep: public State {
public:
    void Execute(Troll * troll) {
        if (troll->isThreatened()) {
            troll->ChangeState(new State_RunAway());
        } else {
            troll->Snore();
        }
    }
};
View Code

  2.3 West World 項目

    2.3.1 BaseGameEntity類

class BaseGameEntity {
private:
    // 每一個實體具備一個惟一的識別數字
    int    m_ID;
    // 這是下一個有效的ID.每次BaseGameEntity被實例化這個值就被更新
    static int m_iNextValidID;
    // 在構造函數中調用這個來確認ID被正確設置,在設置ID和增量前,
    // 它校驗傳遞給方法的值是大於仍是等於下一個有效的ID
    void SetID(int val);
public:
    BaseGameEntity(int id) {
        SetID(id);
    }
    virtual ~BaseGameEntity() {}
    // 全部的實體必須執行一個更新函數
    virtual void Update() = 0;
    int  ID() const { return m_ID; }
};
BaseGameEntity

    2.3.2 Miner類

class Miner : public BaseGameEntity {
private:
    // 指向一個狀態實例的指針
    State * m_pCurrentState;
    // 礦工當前所處的位置
    location_type m_Location;
    // 礦工的包中裝了多少自然金塊
    int    m_iGoldCarried;
    // 礦工在銀行存了多少錢
    int m_iMoneyInBank;
    // 礦工的口渴值
    int m_iThirst;
    // 礦工的疲勞值
    int m_iFatigue;
public:
    Miner(int ID);
    // 這是必須被執行的
    void Update();
    // 這個方法改變當前的狀態到一個新的狀態
    void ChangeState(State * pNewState);
    // 省略了大量的接口
};

void Miner::Update() {
    m_iThirst += 1;
    if (m_pCurrentState) {
        m_pCurrentState->Execute(this);
    }
}
Miner

    2.3.3 Miner狀態

    2.3.4 重訪問的狀態設計模式

class State {
public:
    virtual ~State() {}
    // 當狀態被進入時執行這個
    virtual void Enter(Miner *) = 0;
    // 每一更新步驟這個被礦工更新函數調用
    virtual void Execute(Miner *) = 0;
    // 當狀態退出時執行這個
    virtual void Exit(Miner *) = 0;
};

void Miner::ChangeState(State * pNewState) {
    // 調用現有狀態的退出方法
    m_pCurrentState->Exit(this);
    // 改變狀態到新的狀態
    m_pCurrentState = pNewState;
    // 調用新狀態的進入方法
    m_pCurrentState->Enter(this);
}
View Code

/* --------------------- MyClass.h ----------------- */

#ifndef MY_SINGLETON
#define MY_SINGLETON

class MyClass {
private:
    // 數據成員
    int m_iNum;
    // 構造器是私有的
    MyClass() {}
    // 拷貝構造函數和賦值運算符應該是私有的
    MyClass& operator=(const MyClass &);
public:
    // 嚴格的說,singleton的析構函數應該是私有的,可是一些編譯器
    // 處理這種狀況會出現問題,所以讓它們做爲公有的
    ~MyClass();
    // 方法
    int GetVal() const { return m_iNum; }
    static MyClass * Instance();
};

/* --------------------- MyClass.cpp ----------------- */
MyClass* MyClass::Instance() {
    static MyClass instance;
    return &instance;
}
MyClass.h

  2.4 使State基類可重用

template <class entity_type>
class State {
public:
    virtual void Enter(entity_type *) = 0;
    virtual void Execute(entity_type *) = 0;
    virtual void Exit(entity_type *) = 0;
    virtual ~State() {}
};

class EnterMineAndDigForNugget : public State<Miner> {
public:
    /* 省略 */
};
View Code

  2.5 全局狀態和狀態翻轉 (State Blip)

當設計一個有限狀態機時,你每每會由於在每個狀態中複製代碼而死掉.例如,在流行的遊戲Maxis公司的<<模擬人生>>(The Sims)中,Sim可能會感到本能的迫切要求,不得不去洗手間方便.這種急切的需求會發生在Sim的任何狀態或任何可能的時間.假設當前的設計,是爲了把這類行爲賦予挖金礦工,複製條件的邏輯將會被加進他的每個狀態,或者,放置進Miner::Update函數.雖而後面的解決方法是可接受的,但最好建立一個全局狀態,這樣每次FSM更新時就會被調用.那樣,全部用於FSM的邏輯被包含在狀態中而且不在擁有FSM的智能體類中

除了全局行爲以外,偶爾地讓智能體帶着一個條件進入一個狀態也會帶來方便,條件就是當狀態退出時,智能體返回到前一個狀態.咱們稱這種行爲爲狀態翻轉(State Blip)

class Miner : public BaseGameEntity {
private:
    State<Miner> * m_pCurrentState;
    State<Miner> * m_pPreviousState;
    State<Miner> * m_pGlobalState;
    ...
public:
    void ChangeState(State<Miner> * pNewState);
    void RevertToPreviousState();
    ...
};
View Code

  2.6 建立一個StateMachine類

經過把全部與狀態相關的數據和方法封裝到一個StateMachine類中,可使得設計更爲簡潔.這種方式下一個智能體能夠擁有一個StateMachine類的實例,而且委託它管理當前狀態,全局狀態,前面的狀態.

template <class entity_type>
class StateMachine {
private:
    // 指向擁有這個實例的智能體的指針
    entity_type * m_pOwner;
    State<entity_type> * m_pCurrentState;
    // 智能體處於的上一個狀態的記錄
    State<entity_type> * m_pPreviousState;
    // 每次FSM被更新時,這個狀態邏輯被調用
    State<entity_type> * m_pGlobalState;
public:
    StateMachine(entity_type * owner) : m_pOwner(owner),
                                                          m_pCurrentState(NULL),
                                                          m_pPreviousState(NULL),
                                                          m_pGlobalState(NULL) {}
    // 使用這些方法來初始化FSM
    void SetCurrentState(State<entity_type> * s) { m_pCurrentState = s;}
    void SetGlobalState(State<entity_type> * s) { m_pGlobalState = s; }
    void SetPreviousState(State<entity_type> * s) { m_pPrevisouState = s; }
    // 調用這個來更新FSM
    void  Update() const {
        // 若是一個全局狀態存在,調用它的執行方法
        if (m_pGlobalState) m_pGlobalState->Execute(m_pOwner);
        // 對當前的狀態相同
        if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner);
    }
    // 改變到一個新狀態
    void ChangeState(State<entity_type> * pNewState) {
        assert(pNewState && "<StateMachine::ChangeState>: trying to change to a null state");
        // 保留前一個狀態的記錄
        m_pPreviousState = m_pCurrentState;
        // 調用現有的狀態的退出方法
        m_pCurrentState->Exit(m_pOwner);
        // 改變狀態到一個新狀態
        m_pCurrentState = pNewState;
        // 調用新狀態的進入方法
        m_pCurrentState->Enter(m_pOwner);
}

    // 改變狀態回到前一個狀態
    void RevertToPreviousState() {
        ChangeState(m_pPreviousState);
    }
    // 訪問
    State<entity_type> * CurrentState() const {
        return m_pCurrentState;
    }
    State<entity_type> * GlobalState() const {
        return m_pGlobalState;
    }
    State<entity_type> * PrevisouState() const {
        return m_pPreviousState;
    }
    // 若是當前的狀態類型等於做爲指針傳遞的類的類型,返回true
    bool isInState(const State<entity_type> & st) const;
};
View Code

一個智能體所須要作的所有事情就是去擁有一個StateMachine類的實例,而且爲了獲得徹底的FSM功能,實現一個方法來更新狀態機

class Miner : public BaseGameEntity {
private:
    // state machine類的一個實例
    StateMachine<Miner> * m_pStateMachine;
    /* 無關係的細節被省略了 */
public:
    Miner(int id) : m_Location(shack),
                         m_iGoldCarried(0),
                         m_iMoneyInBank(0),
                         m_iThirst(0),
                         m_iFatigue(0),
                         BaseGameEntity(id) {
        // 創建state machine
        m_pStateMachine = new StateMachine<Miner>(this);
        m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
        m_pStateMachine->SetGlobalState(MinerGlobalState::Instance());
    }
    ~Miner() { delete m_pStateMachine; }
    void Update() {
        ++m_iThirst;
        m_pStateMachine->Update();
    }
    StateMachine<Miner> * GetFSM() const { return m_pStateMachine;}
    /* 無關係的細節被省略了 */
};
View Code

  2.7 引入Elsa

  2.8 爲你的FSM增長消息功能

設計精度的遊戲趨向於事件驅動.即當一個事件發生了(發射了武器,搬動了手柄,一個犯錯的警告,等等),事件被廣播給遊戲中相關的對象.這樣它們能夠恰當地作出反應.這些事件通常是以一個數據包的形式送出,數據包包括關於事件的信息例如什麼事件發送了它,什麼對象應該對它作出反應,實際的事件是什麼,一個時間戳,等等

事件驅動結構被廣泛選取的緣由是由於它們是高效率的.沒有事件處理,對象不得不持續地檢測遊戲世界來看是否有一個特定的行爲已經發生了.使用事件處理,對象能夠簡單地繼續它們本身的工做,知道有一個事件消息廣播給它們.而後,若是消息是與己相關的,它們能夠遵守它行事

聰明的遊戲智能體可使用一樣的概念來相互交流.當具備發送,處理和對事件作出反應的機制,很容易設計出以下行爲

  一個巫師向一個妖魔扔出火球.巫師發出一個消息給妖魔,通知它迫近的命運,所以它可能會作出相應的反應.例如,壯烈地死去

  一個足球運動員從隊友旁邊經過.傳球者能夠發送一個消息給接收者,讓他知道應該移動到什麼位置來攔截這個球,什麼時間他應該出如今那個位置

  一個受傷的步兵.他發送一個消息給他的每個通知尋求幫助.當一個救援者來到了,另外一個消息要廣播出去通知其餘的人,以便他們能夠從新開始行動了

  一個角色點燃了火柴來照亮他所走的幽暗走廊.一個延遲的消息發送出警告:他在30s內火柴將會燒到他的手指.當他收到這個消息時,若是他還拿着火柴,他的反應是扔掉火柴並悲傷地喊叫

    2.8.1 Telegram的結構

struct Telegram {
    // 發送這個telegram的實體
    int Sender;
    // 接收這個telegram的實體
    int Receiver;
    // 信息自己,全部的枚舉值都在文件中
    // "MessageType.h"
    int Msg;
    // 能夠被當即發送或者延遲一個指定數量的時間後發送的信息
    // 若是一個延遲是必須的,這個域打上時間戳,消息應該在此時間後被髮送
    double DispatchTime;
    // 任何應該伴隨着消息的額外信息
    void * ExtraInfo;
    /* 省略構造器 */
};
View Code

    2.8.2 礦工Bob和Elsa交流

    2.8.3 消息發送和管理

class EntityManager {
private:
    // to save the ol' fingers
    typedef std::map<int, BaseGameEntity *> EntityMap;
    // 促進更快的尋找存儲在std::map中的實體,
    // 其中指向實體的指針是利用實體的識別數值來交叉參考的
    EntityMap m_EntityMap;
    EntityManager() {}
    // 拷貝ctor和分配應該是私有的
    EntityManager(const EntityManager &);
    EntityManager& operator=(const EntityManager &);
public:
    static EntityManager * Instance();
    // 該方法存儲了一個指向實體的指針在std::vector中
    // m_Entities在索引位置上由實體的ID顯示(得到更快速的訪問)
    void RegisterEntity(BaseGameEntity * NewEntity);
    // 給出ID做爲一個參數,返回一個指向實體的指針
    BaseGameEntity * GetEntityFromID(int id) const;
    // 該方法從列表中移除實體
    void RemoveEntity(BaseGameEntity * pEntity);
};
// 提供了對EntityManager的一個實例的訪問
#define EntityMgr EntityManager::Instance()

Miner * Bob = new Miner(ent_Miner_Bob);
EntityMgr->RegisterEntity(Bob);

Entity * pBob = EntityMgr->GetEntityFromID(ent_Miner_Bob);

class MessageDispatcher {
private:
    // 一個std::set被用於做爲延遲的消息的容器,由於這樣的好處是能夠
    // 自動地排序和避免產生重複
    std::set<Telegram> PriorityQ;
    // 該方法被DispatchMessage或者DispatchDelaydMessage利用
    // 該方法用最新建立的telegram調用接收實體的消息處理成員函數
    // pReceiver
    void Discharge(Entity * pReceiver, const Telegram & msg);
    MessageDispatcher() {}
public:
    // 這是一個singleton類
    static MessageDispatcher * Instance();
    // 向另外一個智能體發送消息
    void DispatchMessage(double delay, int sender, int receiver, int msg, void * ExtraInfo);
    // 發送任何延遲的消息,該方法每次經過主遊戲循環被調用
    void DispatchDelayMessage();
};
// 使生活更溫馨.. 哈哈
#define Dispatch MessageDispatcher::Instance()

// 獲得一個指向信息接收者的指針
Entity * pReceiver = EntityMgr->GetEntityFromID(receiver);
// 建立一個telegram
Telegram telegram(delay, sender, receiver, msg, ExtraInfo);
// 若是不存在延遲,當即發送telegram
if (delay <= 0.0) {
    // 發送telegram到接收器
    Discharge(pReceiver, telegram);
}
// 不然,當telegram應該被髮送的時候計算時間
else {
    double CurrentTime = Clock->GetCurrentTime();
    telegram.DispatchTime = CurrentTime + delay;
    // 而且將它放入隊列
    PriorityQ.insert(telegram);
}




void MessageDispatcher::DispatchDelayMessages() {
    // 首先獲得當前的時間
    double CurrentTime = Clock->GetCurrentTime();
    // 查看隊列中是否有telegram須要發送.
    // 從隊列的前端移除全部的已通過期的telegram
    while ((PriorityQ.begin()->DispatchTime < CurrentTime) &&
             (PriorityQ.begin()->DispatchTime > 0) {
        // 從隊列的前面讀telegram
        Telegram telegram = *PriorityQ.begin();
        // 找到接收者
        Entity * pReceiver = EntityMgr->GetEntityFromID(telegram.Receiver);
        // 發送telegram到接收者
        Discharge(pReceiver, telegram);
        // 而且從隊列中移除它
        PriorityQ.erase(PriorityQ.begin());
    }
}
View Code

    2.8.4 消息處理

class BaseGameEntity {
private:
    int m_ID;
    /* 爲清晰起見,移除無關係的細節 */
public:
    // 全部的子類可使用消息交流
    virtual bool HandleMessage(const Telegram & msg) = 0;
    /* 爲清晰起見移除無關係的細節 */
};

template <class entity_type>
class State {
public:
    // 若是智能體從消息發送器中接收了一條消息執行這個
    virtual bool OnMessage(entity_type *, const Telegram &) = 0;
    /* 爲清晰起見移除無關係的細節 */
};

bool StateMachine::HandleMessage(const Telegram & msg) cosnt {
    // 首先看看當前的狀態是不是有效的而且能夠處理消息
    if (m_pCurrentState && m_pCurrentState->OnMessage(m_pOwner, msg)) {
        return true;
    }
    // 若是不是,且若是一個全局狀態被執行,發送消息給全局狀態
    if (m_pGlobalState && m_pGlobalState->OnMessage(m_pOwner, msg)) {
        return true;
    }
    return false;
}

bool Miner::HandleMessage(const Telegram & msg) {
    return m_pStateMachine->HandleMessage(msg);
}
View Code

 

    2.8.5 Elsa作晚飯

    2.8.6 總結

WestWorld1 代碼

#ifndef LOCATION_H_
#define LOCATION_H_

enum location_type {
    shack,
    goldmine,
    bank,
    saloon
};

#endif
Location.h
#ifndef NAMES_H
#define NAMES_H
#include <string>

enum {
    ent_Miner_Bob,
    ent_Elsa
};

inline std::string GetNameOfEntity(int n) {
    switch (n)
    {
    case ent_Miner_Bob:
        return "Miner Bob";
    case ent_Elsa:
        return "Elsa";
    default:
        return "UNKNOWN";
    }
}

#endif
EntityNames.h
#ifndef STATE_H_
#define STATE_H_

class Miner;

class State {
public:
    virtual ~State() {}

    // this will execute when the state is entered
    virtual void Enter(Miner *) = 0;

    // this is the state's normal update function
    virtual void Execute(Miner *) = 0;

    // this will execute when the state is exited
    virtual void Exit(Miner *) = 0;
};

#endif
State.h
#ifndef MINER_OWNED_STATES_H
#define MINER_OWNED_STATES_H
#include <iostream>
#include "State.h"


// In this state the miner will walk to a goldmine and pick up a nugget of gold.
// If the miner already has a nugget of gold he'll change state to VisitBankAndDepositGold.
// If he gets thirsty he'll change state to QuenchThirst
class EnterMineAndDigForNugget : public State {
public:
    EnterMineAndDigForNugget() { }
    EnterMineAndDigForNugget(const EnterMineAndDigForNugget &);
    EnterMineAndDigForNugget& operator=(const EnterMineAndDigForNugget &);
public:
    static EnterMineAndDigForNugget * Instance();

    virtual void Enter(Miner * miner);
    virtual void Execute(Miner * miner);
    virtual void Exit(Miner * miner);
};

#endif

// Entity will go to a bank and deposit any nuggets he is carrying.
// If the miner is subsequently wealthy enough he'll walk home,
// otherwise he'll keep going to get more gold
class VisitBankAndDepositGold : public State {
private:
    VisitBankAndDepositGold() {}
    VisitBankAndDepositGold(const VisitBankAndDepositGold &);
    VisitBankAndDepositGold& operator=(const VisitBankAndDepositGold &);
public:
    static VisitBankAndDepositGold * Instance();

    virtual void Enter(Miner * miner);
    virtual void Execute(Miner * miner);
    virtual void Exit(Miner * miner);
};

// Miner will go home and sleep until his fatigue is decreased sufficiently
class GoHomeAndSleepTilRested : public State {
private:
    GoHomeAndSleepTilRested() {}
    GoHomeAndSleepTilRested(const GoHomeAndSleepTilRested &);
    GoHomeAndSleepTilRested& operator=(const GoHomeAndSleepTilRested &);
public:
    static GoHomeAndSleepTilRested * Instance();

    virtual void Enter(Miner * miner);
    virtual void Execute(Miner * miner);
    virtual void Exit(Miner * miner);
};

class QuenchThirst : public State {
private:
    QuenchThirst() {}
    QuenchThirst(const QuenchThirst &);
    QuenchThirst& operator=(const QuenchThirst &);
public:
    static QuenchThirst * Instance();

    virtual void Enter(Miner * miner);
    virtual void Execute(Miner * miner);
    virtual void Exit(Miner * miner);
};
MinerOwnedStates.h
#ifndef BASE_GAME_ENTITY_H_
#define BASE_GAME_ENTITY_H_

class BaseGameEntity {
private:
    // every entity must have a unique identifying number
    int m_ID;

    // this is the next valid ID. Each time a BaseGameEntity is instantiated, this value is udpated
    static int m_iNextValidID;

    // this must be called within the constructor to make sure the ID is set correctly.
    // It verifies that the value passed to the method si greater or equal to the next valid ID,
    // before setting the ID and incrementing the next valid ID
    void SetID(int val);
public:
    BaseGameEntity(int id) {
        SetID(id);
    }

    virtual ~BaseGameEntity() {}

    // all entities must implement an update function
    virtual void Update() = 0;

    int ID() const {
        return m_ID;
    }
};

#endif
BaseGameEntity.h
#ifndef MINER_H_
#define MINER_H_
#include <string>
#include <cassert>
#include "BaseGameEntity.h"
#include "Location.h"

class State;

// the amount of gold a miner must have before he feels comfortable
const int ComfortLevel = 5;
// the amount of nuggets a miner can carry
const int MaxNuggets = 3;
// above this value miner is thirsty
const int ThirstLevel = 5;
// above this value a miner is sleepy
const int TirednessThreshold = 5;


class Miner : public BaseGameEntity {
private:
    State * m_pCurrentState;
    location_type m_Location;
    int m_iGoldCarried;
    int m_iMoneyInBank;
    int m_iThirst;
    int m_iFatigue;
public:
    Miner(int id);
    
    void Update();

    // this method changes the current state to the new state. It first calls the Exit() method of the current state,
    // then assign the new state to m_pCurrentState and finally calls the Enter() method of the new state
    void ChangeState(State * new_State);

    location_type Location() const { return m_Location;}
    void ChangeLocation(const location_type loc) { m_Location = loc; }

    int GoldCarried() const { return m_iGoldCarried; }
    void SetGoldCarried(const int val) { m_iGoldCarried = val; }
    void AddToGoldCarried(const int val);
    bool PocketsFull() const { return m_iGoldCarried >= MaxNuggets; }

    bool Fatigued() const;
    void DecreaseFatigue() { m_iFatigue -= 1; }
    void IncreaseFatigue() { m_iFatigue += 1; }

    int Wealth() const { return m_iMoneyInBank; }
    void SetWealth(const int val) { m_iMoneyInBank = val; }
    void AddToWealth(const int val);

    bool Thirsty() const;
    void BuyAndDrinkAWhiskey() { m_iThirst = 0; m_iMoneyInBank -= 1; }
};

#endif
Miner.h
#ifndef CONSOLE_UTILS_H
#define CONSOLE_UTILS_H
#include <Windows.h>
#include <conio.h>
#include <iostream>

inline void SetTextColor(WORD colors) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, colors);
}

inline void PressAnyKeyToContinue() {
    SetTextColor(FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN);
    std::cout << "\n\nPress any key to continue" << std::endl;
    while (!_kbhit()) {

    }
    return;
}


#endif
ConsoleUtils.h
#include <cassert>
#include "BaseGameEntity.h"

int BaseGameEntity::m_iNextValidID = 0;

void BaseGameEntity::SetID(int val) {
    assert((val >= m_iNextValidID) && "<BaseGameEntity::SetID>: invalid ID");

    m_ID = val;
    m_iNextValidID = m_ID + 1;
}
BaseGameEntity.cpp
#include <iostream>
#include "MinerOwnedStates.h"
#include "Miner.h"

#include "EntityNames.h"
#include "ConsoleUtils.h"

using std::cout;

EnterMineAndDigForNugget * EnterMineAndDigForNugget::Instance() {
    static EnterMineAndDigForNugget instance;
    return &instance;
}

void EnterMineAndDigForNugget::Enter(Miner * miner) {
    if (miner->Location() != goldmine) {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Walking to the goldmine";
        miner->ChangeLocation(goldmine);
    }
}

void EnterMineAndDigForNugget::Execute(Miner * miner) {
    miner->AddToGoldCarried(1);
    miner->IncreaseFatigue();
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Picking up a nugget";

    if (miner->PocketsFull()) {
        miner->ChangeState(VisitBankAndDepositGold::Instance());
    }

    if (miner->Thirsty()) {
        miner->ChangeState(QuenchThirst::Instance());
    }
}

void EnterMineAndDigForNugget::Exit(Miner * miner) {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": "
        << "Ah'm leaving the goldmine with mah pockets full of sweet gold";
}


VisitBankAndDepositGold * VisitBankAndDepositGold::Instance() {
    static VisitBankAndDepositGold instance;
    return &instance;
}

void VisitBankAndDepositGold::Enter(Miner * miner) {
    if (miner->Location() != bank) {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Going to the bank. Yes siree";
    }
    miner->ChangeLocation(bank);
}

void VisitBankAndDepositGold::Execute(Miner * miner) {
    miner->AddToWealth(miner->GoldCarried());
    miner->SetGoldCarried(0);

    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": "
        << "Depositing gold. Total savings now: " << miner->Wealth();

    if (miner->Wealth() >= ComfortLevel) {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": "
            << "WooHoo! Rich enough for now. Back home to mah li'lle lady";

        miner->ChangeState(GoHomeAndSleepTilRested::Instance());
    } else {
        miner->ChangeState(EnterMineAndDigForNugget::Instance());
    }
}

void VisitBankAndDepositGold::Exit(Miner * miner) {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Leavin' the bank";
}

GoHomeAndSleepTilRested * GoHomeAndSleepTilRested::Instance() {
    static GoHomeAndSleepTilRested instance;
    return &instance;
}

void GoHomeAndSleepTilRested::Enter(Miner * miner) {
    if (miner->Location() != shack) {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Walking home";
        miner->ChangeLocation(shack);
    }
}

void GoHomeAndSleepTilRested::Execute(Miner * miner) {
    if (!miner->Fatigued()) {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": "
            << "What a God darn fantastic nap! Time to find more gold";
        miner->ChangeState(EnterMineAndDigForNugget::Instance());
    } else {
        miner->DecreaseFatigue();
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "ZZZZ... ";
    }
}

void GoHomeAndSleepTilRested::Exit(Miner * miner) {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Leaving the house";
}

QuenchThirst * QuenchThirst::Instance() {
    static QuenchThirst instance;
    return &instance;
}

void QuenchThirst::Enter(Miner * miner) {
    if (miner->Location() != saloon) {
        miner->ChangeLocation(saloon);

        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Boy, ah sure is thusty! Walking to the saloon";
    }
}

void QuenchThirst::Execute(Miner * miner) {
    if (miner->Thirsty()) {
        miner->BuyAndDrinkAWhiskey();

        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "That's mighty fine sippin liquer";

        miner->ChangeState(EnterMineAndDigForNugget::Instance());
    } else {
        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
        cout << "\nError!\nError!\nError!";
    }
}

void QuenchThirst::Exit(Miner * miner) {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    cout << "\n" << GetNameOfEntity(miner->ID()) << ": " << "Leaving the saloon, feelin' good";
}
MinerOwnedStates.cpp
#include "Miner.h"
#include "MinerOwnedStates.h"


Miner::Miner(int id) : 
    BaseGameEntity::BaseGameEntity(id), 
    m_Location(shack), 
    m_iGoldCarried(0), 
    m_iMoneyInBank(0), 
    m_iThirst(0), 
    m_iFatigue(0), 
    m_pCurrentState(GoHomeAndSleepTilRested::Instance()) {}

void Miner::ChangeState(State * newState) {
    m_pCurrentState->Exit(this);
    m_pCurrentState = newState;
    m_pCurrentState->Enter(this);
}

void Miner::AddToGoldCarried(const int val) {
    m_iGoldCarried += val;
    if (m_iGoldCarried < 0) {
        m_iGoldCarried = 0;
    }
}

void Miner::AddToWealth(const int val) {
    m_iMoneyInBank += val;
    if (m_iMoneyInBank < 0) {
        m_iMoneyInBank = 0;
    }
}

bool Miner::Thirsty() const {
    if (m_iThirst >= ThirstLevel) {
        return true;
    }
    return false;
}

void Miner::Update() {
    m_iThirst += 1;

    if (m_pCurrentState) {
        m_pCurrentState->Execute(this);
    }
}

bool Miner::Fatigued() const {
    if (m_iFatigue > TirednessThreshold) {
        return true;
    }

    return false;
}
Miner.cpp
#include "Miner.h"
#include "EntityNames.h"
#include "Location.h"
#include "ConsoleUtils.h"

int main() {

    Miner miner(ent_Miner_Bob);

    for (int i = 0; i < 20; i++) {
        miner.Update();
        Sleep(1000);
    }

    PressAnyKeyToContinue();
}
Main.cpp

WestWorldWithWomen 代碼

#ifndef LOCATIONS_H
#define LOCATIONS_H

enum location_type {
    shack,
    goldmine,
    bank,
    saloon
};

#endif
Locations.h
#ifndef NAMES_H
#define NAMES_H

#include <string>

enum {
    ent_Miner_Bob,
    ent_Elsa
};

inline std::string GetNameOfEntity(int n) {
    switch (n)
    {
    case ent_Miner_Bob:
        return "Miner Bob";
    case ent_Elsa:
        return "Elsa";
    default:
        return "UNKNOWN!";
    }
}

#endif
EntityNames.h
#ifndef ENTITY_H
#define ENTITY_H

class BaseGameEntity
{
private:
    //every entity must have a unique identifying number
    int          m_ID;
    //this is the next valid ID. Each time a BaseGameEntity is instantiated
    //this value is updated
    static int  m_iNextValidID;
    //this must be called within the constructor to make sure the ID is set
    //correctly. It verifies that the value passed to the method is greater
    //or equal to the next valid ID, before setting the ID and incrementing
    //the next valid ID
    void SetID(int val);

public:
    BaseGameEntity(int id) {
        SetID(id);
    }

    virtual ~BaseGameEntity() {}

    //all entities must implement an update function
    virtual void  Update() = 0;

    int           ID()const { return m_ID; }
};
#endif
BaseGameEntity.h
#ifndef MINER_H
#define MINER_H

#include <string>
#include <cassert>

#include "BaseGameEntity.h"
#include "Locations.h"
#include "MinerOwnedStates.h"
#include "StateMachine.h"


//the amount of gold a miner must have before he feels comfortable
const int ComfortLevel = 5;
//the amount of nuggets a miner can carry
const int MaxNuggets = 3;
//above this value a miner is thirsty
const int ThirstLevel = 5;
//above this value a miner is sleepy
const int TirednessThreshold = 5;



class Miner : public BaseGameEntity
{
private:

    //an instance of the state machine class
    StateMachine<Miner>*  m_pStateMachine;

    location_type         m_Location;

    //how many nuggets the miner has in his pockets
    int                   m_iGoldCarried;

    int                   m_iMoneyInBank;

    //the higher the value, the thirstier the miner
    int                   m_iThirst;

    //the higher the value, the more tired the miner
    int                   m_iFatigue;

public:

    Miner(int id) :BaseGameEntity(id),
        m_Location(shack),
        m_iGoldCarried(0),
        m_iMoneyInBank(0),
        m_iThirst(0),
        m_iFatigue(0)
    {
        m_pStateMachine = new StateMachine<Miner>(this);

        m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());
    }

    ~Miner() { delete m_pStateMachine; }

    //this must be implemented
    void Update();

    StateMachine<Miner>*  GetFSM()const { return m_pStateMachine; }


    location_type Location()const { return m_Location; }
    void          ChangeLocation(const location_type loc) { m_Location = loc; }

    int           GoldCarried()const { return m_iGoldCarried; }
    void          SetGoldCarried(const int val) { m_iGoldCarried = val; }
    void          AddToGoldCarried(const int val);
    bool          PocketsFull()const { return m_iGoldCarried >= MaxNuggets; }

    bool          Fatigued()const;
    void          DecreaseFatigue() { m_iFatigue -= 1; }
    void          IncreaseFatigue() { m_iFatigue += 1; }

    int           Wealth()const { return m_iMoneyInBank; }
    void          SetWealth(const int val) { m_iMoneyInBank = val; }
    void          AddToWealth(const int val);

    bool          Thirsty()const;
    void          BuyAndDrinkAWhiskey() { m_iThirst = 0; m_iMoneyInBank -= 2; }

};

#endif
Miner.h
#ifndef MINERSWIFE_H
#define MINERSWIFE_H

#include "BaseGameEntity.h"
#include "StateMachine.h"
#include "Locations.h"
#include "MinersWifeOwnedStates.h"

class MinersWife : public BaseGameEntity {
private:
    // an instance of the state machine class
    StateMachine<MinersWife> * m_pStateMachine;
    location_type m_Location;
public:
    MinersWife(int id) :
        BaseGameEntity(id),
        m_Location(shack) {
        m_pStateMachine = new StateMachine<MinersWife>(this);
        m_pStateMachine->SetCurrentState(DoHouseWork::Instance());
        m_pStateMachine->SetGlobalState(WifesGlobalState::Instance());
    }
    ~MinersWife() { delete m_pStateMachine; }

    void Update();

    StateMachine<MinersWife> * GetFSM() const { return m_pStateMachine; }

    location_type Location() const { return m_Location; }
    void ChangeLocation(const location_type loc) { m_Location = loc; }
};

#endif
MinersWife.h
#ifndef STATE_H
#define STATE_H

template <class entity_type>
class State {
public:
    virtual ~State() {}
    virtual void Enter(entity_type *) = 0;
    virtual void Execute(entity_type *) = 0;
    virtual void Exit(entity_type *) = 0;
};

#endif
State.h
#ifndef MINER_OWNED_STATES_H
#define MINER_OWNED_STATES_H

#include "State.h"

class Miner;

class EnterMineAndDigForNugget : public State<Miner>
{
private:
    EnterMineAndDigForNugget() {}
    EnterMineAndDigForNugget(const EnterMineAndDigForNugget&);
    EnterMineAndDigForNugget& operator=(const EnterMineAndDigForNugget&);
public:
    static EnterMineAndDigForNugget* Instance();
    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
};

class VisitBankAndDepositGold : public State<Miner>
{
private:
    VisitBankAndDepositGold() {}
    VisitBankAndDepositGold(const VisitBankAndDepositGold&);
    VisitBankAndDepositGold& operator=(const VisitBankAndDepositGold&);
public:
    static VisitBankAndDepositGold* Instance();
    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
};

class GoHomeAndSleepTilRested : public State<Miner>
{
private:
    GoHomeAndSleepTilRested() {}
    GoHomeAndSleepTilRested(const GoHomeAndSleepTilRested&);
    GoHomeAndSleepTilRested& operator=(const GoHomeAndSleepTilRested&);
public:
    static GoHomeAndSleepTilRested* Instance();
    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
};

class QuenchThirst : public State<Miner>
{
private:
    QuenchThirst() {}
    QuenchThirst(const QuenchThirst&);
    QuenchThirst& operator=(const QuenchThirst&);
public:
    static QuenchThirst* Instance();
    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
};

#endif
MinerOwnedStates.h
#ifndef MINERSWIFE_OWNED_STATES_H
#define MINERSWIFE_OWNED_STATES_H

#include "State.h"

class MinersWife;

class WifesGlobalState : public State<MinersWife>
{
private:
    WifesGlobalState() {}
    WifesGlobalState(const WifesGlobalState&);
    WifesGlobalState& operator=(const WifesGlobalState&);

public:
    static WifesGlobalState* Instance();
    virtual void Enter(MinersWife* wife) {}
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife) {}
};

class DoHouseWork : public State<MinersWife>
{
private:
    DoHouseWork() {}
    DoHouseWork(const DoHouseWork&);
    DoHouseWork& operator=(const DoHouseWork&);
public:
    static DoHouseWork* Instance();
    virtual void Enter(MinersWife* wife);
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife);
};

class VisitBathroom : public State<MinersWife>
{
private:
    VisitBathroom() {}
    VisitBathroom(const VisitBathroom&);
    VisitBathroom& operator=(const VisitBathroom&);
public:
    static VisitBathroom* Instance();
    virtual void Enter(MinersWife* wife);
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife);
};


#endif
MinersWifeOwnedStates.h
#ifndef STATEMACHINE_H
#define STATEMACHINE_H

#include <cassert>
#include <string>
#include <iostream>

#include "State.h"

template <class entity_type>
class StateMachine {
private:
    entity_type * m_pOwner;
    State<entity_type> * m_pCurrentState;
    State<entity_type> * m_pPreviousState;
    State<entity_type> * m_pGlobalState;
public:
    StateMachine(entity_type * owner) :
        m_pOwner(owner),
        m_pCurrentState(NULL),
        m_pPreviousState(NULL),
        m_pGlobalState(NULL) {}

    virtual ~StateMachine() {}

    // use these methods to initialize the FSM
    void SetCurrentState(State<entity_type> * s) { m_pCurrentState = s; }
    void SetGlobalState(State<entity_type> * s) { m_pGlobalState = s; }
    void SetPreviousState(State<entity_type> * s) { m_pPreviousState = s; }

    void Update() const {
        // if a global state exists, call its execute method, else do nothing
        if (m_pGlobalState) {
            m_pGlobalState->Execute(m_pOwner);
        }
        // same for the current state
        if (m_pCurrentState) {
            m_pCurrentState->Execute(m_pOwner);
        }
    }

    void ChangeState(State<entity_type> * pNewState) {
        assert(pNewState && "<StateMachine::ChangeState>: trying to change NULL state");

        // keep a record of the previous state
        m_pPreviousState = m_pCurrentState;

        m_pCurrentState->Exit(m_pOwner);
        m_pCurrentState = pNewState;
        m_pCurrentState->Enter(m_pOwner);
    }

    // change state back to the previous state
    void RevertToPreviousState() {
        ChangeState(m_pPreviousState);
    }

    // returns true if the current state's type is equal to the type of the class passed as a parameter
    bool IsInState(const State<entity_type> & st) const {
        std::cout << typeid(*m_pCurrentState);
        return typeid(*m_pCurrentState) == typeid(st);
    }

    State<entity_type> * CurrentState() const { return m_pCurrentState; }
    State<entity_type> * GlobalState() const { return m_pGlobalState; }
    State<entity_type> * PreviousState() const { return m_pPreviousState; }
};

#endif
StateMachine.h
#ifndef CONSOLE_UTILS_H
#define CONSOLE_UTILS_H
#include <Windows.h>
#include <conio.h>
#include <iostream>

inline void SetTextColor(WORD colors) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, colors);
}

inline void PressAnyKeyToContinue() {
    SetTextColor(FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN);
    std::cout << "\n\nPress any key to continue" << std::endl;
    while (!_kbhit()) {

    }
    return;
}


#endif
ConsoleUtils.h
#ifndef UTILS_H
#define UTILS_H

inline double RandFloat() {
    return ((rand()) / (RAND_MAX + 1.0));
}

inline int RandInt(int x, int y) {
    assert(y >= x && "<RandInt>: y is less than x");
    return rand() % (y - x + 1) + x;
}

#endif
Utils.h
#include "BaseGameEntity.h"
#include <cassert>

int BaseGameEntity::m_iNextValidID = 0;

void BaseGameEntity::SetID(int val)
{
    //make sure the val is equal to or greater than the next available ID
    assert((val >= m_iNextValidID) && "<BaseGameEntity::SetID>: invalid ID");

    m_ID = val;

    m_iNextValidID = m_ID + 1;
}
BaseGameEntity.cpp
#include "Miner.h"
#include "ConsoleUtils.h"


//-----------------------------------------------------------------------------
void Miner::Update()
{
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);

    m_iThirst += 1;

    m_pStateMachine->Update();
}

//-----------------------------------------------------------------------------
void Miner::AddToGoldCarried(const int val)
{
    m_iGoldCarried += val;

    if (m_iGoldCarried < 0) m_iGoldCarried = 0;
}

//-----------------------------------------------------------------------------
void Miner::AddToWealth(const int val)
{
    m_iMoneyInBank += val;

    if (m_iMoneyInBank < 0) m_iMoneyInBank = 0;
}

//-----------------------------------------------------------------------------
bool Miner::Thirsty()const
{
    if (m_iThirst >= ThirstLevel) { return true; }

    return false;
}


//-----------------------------------------------------------------------------
bool Miner::Fatigued()const
{
    if (m_iFatigue > TirednessThreshold)
    {
        return true;
    }

    return false;
}
Miner.cpp
#include "MinerOwnedStates.h"
#include "State.h"
#include "Miner.h"
#include "Locations.h"
#include "EntityNames.h"

#include <iostream>
using std::cout;


#ifdef TEXTOUTPUT
#include <fstream>
extern std::ofstream os;
#define cout os
#endif

//--------------------------------------methods for EnterMineAndDigForNugget

EnterMineAndDigForNugget* EnterMineAndDigForNugget::Instance()
{
    static EnterMineAndDigForNugget instance;

    return &instance;
}

void EnterMineAndDigForNugget::Enter(Miner* pMiner)
{
    //if the miner is not already located at the goldmine, he must
    //change location to the gold mine
    if (pMiner->Location() != goldmine)
    {
        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Walkin' to the goldmine";

        pMiner->ChangeLocation(goldmine);
    }
}


void EnterMineAndDigForNugget::Execute(Miner* pMiner)
{
    //if the miner is at the goldmine he digs for gold until he
    //is carrying in excess of MaxNuggets. If he gets thirsty during
    //his digging he packs up work for a while and changes state to
    //gp to the saloon for a whiskey.
    pMiner->AddToGoldCarried(1);

    pMiner->IncreaseFatigue();

    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Pickin' up a nugget";

    //if enough gold mined, go and put it in the bank
    if (pMiner->PocketsFull())
    {
        pMiner->GetFSM()->ChangeState(VisitBankAndDepositGold::Instance());
    }

    if (pMiner->Thirsty())
    {
        pMiner->GetFSM()->ChangeState(QuenchThirst::Instance());
    }
}


void EnterMineAndDigForNugget::Exit(Miner* pMiner)
{
    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
        << "Ah'm leavin' the goldmine with mah pockets full o' sweet gold";
}



//----------------------------------------methods for VisitBankAndDepositGold

VisitBankAndDepositGold* VisitBankAndDepositGold::Instance()
{
    static VisitBankAndDepositGold instance;

    return &instance;
}

void VisitBankAndDepositGold::Enter(Miner* pMiner)
{
    //on entry the miner makes sure he is located at the bank
    if (pMiner->Location() != bank)
    {
        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Goin' to the bank. Yes siree";

        pMiner->ChangeLocation(bank);
    }
}


void VisitBankAndDepositGold::Execute(Miner* pMiner)
{
    //deposit the gold
    pMiner->AddToWealth(pMiner->GoldCarried());

    pMiner->SetGoldCarried(0);

    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
        << "Depositing gold. Total savings now: " << pMiner->Wealth();

    //wealthy enough to have a well earned rest?
    if (pMiner->Wealth() >= ComfortLevel)
    {
        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
            << "WooHoo! Rich enough for now. Back home to mah li'lle lady";

        pMiner->GetFSM()->ChangeState(GoHomeAndSleepTilRested::Instance());
    }

    //otherwise get more gold
    else
    {
        pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
    }

}


void VisitBankAndDepositGold::Exit(Miner* pMiner)
{
    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Leavin' the bank";
}


//----------------------------------------methods for GoHomeAndSleepTilRested

GoHomeAndSleepTilRested* GoHomeAndSleepTilRested::Instance()
{
    static GoHomeAndSleepTilRested instance;

    return &instance;
}

void GoHomeAndSleepTilRested::Enter(Miner* pMiner)
{
    if (pMiner->Location() != shack)
    {
        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Walkin' home";

        pMiner->ChangeLocation(shack);
    }
}

void GoHomeAndSleepTilRested::Execute(Miner* pMiner)
{
    //if miner is not fatigued start to dig for nuggets again.
    if (!pMiner->Fatigued())
    {
        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
            << "What a God darn fantastic nap! Time to find more gold";

        pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
    }

    else
    {
        //sleep
        pMiner->DecreaseFatigue();

        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "ZZZZ... ";
    }
}

void GoHomeAndSleepTilRested::Exit(Miner* pMiner)
{
    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Leaving the house";
}




//------------------------------------------------methods for QuenchThirst

QuenchThirst* QuenchThirst::Instance()
{
    static QuenchThirst instance;

    return &instance;
}

void QuenchThirst::Enter(Miner* pMiner)
{
    if (pMiner->Location() != saloon)
    {
        pMiner->ChangeLocation(saloon);

        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Boy, ah sure is thusty! Walking to the saloon";
    }
}

void QuenchThirst::Execute(Miner* pMiner)
{
    if (pMiner->Thirsty())
    {
        pMiner->BuyAndDrinkAWhiskey();

        cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "That's mighty fine sippin' liquer";

        pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
    }

    else
    {
        cout << "\nERROR!\nERROR!\nERROR!";
    }
}

void QuenchThirst::Exit(Miner* pMiner)
{
    cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Leaving the saloon, feelin' good";
}
MinerOwnedStates.cpp
#include "MinersWife.h"
#include "ConsoleUtils.h"

void MinersWife::Update() {
    SetTextColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
    m_pStateMachine->Update();
}
MinersWife.cpp
#include "MinersWife.h"
#include "Locations.h"
#include "EntityNames.h"
#include "Utils.h"



WifesGlobalState * WifesGlobalState::Instance() {
    static WifesGlobalState instance;
    return &instance;
}

void WifesGlobalState::Execute(MinersWife * wife) {
    // 1 in 10 chance of needing the bathroom
    if (RandFloat() < 0.1) {
        wife->GetFSM()->ChangeState(VisitBathroom::Instance());
    }
}

DoHouseWork * DoHouseWork::Instance() {
    static DoHouseWork instance;
    return &instance;
}

void DoHouseWork::Enter(MinersWife * wife) {

}

void DoHouseWork::Execute(MinersWife * wife) {
    switch (RandInt(0, 2))
    {
    case 0:
        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Moppin' the floor";
        break;
    case 1:
        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Washin' the dishes";
        break;
    case 2:
        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Markin' the bed";
        break;
    }
}

void DoHouseWork::Exit(MinersWife * wife) {

}

VisitBathroom * VisitBathroom::Instance() {
    static VisitBathroom instance;
    return &instance;
}

void VisitBathroom::Enter(MinersWife * wife) {
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Walkin' to the can. Need to powda mah pretty li'lle nose";
}

void VisitBathroom::Execute(MinersWife * wife) {
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Ahhhhhh! Sweet relief!";
    wife->GetFSM()->RevertToPreviousState();
}

void VisitBathroom::Exit(MinersWife * wife) {
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Leavin' the Jon";
}
MinersWifeOwnedStates.cpp
#include <iostream>
#include "Miner.h"
#include "MinersWife.h"
#include "EntityNames.h"

#include "ConsoleUtils.h"

int main() {
    Miner Bob(ent_Miner_Bob);

    MinersWife Elsa(ent_Elsa);

    for (int i = 0; i < 20; i++) {
        Bob.Update();
        Elsa.Update();

        Sleep(2000);
    }

    PressAnyKeyToContinue();
}
main.cpp

WestWorldWithMessaing 代碼

#ifndef ENTITY_H
#define ENTITY_H

#include <string>
#include "Telegram.h"

class BaseGameEntity {
private:
    int m_ID;
    static int m_iNextValidID;
    void SetID(int val);
public:
    BaseGameEntity(int id) {
        SetID(id);
    }
    virtual ~BaseGameEntity() {}
    virtual void Update() = 0;
    virtual bool HandleMessage(const Telegram & msg) = 0;
    int ID() const { return m_ID; }
};

#endif
BaseGameEntity.h
#ifndef CONSOLE_UTILS_H
#define CONSOLE_UTILS_H
#include <Windows.h>
#include <conio.h>
#include <iostream>

inline void SetTextColor(WORD colors) {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    SetConsoleTextAttribute(hConsole, colors);
}

inline void PressAnyKeyToContinue() {
    SetTextColor(FOREGROUND_BLUE | FOREGROUND_RED | FOREGROUND_GREEN);
    std::cout << "\n\nPress any key to continue" << std::endl;
    while (!_kbhit()) {

    }
    return;
}


#endif
ConsoleUtils.h
#ifndef CRUDETIMER_H
#define CRUDETIMER_H

#pragma comment(lib, "winmm.lib")
#include <Windows.h>

#define Clock CrudeTimer::Instance()

class CrudeTimer {
private:
    // set to the time (in seconds) when class is instantiated
    double m_dStartTime;

    // set the start time
    CrudeTimer() { m_dStartTime = timeGetTime() * 0.001; }

    CrudeTimer(const CrudeTimer &);
    CrudeTimer & operator=(const  CrudeTimer &);
public:
    static CrudeTimer * Instance();
    // returns how much time has elapsed since the timer was started
    double GetCurrentTime() { return timeGetTime() * 0.001 - m_dStartTime; }
};

#endif
CrudeTimer.h
#ifndef ENTITYMANAGER_H
#define ENTITYMANAGER_H

#include <map>
#include <assert.h>
#include <string>


class BaseGameEntity;

#define EntityMgr EntityManager::Instance()

class EntityManager {
private:
    EntityManager() {}
    EntityManager(const EntityManager &);
    EntityManager& operator=(const EntityManager &);

    typedef std::map<int, BaseGameEntity *> EntityMap;
    EntityMap m_EntityMap;
public:
    static EntityManager * Instance();
    void RegisterEntity(BaseGameEntity * NewEntity);
    void RemoveEntity(BaseGameEntity * pEntity);
    BaseGameEntity * GetEntityFromID(int id) const;
};

#endif
EntityManager.h
#ifndef NAMES_H
#define NAMES_H

#include <string>

enum {
    ent_Miner_Bob,
    ent_Elsa
};

inline std::string GetNameOfEntity(int n) {
    switch (n)
    {
    case ent_Miner_Bob:
        return "ent_Miner_Bob";
    case ent_Elsa:
        return "Elsa";
    default:
        return "UNKOWN";
    }
}

#endif
EntityNames.h
#ifndef LOCATIONS_H
#define LOCATIONS_H

enum location_type {
    shack,
    goldmine,
    bank,
    saloon
};

#endif
Locations.h
#ifndef MESSAGE_DISPATCHER_H
#define MESSAGE_DISPATCHER_H

#include <set>

#include "ConsoleUtils.h"
#include "Telegram.h"

class BaseGameEntity;

const double SEND_MSG_IMMEDIATELY = 0.0f;
const int NO_ADDITIONAL_INFO = 0;

#define Dispatch MessageDispatcher::Instance()

class MessageDispatcher {
private:
    // a std::set is used as the container for the delayed messages
    // because of the benefit of automatic sorting and avoidance
    // of duplicates. Messages are sorted by their dispatch time.
    std::set<Telegram> PriorityQ;

    // this method is utilized by DispatchMessage or DispatchDelayedMessages.
    // This method calls the message handling memeber function of the receiving
    // entity, pReceiver, with the newly created telegram
    void Discharge(BaseGameEntity * pReceiver, const Telegram & msg);

    MessageDispatcher() {}

    MessageDispatcher(const MessageDispatcher &);
    MessageDispatcher& operator=(const MessageDispatcher &);

public:
    static MessageDispatcher * Instance();
    void DispatchMessage(double delay, int sender, int receiver, int msg, void * ExtraInfo);
    // send out any delayed message. This method is called each time through
    // the main game loop.
    void DispatchDelayedMessages();
};

#endif
MessageDispatcher.h
#ifndef MESSAGE_TYPES
#define MESSAGE_TYPES

#include <string>

enum message_type {
    Msg_HiHoneyImHome,
    Msg_StewReady
};

inline std::string MsgToStr(int msg) {
    switch (msg)
    {
    case Msg_HiHoneyImHome:
        return "HiHoneyImHome";
    case Msg_StewReady:
        return "StewReady";
    default:
        return "Not recognized";
    }
}

#endif
MessageTypes.h
#ifndef MINER_H
#define MINER_H

#include <string>
#include <cassert>
#include <iostream>

#include "BaseGameEntity.h"
#include "Locations.h"
#include "ConsoleUtils.h"
#include "MinerOwnedStates.h"
#include "StateMachine.h"

//template <class entity_type> class State; //pre-fixed with "template <class entity_type> " for vs8 compatibility

struct Telegram;

//the amount of gold a miner must have before he feels he can go home
const int ComfortLevel = 5;
//the amount of nuggets a miner can carry
const int MaxNuggets = 3;
//above this value a miner is thirsty
const int ThirstLevel = 5;
//above this value a miner is sleepy
const int TirednessThreshold = 5;



class Miner : public BaseGameEntity
{
private:

    //an instance of the state machine class
    StateMachine<Miner>*  m_pStateMachine;

    location_type         m_Location;

    //how many nuggets the miner has in his pockets
    int                   m_iGoldCarried;

    int                   m_iMoneyInBank;

    //the higher the value, the thirstier the miner
    int                   m_iThirst;

    //the higher the value, the more tired the miner
    int                   m_iFatigue;

public:

    Miner(int id) :m_Location(shack),
        m_iGoldCarried(0),
        m_iMoneyInBank(0),
        m_iThirst(0),
        m_iFatigue(0),
        BaseGameEntity(id)

    {
        //set up state machine
        m_pStateMachine = new StateMachine<Miner>(this);

        m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance());

        /* NOTE, A GLOBAL STATE HAS NOT BEEN IMPLEMENTED FOR THE MINER */
    }

    ~Miner() { delete m_pStateMachine; }

    //this must be implemented
    void Update();

    //so must this
    virtual bool  HandleMessage(const Telegram& msg);


    StateMachine<Miner>* GetFSM()const { return m_pStateMachine; }



    //-------------------------------------------------------------accessors
    location_type Location()const { return m_Location; }
    void          ChangeLocation(location_type loc) { m_Location = loc; }

    int           GoldCarried()const { return m_iGoldCarried; }
    void          SetGoldCarried(int val) { m_iGoldCarried = val; }
    void          AddToGoldCarried(int val);
    bool          PocketsFull()const { return m_iGoldCarried >= MaxNuggets; }

    bool          Fatigued()const;
    void          DecreaseFatigue() { m_iFatigue -= 1; }
    void          IncreaseFatigue() { m_iFatigue += 1; }

    int           Wealth()const { return m_iMoneyInBank; }
    void          SetWealth(int val) { m_iMoneyInBank = val; }
    void          AddToWealth(int val);

    bool          Thirsty()const;
    void          BuyAndDrinkAWhiskey() { m_iThirst = 0; m_iMoneyInBank -= 2; }

};



#endif
Miner.h
#ifndef MINER_OWNED_STATES_H
#define MINER_OWNED_STATES_H

#include "State.h"

class Miner;
struct Telegram;

class EnterMineAndDigForNugget : public State<Miner>
{
private:
    EnterMineAndDigForNugget() {}
    EnterMineAndDigForNugget(const EnterMineAndDigForNugget&);
    EnterMineAndDigForNugget& operator=(const EnterMineAndDigForNugget&);
public:
    static EnterMineAndDigForNugget* Instance();

    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
    virtual bool OnMessage(Miner* agent, const Telegram& msg);
};

class VisitBankAndDepositGold : public State<Miner>
{
private:
    VisitBankAndDepositGold() {}
    VisitBankAndDepositGold(const VisitBankAndDepositGold&);
    VisitBankAndDepositGold& operator=(const VisitBankAndDepositGold&);
public:
    static VisitBankAndDepositGold* Instance();

    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
    virtual bool OnMessage(Miner* agent, const Telegram& msg);
};

class GoHomeAndSleepTilRested : public State<Miner>
{
private:
    GoHomeAndSleepTilRested() {}
    GoHomeAndSleepTilRested(const GoHomeAndSleepTilRested&);
    GoHomeAndSleepTilRested& operator=(const GoHomeAndSleepTilRested&);
public:
    static GoHomeAndSleepTilRested* Instance();

    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
    virtual bool OnMessage(Miner* agent, const Telegram& msg);
};

class QuenchThirst : public State<Miner>
{
private:
    QuenchThirst() {}
    QuenchThirst(const QuenchThirst&);
    QuenchThirst& operator=(const QuenchThirst&);
public:
    static QuenchThirst* Instance();

    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
    virtual bool OnMessage(Miner* agent, const Telegram& msg);
};


//------------------------------------------------------------------------
//
//  this is implemented as a state blip. The miner eats the stew, gives
//  Elsa some compliments and then returns to his previous state
//------------------------------------------------------------------------
class EatStew : public State<Miner>
{
private:
    EatStew() {}
    EatStew(const EatStew&);
    EatStew& operator=(const EatStew&);
public:

    static EatStew* Instance();

    virtual void Enter(Miner* miner);
    virtual void Execute(Miner* miner);
    virtual void Exit(Miner* miner);
    virtual bool OnMessage(Miner* agent, const Telegram& msg);
};

#endif
MinerOwnedStates.h
#ifndef MINERSWIFE_H
#define MINERSWIFE_H

#include <string>

#include "State.h"
#include "BaseGameEntity.h"
#include "Locations.h"
#include "MinersWifeOwnedStates.h"
#include "ConsoleUtils.h"
#include "Miner.h"
#include "StateMachine.h"
#include "Utils.h"


class MinersWife : public BaseGameEntity
{
private:
    StateMachine<MinersWife>* m_pStateMachine;
    location_type   m_Location;
    bool            m_bCooking;
public:
    MinersWife(int id) :m_Location(shack),
        m_bCooking(false),
        BaseGameEntity(id)
    {
        m_pStateMachine = new StateMachine<MinersWife>(this);
        m_pStateMachine->SetCurrentState(DoHouseWork::Instance());
        m_pStateMachine->SetGlobalState(WifesGlobalState::Instance());
    }

    ~MinersWife() { delete m_pStateMachine; }
    void          Update();
    virtual bool  HandleMessage(const Telegram& msg);

    StateMachine<MinersWife>* GetFSM()const { return m_pStateMachine; }

    location_type Location()const { return m_Location; }
    void          ChangeLocation(location_type loc) { m_Location = loc; }

    bool          Cooking()const { return m_bCooking; }
    void          SetCooking(bool val) { m_bCooking = val; }

};

#endif
MinersWife.h
#ifndef MINERSWIFE_OWNED_STATES_H
#define MINERSWIFE_OWNED_STATES_H

#include "State.h"

class MinersWife;


class WifesGlobalState : public State<MinersWife>
{
private:
    WifesGlobalState() {}
    WifesGlobalState(const WifesGlobalState&);
    WifesGlobalState& operator=(const WifesGlobalState&);
public:
    static WifesGlobalState* Instance();

    virtual void Enter(MinersWife* wife) {}
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife) {}
    virtual bool OnMessage(MinersWife* wife, const Telegram& msg);
};

class DoHouseWork : public State<MinersWife>
{
private:
    DoHouseWork() {}
    DoHouseWork(const DoHouseWork&);
    DoHouseWork& operator=(const DoHouseWork&);
public:
    static DoHouseWork* Instance();

    virtual void Enter(MinersWife* wife);
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife);
    virtual bool OnMessage(MinersWife* wife, const Telegram& msg);
};

class VisitBathroom : public State<MinersWife>
{
private:
    VisitBathroom() {}
    VisitBathroom(const VisitBathroom&);
    VisitBathroom& operator=(const VisitBathroom&);
public:
    static VisitBathroom* Instance();

    virtual void Enter(MinersWife* wife);
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife);
    virtual bool OnMessage(MinersWife* wife, const Telegram& msg);
};

class CookStew : public State<MinersWife>
{
private:
    CookStew() {}
    CookStew(const CookStew&);
    CookStew& operator=(const CookStew&);
public:
    static CookStew* Instance();

    virtual void Enter(MinersWife* wife);
    virtual void Execute(MinersWife* wife);
    virtual void Exit(MinersWife* wife);
    virtual bool OnMessage(MinersWife* wife, const Telegram& msg);
};

#endif
MinersWifeOwnedStates.h
#ifndef STATE_H
#define STATE_H

struct Telegram;

template <class entity_type>
class State
{
public:

    virtual ~State() {}

    virtual void Enter(entity_type*) = 0;
    virtual void Execute(entity_type*) = 0;
    virtual void Exit(entity_type*) = 0;

    //this executes if the agent receives a message from the 
    //message dispatcher
    virtual bool OnMessage(entity_type *, const Telegram &) = 0;
};

#endif
State.h
#ifndef STATEMACHINE_H
#define STATEMACHINE_H

#include <cassert>
#include <string>

#include "State.h"
#include "Telegram.h"


template <class entity_type>
class StateMachine
{
private:
    entity_type*          m_pOwner;
    State<entity_type>*   m_pCurrentState;
    State<entity_type>*   m_pPreviousState;

    //this is called every time the FSM is updated
    State<entity_type>*   m_pGlobalState;


public:

    StateMachine(entity_type* owner) :m_pOwner(owner),
        m_pCurrentState(NULL),
        m_pPreviousState(NULL),
        m_pGlobalState(NULL)
    {}

    virtual ~StateMachine() {}

    //use these methods to initialize the FSM
    void SetCurrentState(State<entity_type>* s) { m_pCurrentState = s; }
    void SetGlobalState(State<entity_type>* s) { m_pGlobalState = s; }
    void SetPreviousState(State<entity_type>* s) { m_pPreviousState = s; }

    void  Update()const
    {
        //if a global state exists, call its execute method, else do nothing
        if (m_pGlobalState)   m_pGlobalState->Execute(m_pOwner);

        //same for the current state
        if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner);
    }

    bool  HandleMessage(const Telegram& msg)const
    {
        //first see if the current state is valid and that it can handle
        //the message
        if (m_pCurrentState && m_pCurrentState->OnMessage(m_pOwner, msg))
        {
            return true;
        }

        //if not, and if a global state has been implemented, send 
        //the message to the global state
        if (m_pGlobalState && m_pGlobalState->OnMessage(m_pOwner, msg))
        {
            return true;
        }

        return false;
    }

    //change to a new state
    void  ChangeState(State<entity_type>* pNewState)
    {
        assert(pNewState && "<StateMachine::ChangeState>:trying to assign null state to current");

        //keep a record of the previous state
        m_pPreviousState = m_pCurrentState;

        //call the exit method of the existing state
        m_pCurrentState->Exit(m_pOwner);

        //change state to the new state
        m_pCurrentState = pNewState;

        //call the entry method of the new state
        m_pCurrentState->Enter(m_pOwner);
    }

    void  RevertToPreviousState()
    {
        ChangeState(m_pPreviousState);
    }

    //returns true if the current state's type is equal to the type of the
    //class passed as a parameter. 
    bool  isInState(const State<entity_type>& st)const
    {
        if (typeid(*m_pCurrentState) == typeid(st)) return true;
        return false;
    }

    State<entity_type>*  CurrentState()  const { return m_pCurrentState; }
    State<entity_type>*  GlobalState()   const { return m_pGlobalState; }
    State<entity_type>*  PreviousState() const { return m_pPreviousState; }

    //only ever used during debugging to grab the name of the current state
    std::string GetNameOfCurrentState() const
    {
        std::string s(typeid(*m_pCurrentState).name());

        //remove the 'class ' part from the front of the string
        if (s.size() > 5)
        {
            s.erase(0, 6);
        }

        return s;
    }
};




#endif
StateMachine.h
#ifndef TELEGRAM_H
#define TELEGRAM_H

struct Telegram {
    // the entity that sent this telegram
    int Sender;
    // the entity that is to receive this telegram
    int Receiver;
    // the message itself, These are all enumerated in the file "MessageTypes.h"
    int Msg;
    // messages can be dispatched immediately or delayed for a specified amount
    // of time. If a delay is necessary this field is stamped with the time
    // the message should be dispatched
    double DispatchTime;
    // any addtional information that may accompany the message
    void * ExtraInfo;

    Telegram() : DispatchTime(-1),
        Sender(-1),
        Receiver(-1),
        Msg(-1) {}

    Telegram(double time, int sender, int receiver, int msg, void * info = NULL) : DispatchTime(time),
        Sender(sender),
        Receiver(receiver),
        Msg(msg),
        ExtraInfo(info) {}
};

//these telegrams will be stored in a priority queue. Therefore the >
//operator needs to be overloaded so that the PQ can sort the telegrams
//by time priority. Note how the times must be smaller than
//SmallestDelay apart before two Telegrams are considered unique.
const double SmallestDelay = 0.25;


inline bool operator==(const Telegram& t1, const Telegram& t2)
{
    return (fabs(t1.DispatchTime - t2.DispatchTime) < SmallestDelay) &&
        (t1.Sender == t2.Sender) &&
        (t1.Receiver == t2.Receiver) &&
        (t1.Msg == t2.Msg);
}

inline bool operator<(const Telegram& t1, const Telegram& t2)
{
    if (t1 == t2)
    {
        return false;
    }

    else
    {
        return  (t1.DispatchTime < t2.DispatchTime);
    }
}

inline std::ostream& operator<<(std::ostream& os, const Telegram& t)
{
    os << "time: " << t.DispatchTime << "  Sender: " << t.Sender
        << "   Receiver: " << t.Receiver << "   Msg: " << t.Msg;

    return os;
}

//handy helper function for dereferencing the ExtraInfo field of the Telegram 
//to the required type.
template <class T>
inline T DereferenceToType(void* p)
{
    return *(T*)(p);
}


#endif
Telegram.h
#ifndef UTILS_H
#define UTILS_H

#include <cassert>
#include <iostream>

inline double RandFloat() {
    return ((rand()) / (RAND_MAX + 1.0));
}

inline int RandInt(int x, int y) {
    assert(y >= x && "<RandInt>: y is less than x");
    return rand() % (y - x + 1) + x;
}

#endif
Utils.h
#include "BaseGameEntity.h"
#include <cassert>

int BaseGameEntity::m_iNextValidID = 0;

void BaseGameEntity::SetID(int val) {
    assert((val >= m_iNextValidID) && "<BaseGameEntity::SetID: invalid ID");

    m_ID = val;
    m_iNextValidID = m_ID + 1;
}
BaseGameEntity.cpp
#include "CrudeTimer.h"

CrudeTimer * CrudeTimer::Instance() {
    static CrudeTimer instance;
    return &instance;
}
CrudeTimer.cpp
#include "EntityManager.h"
#include "BaseGameEntity.h"

EntityManager * EntityManager::Instance() {
    static EntityManager instance;
    return &instance;
}

void EntityManager::RegisterEntity(BaseGameEntity * NewEntity) {
    m_EntityMap.insert(std::make_pair(NewEntity->ID(), NewEntity));
}

void EntityManager::RemoveEntity(BaseGameEntity * pEntity) {
    m_EntityMap.erase(m_EntityMap.find(pEntity->ID()));
}

BaseGameEntity * EntityManager::GetEntityFromID(int id) const {
    // find the entity
    EntityMap::const_iterator ent = m_EntityMap.find(id);

    // assert that the entity is a member of the map
    assert((ent != m_EntityMap.end()) && "<EntityManager::GetEntityFromID>: invalid ID");

    return ent->second;
}
EntityManager.cpp
#include "MessageDispatcher.h"
#include "BaseGameEntity.h"
#include "CrudeTimer.h"
#include "EntityManager.h"
#include "Locations.h"
#include "MessageTypes.h"
#include "EntityNames.h"


//------------------------------ Instance -------------------------------------

MessageDispatcher* MessageDispatcher::Instance()
{
    static MessageDispatcher instance;

    return &instance;
}


//----------------------------- Dispatch ---------------------------------
//  
//  see description in header
//------------------------------------------------------------------------
void MessageDispatcher::Discharge(BaseGameEntity* pReceiver,
    const Telegram& telegram)
{
    if (!pReceiver->HandleMessage(telegram))
    {
        //telegram could not be handled
        std::cout << "Message not handled";
    }
}

//---------------------------- DispatchMessage ---------------------------
//
//  given a message, a receiver, a sender and any time delay , this function
//  routes the message to the correct agent (if no delay) or stores
//  in the message queue to be dispatched at the correct time
//------------------------------------------------------------------------
void MessageDispatcher::DispatchMessage(double  delay,
    int    sender,
    int    receiver,
    int    msg,
    void*  ExtraInfo)
{
    SetTextColor(BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    //get pointers to the sender and receiver
    BaseGameEntity* pSender = EntityMgr->GetEntityFromID(sender);
    BaseGameEntity* pReceiver = EntityMgr->GetEntityFromID(receiver);

    //make sure the receiver is valid
    if (pReceiver == NULL)
    {
        std::cout << "\nWarning! No Receiver with ID of " << receiver << " found";

        return;
    }

    //create the telegram
    Telegram telegram(0, sender, receiver, msg, ExtraInfo);

    //if there is no delay, route telegram immediately                       
    if (delay <= 0.0f)
    {
        std::cout << "\nInstant telegram dispatched at time: " << Clock->GetCurrentTime()
            << " by " << GetNameOfEntity(pSender->ID()) << " for " << GetNameOfEntity(pReceiver->ID())
            << ". Msg is " << MsgToStr(msg);

        //send the telegram to the recipient
        Discharge(pReceiver, telegram);
    }

    //else calculate the time when the telegram should be dispatched
    else
    {
        double CurrentTime = Clock->GetCurrentTime();

        telegram.DispatchTime = CurrentTime + delay;

        //and put it in the queue
        PriorityQ.insert(telegram);

        std::cout << "\nDelayed telegram from " << GetNameOfEntity(pSender->ID()) << " recorded at time "
            << Clock->GetCurrentTime() << " for " << GetNameOfEntity(pReceiver->ID())
            << ". Msg is " << MsgToStr(msg);

    }
}


//---------------------- DispatchDelayedMessages -------------------------
//
//  This function dispatches any telegrams with a timestamp that has
//  expired. Any dispatched telegrams are removed from the queue
//------------------------------------------------------------------------
void MessageDispatcher::DispatchDelayedMessages()
{
    SetTextColor(BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    //get current time
    double CurrentTime = Clock->GetCurrentTime();

    //now peek at the queue to see if any telegrams need dispatching.
    //remove all telegrams from the front of the queue that have gone
    //past their sell by date
    while (!PriorityQ.empty() &&
        (PriorityQ.begin()->DispatchTime < CurrentTime) &&
        (PriorityQ.begin()->DispatchTime > 0))
    {
        //read the telegram from the front of the queue
        const Telegram& telegram = *PriorityQ.begin();

        //find the recipient
        BaseGameEntity* pReceiver = EntityMgr->GetEntityFromID(telegram.Receiver);

        std::cout << "\nQueued telegram ready for dispatch: Sent to "
            << GetNameOfEntity(pReceiver->ID()) << ". Msg is " << MsgToStr(telegram.Msg);

        //send the telegram to the recipient
        Discharge(pReceiver, telegram);

        //remove it from the queue
        PriorityQ.erase(PriorityQ.begin());
    }
}
MessageDispatcher.cpp
#include "Miner.h"

bool Miner::HandleMessage(const Telegram & msg) {
    return m_pStateMachine->HandleMessage(msg);
}

void Miner::Update() {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    m_iThirst += 1;
    m_pStateMachine->Update();
}

void Miner::AddToGoldCarried(const int val) {
    m_iGoldCarried += val;
    if (m_iGoldCarried < 0) {
        m_iGoldCarried = 0;
    }
}

void Miner::AddToWealth(const int val) {
    m_iMoneyInBank += val;
    if (m_iMoneyInBank < 0) {
        m_iMoneyInBank = 0;
    }
}

bool Miner::Thirsty() const {
    if (m_iThirst >= ThirstLevel) {
        return true;
    }
    return false;
}

bool Miner::Fatigued() const {
    if (m_iFatigue > TirednessThreshold) {
        return true;
    }
    return false;
}
Miner.cpp
#include "MinerOwnedStates.h"
#include "State.h"
#include "Miner.h"
#include "Locations.h"
#include "Telegram.h"
#include "MessageDispatcher.h"
#include "MessageTypes.h"
#include "CrudeTimer.h"
#include "EntityNames.h"

#include <iostream>


//------------------------------------------------------------------------methods for EnterMineAndDigForNugget
EnterMineAndDigForNugget* EnterMineAndDigForNugget::Instance()
{
    static EnterMineAndDigForNugget instance;

    return &instance;
}


void EnterMineAndDigForNugget::Enter(Miner* pMiner)
{
    if (pMiner->Location() != goldmine)
    {
        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Walkin' to the goldmine";

        pMiner->ChangeLocation(goldmine);
    }
}

void EnterMineAndDigForNugget::Execute(Miner* pMiner)
{
    pMiner->AddToGoldCarried(1);
    pMiner->IncreaseFatigue();

    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Pickin' up a nugget";

    if (pMiner->PocketsFull())
    {
        pMiner->GetFSM()->ChangeState(VisitBankAndDepositGold::Instance());
    }

    if (pMiner->Thirsty())
    {
        pMiner->GetFSM()->ChangeState(QuenchThirst::Instance());
    }
}


void EnterMineAndDigForNugget::Exit(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
        << "Ah'm leavin' the goldmine with mah pockets full o' sweet gold";
}

bool EnterMineAndDigForNugget::OnMessage(Miner* pMiner, const Telegram& msg)
{
    //send msg to global message handler
    return false;
}

//------------------------------------------------------------------------methods for VisitBankAndDepositGold

VisitBankAndDepositGold* VisitBankAndDepositGold::Instance()
{
    static VisitBankAndDepositGold instance;
    return &instance;
}

void VisitBankAndDepositGold::Enter(Miner* pMiner)
{
    if (pMiner->Location() != bank)
    {
        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Goin' to the bank. Yes siree";
        pMiner->ChangeLocation(bank);
    }
}


void VisitBankAndDepositGold::Execute(Miner* pMiner)
{
    pMiner->AddToWealth(pMiner->GoldCarried());
    pMiner->SetGoldCarried(0);

    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
        << "Depositing gold. Total savings now: " << pMiner->Wealth();

    if (pMiner->Wealth() >= ComfortLevel)
    {
        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
            << "WooHoo! Rich enough for now. Back home to mah li'lle lady";
        pMiner->GetFSM()->ChangeState(GoHomeAndSleepTilRested::Instance());
    }
    else
    {
        pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
    }
}


void VisitBankAndDepositGold::Exit(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Leavin' the bank";
}


bool VisitBankAndDepositGold::OnMessage(Miner* pMiner, const Telegram& msg)
{
    //send msg to global message handler
    return false;
}
//------------------------------------------------------------------------methods for GoHomeAndSleepTilRested

GoHomeAndSleepTilRested* GoHomeAndSleepTilRested::Instance()
{
    static GoHomeAndSleepTilRested instance;

    return &instance;
}

void GoHomeAndSleepTilRested::Enter(Miner* pMiner)
{
    if (pMiner->Location() != shack)
    {
        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Walkin' home";

        pMiner->ChangeLocation(shack);

        //let the wife know I'm home
        Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY, //time delay
            pMiner->ID(),        //ID of sender
            ent_Elsa,            //ID of recipient
            Msg_HiHoneyImHome,   //the message
            NO_ADDITIONAL_INFO);
    }
}

void GoHomeAndSleepTilRested::Execute(Miner* pMiner)
{
    //if miner is not fatigued start to dig for nuggets again.
    if (!pMiner->Fatigued())
    {
        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": "
            << "All mah fatigue has drained away. Time to find more gold!";

        pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
    }

    else
    {
        //sleep
        pMiner->DecreaseFatigue();

        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "ZZZZ... ";
    }
}

void GoHomeAndSleepTilRested::Exit(Miner* pMiner)
{
}


bool GoHomeAndSleepTilRested::OnMessage(Miner* pMiner, const Telegram& msg)
{
    SetTextColor(BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    switch (msg.Msg)
    {
    case Msg_StewReady:

        std::cout << "\nMessage handled by " << GetNameOfEntity(pMiner->ID())
            << " at time: " << Clock->GetCurrentTime();

        SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);

        std::cout << "\n" << GetNameOfEntity(pMiner->ID())
            << ": Okay Hun, ahm a comin'!";

        pMiner->GetFSM()->ChangeState(EatStew::Instance());

        return true;

    }//end switch

    return false; //send message to global message handler
}

//------------------------------------------------------------------------QuenchThirst

QuenchThirst* QuenchThirst::Instance()
{
    static QuenchThirst instance;

    return &instance;
}

void QuenchThirst::Enter(Miner* pMiner)
{
    if (pMiner->Location() != saloon)
    {
        pMiner->ChangeLocation(saloon);

        std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Boy, ah sure is thusty! Walking to the saloon";
    }
}

void QuenchThirst::Execute(Miner* pMiner)
{
    pMiner->BuyAndDrinkAWhiskey();

    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "That's mighty fine sippin' liquer";

    pMiner->GetFSM()->ChangeState(EnterMineAndDigForNugget::Instance());
}


void QuenchThirst::Exit(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Leaving the saloon, feelin' good";
}


bool QuenchThirst::OnMessage(Miner* pMiner, const Telegram& msg)
{
    //send msg to global message handler
    return false;
}

//------------------------------------------------------------------------EatStew

EatStew* EatStew::Instance()
{
    static EatStew instance;

    return &instance;
}


void EatStew::Enter(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Smells Reaaal goood Elsa!";
}

void EatStew::Execute(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Tastes real good too!";

    pMiner->GetFSM()->RevertToPreviousState();
}

void EatStew::Exit(Miner* pMiner)
{
    std::cout << "\n" << GetNameOfEntity(pMiner->ID()) << ": " << "Thankya li'lle lady. Ah better get back to whatever ah wuz doin'";
}


bool EatStew::OnMessage(Miner* pMiner, const Telegram& msg)
{
    //send msg to global message handler
    return false;
}
MinerOwnedStates.cpp
#include "MinersWife.h"

bool MinersWife::HandleMessage(const Telegram & msg) {
    return m_pStateMachine->HandleMessage(msg);
}

void MinersWife::Update() {
    SetTextColor(FOREGROUND_RED | FOREGROUND_INTENSITY);
    m_pStateMachine->Update();
}
MinersWife.cpp
#include "MinersWifeOwnedStates.h"
#include "MinerOwnedStates.h"
#include "MinersWife.h"
#include "Locations.h"
#include "CrudeTimer.h"
#include "MessageDispatcher.h"
#include "MessageTypes.h"
#include "EntityNames.h"

#include <iostream>


//-----------------------------------------------------------------------Global state

WifesGlobalState* WifesGlobalState::Instance()
{
    static WifesGlobalState instance;

    return &instance;
}


void WifesGlobalState::Execute(MinersWife* wife)
{
    //1 in 10 chance of needing the bathroom (provided she is not already
    //in the bathroom)
    if ((RandFloat() < 0.1) &&
        !wife->GetFSM()->isInState(*VisitBathroom::Instance()))
    {
        wife->GetFSM()->ChangeState(VisitBathroom::Instance());
    }
}

bool WifesGlobalState::OnMessage(MinersWife* wife, const Telegram& msg)
{
    SetTextColor(BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    switch (msg.Msg)
    {
    case Msg_HiHoneyImHome:
    {
        std::cout << "\nMessage handled by " << GetNameOfEntity(wife->ID()) << " at time: "
            << Clock->GetCurrentTime();

        SetTextColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);

        std::cout << "\n" << GetNameOfEntity(wife->ID()) <<
            ": Hi honey. Let me make you some of mah fine country stew";

        wife->GetFSM()->ChangeState(CookStew::Instance());
    }

    return true;

    }//end switch

    return false;
}

//-------------------------------------------------------------------------DoHouseWork

DoHouseWork* DoHouseWork::Instance()
{
    static DoHouseWork instance;

    return &instance;
}


void DoHouseWork::Enter(MinersWife* wife)
{
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Time to do some more housework!";
}


void DoHouseWork::Execute(MinersWife* wife)
{
    switch (RandInt(0, 2))
    {
    case 0:

        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Moppin' the floor";

        break;

    case 1:

        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Washin' the dishes";

        break;

    case 2:

        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Makin' the bed";

        break;
    }
}

void DoHouseWork::Exit(MinersWife* wife)
{
}

bool DoHouseWork::OnMessage(MinersWife* wife, const Telegram& msg)
{
    return false;
}

//------------------------------------------------------------------------VisitBathroom

VisitBathroom* VisitBathroom::Instance()
{
    static VisitBathroom instance;

    return &instance;
}


void VisitBathroom::Enter(MinersWife* wife)
{
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Walkin' to the can. Need to powda mah pretty li'lle nose";
}


void VisitBathroom::Execute(MinersWife* wife)
{
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Ahhhhhh! Sweet relief!";

    wife->GetFSM()->RevertToPreviousState();
}

void VisitBathroom::Exit(MinersWife* wife)
{
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Leavin' the Jon";
}


bool VisitBathroom::OnMessage(MinersWife* wife, const Telegram& msg)
{
    return false;
}


//------------------------------------------------------------------------CookStew

CookStew* CookStew::Instance()
{
    static CookStew instance;

    return &instance;
}


void CookStew::Enter(MinersWife* wife)
{
    //if not already cooking put the stew in the oven
    if (!wife->Cooking())
    {
        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Putting the stew in the oven";

        //send a delayed message myself so that I know when to take the stew
        //out of the oven
        Dispatch->DispatchMessage(1.5,                  //time delay
            wife->ID(),           //sender ID
            wife->ID(),           //receiver ID
            Msg_StewReady,        //msg
            NO_ADDITIONAL_INFO);

        wife->SetCooking(true);
    }
}


void CookStew::Execute(MinersWife* wife)
{
    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Fussin' over food";
}

void CookStew::Exit(MinersWife* wife)
{
    SetTextColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);

    std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": Puttin' the stew on the table";
}


bool CookStew::OnMessage(MinersWife* wife, const Telegram& msg)
{
    SetTextColor(BACKGROUND_RED | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);

    switch (msg.Msg)
    {
    case Msg_StewReady:
    {
        std::cout << "\nMessage received by " << GetNameOfEntity(wife->ID()) <<
            " at time: " << Clock->GetCurrentTime();

        SetTextColor(FOREGROUND_GREEN | FOREGROUND_INTENSITY);
        std::cout << "\n" << GetNameOfEntity(wife->ID()) << ": StewReady! Lets eat";

        //let hubby know the stew is ready
        Dispatch->DispatchMessage(SEND_MSG_IMMEDIATELY,
            wife->ID(),
            ent_Miner_Bob,
            Msg_StewReady,
            NO_ADDITIONAL_INFO);

        wife->SetCooking(false);

        wife->GetFSM()->ChangeState(DoHouseWork::Instance());
    }

    return true;

    }//end switch

    return false;
}
MinersWifeOwnedState.cpp
#include "MessageTypes.h"
#include "Telegram.h"
#include <iostream>
#include <time.h>
#include "EntityNames.h"
#include "Miner.h"
#include "MinersWife.h"
#include "MessageDispatcher.h"
#include "EntityManager.h"


int main() {

    // seed random number generator
    srand((unsigned)time(NULL));

    Miner * Bob = new Miner(ent_Miner_Bob);

    MinersWife * Elsa = new MinersWife(ent_Elsa);

    EntityMgr->RegisterEntity(Bob);
    EntityMgr->RegisterEntity(Elsa);

    for (int i = 0; i < 30; i++) {
        Bob->Update();
        Elsa->Update();

        Dispatch->DispatchDelayedMessages();

        Sleep(800);
    }

    delete Bob;
    delete Elsa;

    PressAnyKeyToContinue();
}
Main.cpp

第3章 如何建立自治的可移動遊戲智能體

在20世界80年代晚期,BBC Horizon 一期紀錄片,主要講述了經典的計算機圖形和動畫.片中呈現的內容精彩紛呈,使人興奮,其中最生動的莫過於一羣鳥的羣集行爲.它的原理其實十分簡單,但看上去的確很天然逼真.節目的設計者叫Craig Reynolds.他稱鳥羣爲"boids",稱那些簡單原理爲操控行爲(Steering Behaviors)

  3.1 什麼是自治智能體

對於自治智能體的定義,有許多不一樣的版本,可是以下這個多是最好的

一個自治智能體是這樣一個系統,它位於一個環境的內部,是環境的一部分,且能感知該環境對它有效實施的做用,並永遠按此進行,爲將來的新感知提供條件

自治智能體的運行過程能夠分解成如下三個小環節

  行動選擇  該部分負責選定目標,指定計劃.它告訴咱們"到這來"和"作好A,B,而後作C".

  操控    該環節負責計算軌道數據,服務行動選擇環節指定的目標和計劃.由操控行爲執行.操控行爲產生一個操控力,它決定智能體往哪移動及如何快速移動

  移動    最後環節.主要產生一個智能體運動的機械因素,即如何從A到B.好比,若是你掌握了駱駝,坦克和金魚的機械學原理,並命令它們向北走,它們會依據各類不一樣的力學方法來產生動做,即便它們有相同的意向.將移動環節與操控環節區分開來,就頗有可能以相同的

        操控行爲來完成迥然不一樣的移動,而幾乎不須要修正

  3.2 交通工具模型

MovingEntity 是一個基類,全部可移動的遊戲智能體都繼承於它,它封裝一些數據,用來描述爲質點的基本交通工具

class MovingEntity : public BaseGameEntity {
protected:
    SVector2D m_vVelocity;
    // 一個標準化向量,指向實體的朝向
    SVector2D m_vHeading;
    //垂直於朝向向量的向量
    SVector2D m_vSide;
    double m_dMass;
    // 實體的最大速度(速率)
    double m_dMaxSpeed;
    // 實體產生的供以本身動力的最大力(想一下火箭和發送機推力)
    double m_dMaxForce;
    // 交通工具能旋轉的最大速率(弧度每秒)
    double m_dMaxTurnRate;
public:
    /* 忽略無關細節 */
};
View Code

儘管這些數據足夠表述一個可移動的對象,可是還需能夠訪問不一樣種類的操控行爲.建立一個繼承於MovingEntity的類Vehicle,它擁有操控行爲類SteeringBehaviors的實例.

class Vehicle : public BaseGameEntity {
private:
    // a pointer to the world data enabling a vehicle to access any obstacle path, wall, or agent data
    GameWorld * m_pWorld;
    // the steering behavior class
    SteeringBehaviors * m_pSteering;
public:
    // updates the vehicle's position and orientation
    void Update(double time_elapsed);
    /* EXTRANEOUS DETAIL OMITTED */
};
View Code

  3.3 更新交通工具物理屬性

bool Vehicle::Update(double time_elapsed) {
    // 計算操控行爲的協力
    SVector2D SteeringForce = m_pSteering->Calculate();
    // 加速度 = 力 / 質量
    SVector2D acceleration = SteeringForce / m_dMass;
    // 更新速度
    m_vVelocity += acceleration * time_elapsed;
    // 確保交通工具不超過最大速度
    m_vVelocity.Truncate(m_dMaxSpeed);
    // 更新位置
    m_vPos += m_vVelocity * time_elapsed;
    // 若是速度遠大於一個很小值,那麼更新朝向
    if (m_vVelocity.LengthSq() > 0.00000001) {
        m_vHeading = Vec2DNormalize(m_vVelocity);
        m_vSide = m_vHeading.Perp();
    }
    // 把屏幕看做環(toroid)
    WrapAround(m_vPos, m_pWorld->cxClient(), m_pWorld->cyClient());
}
View Code

  3.4 操控行爲

    3.4.1 Seek(靠近)

seek操控行爲返回一個操控智能體到達目標位置的力

Vector2D SteeringBehaviors::Seek(Vector2D TargetPos) {
    Vector2D DesiredVelocity = Vec2DNormalize(TargetPos -m_pVehicle->Pos()) * m_pVechicle->MaxSpeed();
    return (DesiredVelocity - m_pVehicle->Velocity());
}
View Code

    3.4.2 Flee(離開)

flee和seek相反,flee產生一個操控智能體離開的力,而不是產生靠近智能體的力

Vector2D SteeringBehaviors::Flee(Vector2D TargetPos) {
    Vector2D DesiredVelocity = Vec2DNormalize(m_pVehicle->Pos() - TargetPos) * m_pVehicle->MaxSpeed();
    return (DesiredVelocity - m_pVehicle->Velocity());
}

Vector2D SteeringBehaviors::Flee(Vector2D TargetPos) {
    // 若是目標在恐慌距離以內,那麼離開,用距離平方計算
    const double PanicDistanceSq = 100.0 * 100.0;
    if (Vec2DDistanceSq(m_pVehicle->Pos(), target) > PanicDistanceSq) {
        return Vector2D(0, 0);
    }
    Vector2D DesiredVelocity = Vec2DNormalize(m_pVehicle->Pos() - TargetPos) * m_pVehicle->MaxSpeed();
    return (DesiredVelocity - m_pVehicle->Velocity));
}
View Code

    3.4.3 Arrive(抵達)

seek行爲對於讓一個智能體向正確方向移動頗有用,可是不少狀況是,但願智能體徐緩地停在目標位置.如你所看到的,seek行爲不能很好地慢慢停下來.Arrive行爲是一種操控智能體慢慢減速直至停在目標位置的行爲.

enum Deceleration{ slow = 3, normal = 2, fast = 1 };

Vector2D SteeringBehaviors::Arrive(Vector2D TargetPos, Deceleration deceleration) {
    Vector2D ToTarget = TargetPos - m_pVehicle->Pos();
    // 計算到目標位置的距離
    double dist = ToTarget.Length();
    
    if (dist > 0) {
        // 由於枚舉Deceleration是整數int,因此須要這個值提供調整減速度
        const double DecelerationTweaker = 0.3;
        // 給定預期減速度,計算能達到目標位置所需的速度
        double speed = dist / ((double)deceleration * DecelerationTweaker);
        // 確保這個速度不超過最大值
        speed = min(speed, m_pVehicle->MaxSpeed());
        // 這邊的處理和Seek同樣,除了不須要標準化ToTarget向量,
        // 由於咱們已經費力地計算了它的長度: dist
        Vector2D DesiredVelocity = ToTarget * speed / dist;
        return (DesiredVelocity - m_pVehicle->Velocity());
    }
    return Vector2D(0, 0);
}
View Code

    3.4.4 Pursuit(追逐)

當智能體要攔截y一個可移動的目標時,pursuit行爲j就變得頗有用.固然它能向目標的當前位置靠近,可是這不能製造出智能的假象.想象你仍是個小孩,在操場上玩"單腳抓人"遊戲.當想抓住某人時,你不會直接移向他們的當前位置(有效地靠近他們).你預測他們的將來位置,而後移向那個偏移位置,其間經過不斷調整來縮短距離.

pursuit函數的成功與否取決於追逐者預測逃避者的運動軌跡有多準.這k能夠變得很複雜,因此作個折衷以獲得足夠的效率但又不會消耗t太多的時鐘週期

追逐者可能會碰到一種提早結束(enables an early out)的狀況;若是逃避者在前面,幾乎面對智能體,那麼智能體應該直接向逃避者當前位置移動.這能夠經過點積快速算出.在示範代碼中,逃避者朝向的反方向和智能體的朝向必須在20°內(近似)才被認爲是面對着的

一個好的預測的難點之一就是決定預測多遠.很明顯,這個"多遠"應該正比於追逐者與逃避者的距離,反比於追逐者和逃避者的速度.一旦這"多遠"肯定了,咱們就能夠估算出追逐者seek的位置.

 

Vector2D SteeringBehaviors::Pursuit(const Vehicle * evader) {
    // 若是逃避者在前面,並且面對着智能體
    // 那麼咱們能夠正好靠近逃避者的當前位置
    Vector2D ToEvader = evader->Pos() - m_pVehicle->Pos();

    double RelativeHeading = m_pVehicle->Heading().Dot(evader->Heading());
    if ((ToEvader.Dot(m_pVehicle->Heading()) > 0) && (RelativeHeading < -0.95)) {     // acos(0.95) = 18 degs
        return Seek(evader->Pos());
    }
    
    // 預測逃避者的位置
    
    // 預測的時間正比於逃避者和追逐者的距離;反比於智能體的速度和逃避者的速度
    double LookAheadTime = ToEvader.Length() / (m_pVehicle->MaxSpeed() + evader->Speed());
    // 如今靠近逃避者的被預測位置
    return Seek(evader->Pos() + evader->Velocity() * LookAheadTime);
}

LookAheadTime += TurnAroundTime(m_pVehicle, evader->Pos());

double TurnAroundTime(const Vehicle * pAgent, Vector2D TargetPos) {
    // 肯定到目標的標準化向量
    Vector2D toTarget = Vec2DNormalize(TargetPos - pAgent->Pos());
    double dot = pAgent->Heading().Dot(toTarget);
    // 改變這個值獲得預期行爲
    // 交通工具的最大轉彎率越高,這個值越大
    // 若是交通工具正在朝向到目標位置的反方向
    // 那麼0.5這個值意味着這個函數返回1秒的時間以便讓交通工具轉彎.
    const double coefficient = 0.5;
    // 若是目標直接在前面,那麼點積爲1,
    // 若是目標直接在後面, 那麼點積爲-1,
    // 減去1,除以負的coefficient,獲得一個正的值
    // 且正比於交通工具和目標的轉動角位移
    return (dot - 1.0) * -coefficient;
}

Vehicle * prey = new Vehicle(/* params omitted */);
prey->Steering()->WanderOn();

Vehicle * predator = new Vehicle(/* params omitted */);
predator->Steering()->PursuitOn(prey);
View Code

    3.4.5 Evade(逃避)

Vector2D SteeringBehaviors::Evade(const Vehicle * pursuer) {
    /* 沒有必要檢查面向方向 */
    Vector2D ToPursuer = pursuer->Pos() - m_pVehicle->Pos();

    double LookAheadTime = ToPursuer.Length() / (m_pVehicle->MaxSpeed() + pursuer->Speed());
    
    return Flee(pursuer->Pos() + pursuer->Velocity() * LookAheadTime);
}
View Code

    3.4.6 Wander(徘徊)

    3.4.7 Obstacle Avoidance(避開障礙)

    3.4.8 Wall Avoidance(避開牆)

    3.4.9 Interpose(插入)

    3.4.10 Hide(隱藏)

    3.4.11 Path Following(路徑跟隨)

    3.4.12 Offset Pursuit(保持必定偏移的追逐)

  3.5 組行爲(Group Behaviors)

    3.5.1 Separation(分離)

    3.5.2 Alignment(隊列)

    3.5.3 Cohesion(彙集)

    3.5.4 Flocking(羣集)

  3.6 組合操控行爲(Combining Steering Behaviors)

    3.6.1 加權截斷總和(Weighted Truncated Sum)

    3.6.2 帶優先級的加權截斷累計(Weighted Truncated Running Sum with Prioritization)

    3.6.3 帶優先級的抖動(Prioritized Dithering)

  3.7 確保無重疊

  3.8 應對大量交通工具:空間劃分

  3.9 平滑

第4章 體育模擬(簡單足球)

  4.1 簡單足球的環境和規則

    4.1.1 足球場

    4.1.2 球門

    4.1.3 足球

  4.2 設計AI

    4.2.1 SoccerTeam類

    4.2.2 場上隊員

    4.2.3 守門員

    4.2.4 AI使用到的關鍵方法

  4.3 使用估算和假設

  4.4 總結

第5章 圖的祕密生命

  5.1 圖

    5.1.1 一個更規範化的描述

    5.1.2 樹

    5.1.3 圖密度

    5.1.4 有向圖(Digraph)

    5.1.5 遊戲AI中的圖

  5.2 實現一個圖類

    5.2.1 圖節點類(GraphNodeClass)

    5.2.2 圖邊類(GraphEdgeClass)

    5.2.3 稀疏圖類(SparseGraphClass)

  5.3 圖搜索算法

    5.3.1 盲目搜索(UninformedGraphSearches)

    5.3.2 基於開銷的圖搜索(cost-based graph searchs)

  5.4 總結

第6章 用腳本,仍是不用?這是一個問題

  6.1 什麼是腳本語言

  6.2 腳本語言能爲你作什麼

    6.2.1 對話流

    6.2.2 舞臺指示(Stage Direction)

    6.2.3 AI邏輯

  6.3 在Lua中編寫腳本

    6.3.1 爲使用Lua設置編譯器

    6.3.2 起步

    6.3.3 Lua中的石頭剪子布

    6.3.4 與C/C++接口

    6.3.5 Luabind來救援了

  6.4 建立一個腳本化的有限狀態自動機

    6.4.1 它如何工做?

    6.4.2 狀態(State)

  6.5 有用的連接

  6.6 並非一切都這麼美妙

  6.7 總結

第7章 概覽<<掠奪者>>遊戲

  7.1 關於這個遊戲

  7.2 遊戲體系結構概述

    7.2.1 Raven_Game類

    7.2.2 掠奪者地圖

    7.2.3 掠奪者武器

    7.2.4 彈藥(Projectile)

  7.3 觸發器

    7.3.1 觸發器範圍類(TriggerRegion)

    7.3.2 觸發器類(Trigger)

    7.3.3 再生觸發器(Respawning Trigger)

    7.3.4 供給觸發器(Giver-Trigger)

    7.3.5 武器供給器(Weapon Givers)

    7.3.6 健康值供給器(Health Giver)

    7.3.7 限制生命期觸發器(Limited Lifetime Trigger)

    7.3.8 聲音通告觸發器(Sound Notification Trigger)

    7.3.9 管理觸發器:觸發器系統(TriggerSystem)類

  7.4 AI設計的考慮

  7.5 實現AI

    7.5.1 制定決策(DecisionMaking)

    7.5.2 移動(Movement)

    7.5.3 路徑規劃(Path Planning)

    7.5.4 感知(Perception)

    7.5.5 目標選擇(Target Selection)

    7.5.6 武器控制(Weapon Handling)

    7.5.7 把全部東西整合起來

    7.5.8 更新AI組件

  7.6 總結

第8章 實用路徑規劃

  8.1 構建導航圖

    8.1.1 基於單元

    8.1.2 可視點

    8.1.3 擴展圖形

    8.1.4 導航網

  8.2 <<掠奪者>>遊戲導航圖

    8.2.1 粗顆粒狀的圖

    8.2.2 細粒狀的圖

    8.2.3 爲<<掠奪者>>導航圖添加物件

    8.2.4 爲加速就近查詢而使用空間分割

  8.3 建立路徑規劃類

    8.3.1 規劃到達一個位置的一條路徑

    8.3.2 規劃路徑到達一個物件類型

  8.4 節點式路徑或邊式路徑

    8.4.1 註釋邊類示例

    8.4.2 修改路徑規劃器類以容納註釋邊

    8.4.3 路徑平滑

    8.4.4 下降CPU資源消耗的方法

  8.5 走出困境狀態

  8.6 總結

第9章 目標驅動智能體行爲

  9.1 勇士埃裏克的歸來

  9.2 實現

    9.2.1 Goal_Composite:Process Subgoals

    9.2.2 Goal_Composite:Remove AllSubgoals

  9.3 <<掠奪者>>角色所使用的目標例子

    9.3.1 Goal_Wander

    9.3.2 Goal_TraverseEdge

    9.3.3 Goal_FollowPath

    9.3.4 Goal_MoveToPosition

    9.3.5 Goal_AttackTarget

  9.4 目標仲裁

    9.4.1 計算尋找一個健康物件的指望值

    9.4.2 計算尋找一種特殊武器的指望值

    9.4.3 計算供給目標的指望值

    9.4.4 計算尋找地圖的指望值

    9.4.5 把它們都放在一塊兒

  9.5 擴展

    9.5.1 個性

    9.5.2 狀態存儲

    9.5.3 命令排隊

    9.5.4 用隊列編寫腳本行爲

  9.6 總結

第10章 模糊邏輯

  10.1 普通集合

    集合運算符

  10.2 模糊集合

    10.2.1 用隸屬函數來定義模糊的邊界

    10.2.2 模糊集合運算符

    10.2.3 限制詞

  10.3 模糊語言變量

  10.4 模糊規則

    10.4.1 爲武器的選擇設計模糊語言變量

    10.4.2 爲武器的選擇設計規則集

    10.4.3 模糊推理

  10.5 從理論到應用: 給一個模糊邏輯模塊編碼

    10.5.1 模糊l模塊類(FuzzyModule)

    10.5.2 模糊集合基類(FuzzySet)

    10.5.3 三角形的模糊集合類

    10.5.4 右肩模糊集合類

    10.5.5 建立一個模糊語言變量類

    10.5.6 爲創建模糊規則而設計類

  10.6 <<掠奪者>>中是如何使用模糊邏輯類的

  10.7 庫博方法

    10.7.1 模糊推理和庫博方法

    10.7.2 實現

  10.8 總結

參考文獻

A Generic Fuzzy State Machine in C++, Game Programming Gems 2, Eric Dysband

Algorithm in C++: Parts 1-4, Robert Sedgewick

Algorithm in C++: Part 5, Robert Sedgewick

Applying UML and Patterns, Craig Larman

Artificial Intelligence: A Modern Approach, Stuart Russell and Peter Norvig

Artificial Intelligence: A New Synthesis, Nils J. Nilsson

C++ Templates: The Complete Guide, David Vandevoorde and Nicolai M.Josuttis

Design Patterns, Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides

Effective C++, Scott Meyers

Enhancing a State Machine Language through Messaging, AI Game Programming Wisdom, Steve Rabin

Fuzzy Logic: Intelligence, Control, and Information, John Yen and Reza Langari

How Autonomous is an Autonomous Agent? Bertil Ekdahl

Interactions with Groups of Autonomous Characters, Craig Reynolds

It Knows What You're Going To Do: Adding Anticipation to a Quakebot, John E.Laird

Layered Learning in Multiagent Systems: A Winning Approach to Robotic Soccer, Peter Stone

Lua 5.0 Reference Manual

More Effective C++, Scott Meyers

Navigating Doors, Elevators, Ledges and Other Obstacles, AI Game Programming Wisdom, John Hancock

Newtonian Physics, Benjamin Crowell 477

Pathfinding Design Architecture, AI Game Programming Wisdom, Dan Higgins

Pattern Hatching, John Vlissides

Physics for Game Developers, David M. Bourg

Polygon Soup for the Programmer's Soul, Patrick Smith

Smart Moves: Intelligent Pathfinding, Bryan Stout

Steering Behaviors, Christian Schnellhammer and Thomas Feikas

Steering Behaviors for Autonomous Characters, Craig Reynolds

Steering Behaviours, Robin Green

Stigmergy, Self-Organisation, and Sorting in Collective Robotics, Owen Holland and Chris Melhuish

The C++ Programming Language, Bjarne Stroustrup

The C++ Standard Library, Nicolai Josuttis

The Combs Method for Rapid Inference, William E.Combs

The Integration of AI and Level Design in Halo, Jaime Griesemer and Chris Butcher

The Quake 3 Arena Bot, J.M.P. van Waveren

Toward More Realistic Pathfinding, Macro Pinter

UML Distilled, Martin Fowler and Kendall Scott

UML Tutorial: Finite State Machine, Robert C. Martin

相關文章
相關標籤/搜索