Finite State Machines

Advanced game related topics
Yewklid
User
User
Posts: 19
Joined: Sun May 27, 2007 3:40 pm
Location: Berkshire, England

Finite State Machines

Post by Yewklid »

Has anyone tried using Finite State Machines to introduce a bit of AI into their games? Here is my attempt to translate Mat Buckland's West World example into PB.

Code: Select all


;;;****   Miner.pb
;;;****   Yewklid  December 2009
;;;****   Inspired by Mat Buckland


;-*******   Locations *****
Enumeration
#Shack
#Goldmine
#Saloon
#Bank
EndEnumeration

;-***  Constants ****

#ScreenWidth = 500
#ScreenHeight = 768

Global LatestID.w = -1
;Global Nextline.w = 1

;-****    Structures *****

Structure State
  *Exit.l
  *Execute.l
  *Enter.l
  Location.b
EndStructure

Structure BaseGameEntity
  ID.w
EndStructure

Structure GameEntity Extends BaseGameEntity
  *CurrentState.State
  *PreviousState.State
  *GlobalState.State
  Location.b
EndStructure

Structure Miner Extends GameEntity
  Tiredness.b
  GoldCarried.b
  TotalWealth.b
  Thirst.b
EndStructure

Structure Wife Extends GameEntity
  
EndStructure

;-****  Declarations ****

Declare EnterMineAndDig_Enter(*Miner.Miner)
Declare EnterMineAndDig_Execute(*Miner.Miner)
Declare EnterMineAndDig_Exit(*Miner.Miner)


Declare GoHomeAndSleep_Exit(*Miner.Miner)
Declare GoHomeAndSleep_Execute(*Miner.Miner)
Declare GoHomeAndSleep_Enter(*Miner.Miner)

Declare GoToBank_Exit(*Miner.Miner)
Declare GoToBank_Execute(*Miner.Miner)
Declare GoToBank_Enter(*Miner.Miner)

Declare QuenchThirst_Exit(*Miner.Miner)
Declare QuenchThirst_Execute(*Miner.Miner)
Declare QuenchThirst_Enter(*Miner.Miner)

Declare WorkatHome_Exit(*W.Wife)
Declare WorkatHome_Execute(*W.Wife)
Declare WorkatHome_Enter(*W.Wife)

Declare ChangeState(*M.GameEntity,*NewState.State)
Declare RevertToPreviousState(*M.GameEntity)
Declare.w GetLatestID()
Declare.s GetEntityName(*GE.GameEntity)
Declare InitMiner(*M.Miner,Loc.b,*InitState.State)
Declare Update_Miner(*M.Miner)
Declare Update_State(*GE.GameEntity)

Declare InitWife(*M.Wife,Loc.b,*InitState.State,*GlobalState.State)
Declare Update_Wife(*W.Wife)
Declare Write (Txt.s)

Declare VisitBathroom_Exit(*W.Wife)
Declare VisitBathroom_Execute(*W.Wife)
Declare VisitBathroom_Enter(*W.Wife)

Declare WifeGlobalState_Execute(*W.Wife)

 
Global EnterMineAndDig.State
EnterMineAndDig\Exit = @EnterMineAndDig_Exit()
EnterMineAndDig\Execute = @EnterMineAndDig_Execute()
EnterMineAndDig\Enter = @EnterMineAndDig_Enter()
EnterMineAndDig\Location = #Goldmine

Global GoHomeAndSleep.State
GoHomeAndSleep\Exit = @GoHomeAndSleep_Exit()
GoHomeAndSleep\Execute = @GoHomeAndSleep_Execute()
GoHomeAndSleep\Enter = @GoHomeAndSleep_Enter()
GoHomeAndSleep\Location = #Shack

Global GoToBank.State
GoToBank\Exit = @GoToBank_Exit()
GoToBank\Execute = @GoToBank_Execute()
GoToBank\Enter = @GoToBank_Enter()
GoToBank\Location = #Bank

