游戏人工智能编程案例精粹(修订版) (Mat Buckland 著)

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

第1章 数学和物理学初探 (已看)

第2章 状态驱动智能体设计 (已看)

第3章 如何创建自治的可移动游戏智能体

第4章 体育模拟(简单足球)

第5章 图的秘密生命

第6章 用脚本,还是不用?这是一个问题

第7章 概览<<掠夺者>>游戏

第8章 实用路径规划

第9章 目标驱动智能体行为

第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

转载于:https://www.cnblogs.com/revoid/p/9703661.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值