Global QuenchThirst.State
QuenchThirst\Exit = @QuenchThirst_Exit()
QuenchThirst\Execute = @QuenchThirst_Execute()
QuenchThirst\Enter = @QuenchThirst_Enter()
QuenchThirst\Location = #Saloon

Global WorkatHome.State
WorkatHome\Exit = @WorkatHome_Exit()
WorkatHome\Execute = @WorkatHome_Execute()
WorkatHome\Enter = @WorkatHome_Enter()
WorkatHome\Location = #Shack

Global VisitBathroom.State
VisitBathroom\Exit = @VisitBathroom_Exit()
VisitBathroom\Execute = @VisitBathroom_Execute()
VisitBathroom\Enter = @VisitBathroom_Enter()
VisitBathroom\Location = #Shack

Global WifeGlobalState.State
WifeGlobalState\Execute = @WifeGlobalState_Execute()


;-******   Main bit  ******


      Bob.Miner
      InitMiner(@Bob,#Goldmine,@EnterMineAndDig)

      Elsa.Wife
      InitWife(@Elsa,#Shack,@WorkAtHome,@WifeGlobalState)

      For i = 1 To 25
        Update_Miner(@Bob)
        Update_Wife(@Elsa)
      Next
       
 
;-*****  Miner States ******

Procedure EnterMineAndDig_Enter(*Miner.Miner)
  If *Miner\Location <> #Goldmine
    Write (GetEntityName(*Miner) + "Entering the mine")
  EndIf
EndProcedure

Procedure EnterMineAndDig_Execute(*Miner.Miner)
  If *Miner\Location = #Goldmine
    Write (GetEntityName(*Miner) + "Picking up a nugget")
    *Miner\GoldCarried = *Miner\GoldCarried + 1
    *Miner\Tiredness = *Miner\Tiredness + 1
   
    If *Miner\GoldCarried >= 3 
        Changestate(*Miner,@GoToBank)
    ElseIf *Miner\Tiredness >= 5
        Changestate(*Miner,@GoHomeAndSleep)
    ElseIf *Miner\Thirst >= 5
        Changestate(*Miner,@QuenchThirst)
    EndIf 
  EndIf
EndProcedure

Procedure EnterMineAndDig_Exit(*Miner.Miner)
  If *Miner\Location = #Goldmine
    Write (GetEntityName(*Miner) + "Leaving the mine")
  EndIf
EndProcedure

Procedure GoHomeAndSleep_Exit(*Miner.Miner)
  If *Miner\Location = #Shack
    Write (GetEntityName(*Miner) + "Leaving the Shack")
  EndIf
EndProcedure

Procedure GoHomeAndSleep_Execute(*Miner.Miner)
  If *Miner\Location = #Shack
    If *Miner\Tiredness <= 0
        Write (GetEntityName(*Miner) + "Gosh, what a good sleep")
        Changestate(*Miner,@EnterMineAndDig)
    Else
        Write (GetEntityName(*Miner) + "zzzzz")
        *Miner\Tiredness = *Miner\Tiredness - 1
    EndIf
  EndIf
EndProcedure

Procedure GoHomeAndSleep_Enter(*Miner.Miner)
  If *Miner\Location <> #Shack
    Write (GetEntityName(*Miner) + "I'm mighty tired. Walking Home")
  EndIf
EndProcedure

Procedure GoToBank_Exit(*Miner.Miner)
  If *Miner\Location = #Bank
    Write (GetEntityName(*Miner) + "Leaving the Bank")
  EndIf
EndProcedure

Procedure GoToBank_Execute(*Miner.Miner)
  If *Miner\Location = #Bank
    *Miner\TotalWealth = *Miner\TotalWealth + *Miner\GoldCarried
    Write (GetEntityName(*Miner) + "Banking My Gold  Total Wealth: " + Str(*Miner\TotalWealth))
    *Miner\GoldCarried = 0
    Changestate(*Miner,@EnterMineAndDig)
  EndIf
EndProcedure

Procedure GoToBank_Enter(*Miner.Miner)
  If *Miner\Location <> #Bank
    Write (GetEntityName(*Miner) + "Entering the bank")
  EndIf
EndProcedure

Procedure QuenchThirst_Exit(*Miner.Miner)
  If *Miner\Location = #Saloon
    Write (GetEntityName(*Miner) + "Leaving Saloon. That was mighty fine likker")
  EndIf
EndProcedure

Procedure QuenchThirst_Execute(*Miner.Miner)
  If *Miner\Location = #Saloon
    Write ( GetEntityName(*Miner) + "Sipping my likker")
    *Miner\Thirst = 0
    Changestate(*Miner,@EnterMineAndDig)
  EndIf
EndProcedure

Procedure QuenchThirst_Enter(*Miner.Miner)

  If *Miner\Location <> #Saloon
    Write ( GetEntityName(*Miner) + "I'm durn thirsty. Walking to the saloon")
  EndIf
EndProcedure

;- ****** Wife States

Procedure WorkatHome_Exit(*W.Wife)
  
EndProcedure

Procedure WorkatHome_Execute(*W.Wife)
  If *W\Location = #Shack
    Select Random(2)
      Case 0
      Write ( GetEntityName(*W) + "Mopping the floor")
      Case 1
      Write ( GetEntityName(*W) + "Cleaning the kitchen")
      Case 2
      Write ( GetEntityName(*W) + "Making the bed")
    EndSelect
  EndIf
EndProcedure

Procedure WorkatHome_Enter(*W.Wife)
 
EndProcedure


Procedure WifeGlobalState_Execute(*W.Wife)
 If Random(10) < 2
  ChangeState (*W, @VisitBathroom)
 EndIf
EndProcedure


Procedure VisitBathroom_Exit(*W.Wife)
  Write ( GetEntityName(*W) + "Leaving the john")
EndProcedure

Procedure VisitBathroom_Execute(*W.Wife)
  Write ( GetEntityName(*W) + "Aaaah Sweet relief")
  ReverttoPreviousState(*W.Wife)
EndProcedure

Procedure VisitBathroom_Enter(*W.Wife)
 Write ( GetEntityName(*W) + "Walking to the can")
EndProcedure


;- ****  General Procedures

 Procedure ChangeState(*M.GameEntity,*NewState.State)
;Procedure ChangeState(*M.miner,*NewState.State)
  *M\PreviousState = *M\CurrentState
   CallFunctionFast(*M\CurrentState\Exit,*M)
  *M\CurrentState = *NewState
  CallFunctionFast(*M\CurrentState\Enter,*M)
  *M\Location = *NewState\Location
EndProcedure

Procedure RevertToPreviousState(*M.GameEntity)
  ChangeState(*M,*M\PreviousState)
EndProcedure

Procedure.w GetLatestID()
  LatestID = LatestID + 1
  ProcedureReturn LatestID
EndProcedure

Procedure.s GetEntityName(*GE.GameEntity)
  Select  *GE\ID 
    Case 0
      ProcedureReturn "Bob: "
    Case 1
      ProcedureReturn "  Elsa: "
  EndSelect
EndProcedure

Procedure InitMiner(*M.Miner,Loc.b,*InitState.State)
  *M\ID = GetLatestID()
  *M\Tiredness = 0
  *M\GoldCarried = 0
  *M\Location = Loc
  *M\TotalWealth = 0
  *M\CurrentState = *InitState
  *M\Thirst = 0
EndProcedure

Procedure InitWife(*M.Wife,Loc.b,*InitState.State,*GlobalState.State)
  *M\ID = GetLatestID()
  *M\CurrentState = *InitState
  *M\GlobalState = *GlobalState
EndProcedure

Procedure Update_Miner(*M.Miner)
  *M\Thirst =  *M\Thirst + 1
  Update_State(*M)
EndProcedure

Procedure Update_Wife(*W.Wife)
  Update_State(*W)
EndProcedure

Procedure Update_State(*GE.GameEntity)
  If *GE\GlobalState <> #Null
    CallFunctionFast(*GE\GlobalState\Execute,*GE)
  EndIf
  CallFunctionFast(*GE\CurrentState\Execute,*GE)
EndProcedure

Procedure Write (Txt.s)
     Debug Txt
EndProcedure