This example really pushes the envelope and does things that are frowned on within certain circles.
It is posted here to show some of the Power available with the new PowerObjects. It would be of interest to anyone creating text based adventure games and those who want to learn how to implement FMS's(see below) with PowerBasic 5/9.
Note to [email protected]. I have permission from the author to use his code. I will forward the email if requested.
I am not seasoned OOP programmer by any means but this code seems stable.
James
Back ground:
I started investigating FSM's (Finite State Machines) as they are used in RPG game development and came across a number of interesting articles. The one that really caught my eye was an exert from a book written by Mat Buckland (url in the code). By using his methods outlined you eliminate the monster if / then | select / case normally used in these games. He writes of course in C++ and it took some magic to finally come up with a way to implement in PBCC 5.
'******************************************************************************
Problem:
There is no documented way to pass an OBJECTPTR to a Function/Sub/Method AS A BYVAL DWORD.
******************************************************************************
Magic Part #1
Example:
This does not work:
You would need to this:
But what if we really need to pass a DWORD
Here is how this is done but be very careful
Explanation:
The MyObjPtr is an iMyObj Object created outside the METHOD
There is no assignment for oMyObj (we poke it). When the METHOD Ends
it is noted by the compiler that the oMyObj variable contains a valid object reference
so it Calls the COM Release Method to deallocate all the internal stuff.
woops we just NOTHING'ed our original passed in OBJECT so off to GPF land.
To counteract this we use the (PB undocumented - although well known COM call) AddRef
oMyObj.AddRef
Magic Part #2.
I needed a way to fool the compiler into thinking it was dealing with just one type of
Class/Interface while in reality there were as many different class/interface as there
were locations.
Solution:
I use what is called in C++ as a Virtual Class. It has no functionality and is used strictly
as a base class shown below
All Location classes inherit from this class. They then flesh out the three methods Enter,
Execute, Exit.
We have two Control states Called oNewState, oCurrentState also derived from cState/iState.
We then use the same method describe abouve to POKE the OBJPTR's of the locations to oNewState
It is posted here to show some of the Power available with the new PowerObjects. It would be of interest to anyone creating text based adventure games and those who want to learn how to implement FMS's(see below) with PowerBasic 5/9.
Note to [email protected]. I have permission from the author to use his code. I will forward the email if requested.
I am not seasoned OOP programmer by any means but this code seems stable.
James
Back ground:
I started investigating FSM's (Finite State Machines) as they are used in RPG game development and came across a number of interesting articles. The one that really caught my eye was an exert from a book written by Mat Buckland (url in the code). By using his methods outlined you eliminate the monster if / then | select / case normally used in these games. He writes of course in C++ and it took some magic to finally come up with a way to implement in PBCC 5.
'******************************************************************************
Problem:
There is no documented way to pass an OBJECTPTR to a Function/Sub/Method AS A BYVAL DWORD.
******************************************************************************
Magic Part #1
Example:
This does not work:
Code:
METHOD M1(BYVAL MyobjPtr AS DWORD) LOCAL oMyObj AS iMyObj oMyObj = MyObjPtr
Code:
METHOD M1(BYVAL Myobj AS iMyObj ) LOCAL oMyObj AS iMyObj oMyObj = MyObj
Here is how this is done but be very careful
Code:
METHOD M1(BYVAL MyobjPtr AS DWORD) LOCAL oMyObj AS iMyObj POKE DWORD,VARPTR(oMyObj),MyObjPtr) IF ISNOTHING(oMyObj) THEN EXIT METHOD oMyObj.AddRef
The MyObjPtr is an iMyObj Object created outside the METHOD
There is no assignment for oMyObj (we poke it). When the METHOD Ends
it is noted by the compiler that the oMyObj variable contains a valid object reference
so it Calls the COM Release Method to deallocate all the internal stuff.
woops we just NOTHING'ed our original passed in OBJECT so off to GPF land.
To counteract this we use the (PB undocumented - although well known COM call) AddRef
oMyObj.AddRef
Magic Part #2.
I needed a way to fool the compiler into thinking it was dealing with just one type of
Class/Interface while in reality there were as many different class/interface as there
were locations.
Solution:
I use what is called in C++ as a Virtual Class. It has no functionality and is used strictly
as a base class shown below
Code:
CLASS cState INTERFACE iState : INHERIT IUNKNOWN METHOD Enter(BYVAL Agent AS tAgent) END METHOD METHOD Execute(BYVAL Agent AS tAgent) END METHOD METHOD EXIT(BYVAL Agent AS tAgent) END METHOD END INTERFACE END CLASS
Execute, Exit.
We have two Control states Called oNewState, oCurrentState also derived from cState/iState.
We then use the same method describe abouve to POKE the OBJPTR's of the locations to oNewState
Code:
'SED_PBCC '****************************************************************************** ' A Finite State Machine example written in PBCC 5.0 ' James C. Fuller ' August 11,2008 ' [email protected] ' '****************************************************************************** '============================================================================== 'Based on WestWorld c++ example by Mat Buckland 2002 ([email protected]) '------------------------------------------------------------------------------ '############################################################################## 'If you are unfamilar with FSM's here are a few links: '****************************************************************************** ' This one is where I got the code my example is based. ' http://www.ai-junkie.com/architecture/state_driven/tut_state1.html '****************************************************************************** '------------------------------------------------------------------------------ ' http://aigamedev.com/questions/fsm-implementation '------------------------------------------------------------------------------ 'http://klabs.org/DEI/References/design_guidelines/nasa_guidelines/fsm/finite_state_machines.htm '------------------------------------------------------------------------------ 'http://www.rwc.uc.edu/koehler/comath/34.html '------------------------------------------------------------------------------ 'http://centipedia.com/articles/Finite_state_automaton '------------------------------------------------------------------------------ 'http://www.aiwisdom.com/bytopic_statemachines.html '############################################################################## 'I'll try to explain how this works. 'I use a completely Virtual Base Class (cState/iState) that is inherited by all 'the State Classes so I can fool the Update Method into thinking it's dealing 'with a single class. We don't have Pointers that can be used and instantiated so ' this approach seems to work fine? '############################################################################## 'Each location is a class composed of three state classes(Enter,Execute,Exit) ' all inherited from cState/iState '****************************************************************************** 'Note TRACE is enabled specifically to check for AddRef problems. The areas where 'it is used here should always print 2. The origianl and the one we add. When the 'local scope ends the compiler reduces the count by one; exactly what we want. '****************************************************************************** #COMPILE EXE #DIM ALL %shack = 1 %goldmine = 2 %bank = 3 %saloon = 4 $AgentNames="Miner Bob;Elsa" MACRO FUNCTION GetAgentName(Id) END MACRO = PARSE$($AgentNames,";",Id) MACRO MyTrace(MyStr) TRACE ON TRACE PRINT MyStr TRACE OFF END MACRO %AGENT_MINERBOB = 1 TYPE tAgent AgentObj AS DWORD 'Holds OBJPTR(ME) AgentType AS LONG END TYPE '============================================================================== CLASS cBaseGameEntity INSTANCE m_id,m_NextValidId AS LONG INTERFACE iBaseGameEntiy :INHERIT IUNKNOWN PROPERTY GET m_id() AS LONG PROPERTY = m_id END PROPERTY PROPERTY SET m_id(BYVAL Param AS LONG) m_id = Param END PROPERTY END INTERFACE END CLASS '============================================================================== CLASS cMiner INSTANCE Location,GoldCarried,MoneyInBank,Thirst,Fatigue AS LONG INSTANCE ComfortLevel,MaxNuggets,ThirstLevel,TirednessThreshold AS LONG INSTANCE oCurrentState AS iState INSTANCE oNewState AS iState INSTANCE oGoHomeAndSleepTilRested AS iGoHomeAndSleepTilRested INSTANCE Agent AS tAgent '------------------------------------------------------------------------------ CLASS METHOD CREATE ComfortLevel = 5 MaxNuggets = 3 ThirstLevel = 5 TirednessThreshold = 5 Location = %shack Agent.AgentType = %AGENT_MINERBOB END METHOD '------------------------------------------------------------------------------ INTERFACE iMiner : INHERIT cBaseGameEntity, iBaseGameEntiy PROPERTY GET Agent() AS tAgent PROPERTY = Agent END PROPERTY PROPERTY GET Location() AS LONG PROPERTY = Location END PROPERTY '.............................................................................. PROPERTY GET GoldCarried() AS LONG PROPERTY = GoldCarried END PROPERTY PROPERTY SET GoldCarried(BYVAL Param AS LONG) GoldCarried = Param END PROPERTY PROPERTY GET ComfortLevel() AS LONG PROPERTY = ComfortLevel END PROPERTY PROPERTY SET oNewState(BYVAL dwNewState AS DWORD) POKE DWORD,VARPTR(oNewState),dwNewState oNewState.AddRef END PROPERTY '.............................................................................. METHOD ChangeLocation(BYVAL NewLocation AS LONG) Location = NewLocation END METHOD '------------------------------------------------------------------------------ 'This is where the states are changed '------------------------------------------------------------------------------ METHOD UpDate()AS LONG LOCAL RefC AS LONG ,TrStr AS STRING Thirst +=1 'This will be the first time UpDate is Called ' Create the Start Location -> oGoHomeAndSleepTilRested IF Agent.AgentObj = 0 THEN 'First Time Agent.AgentObj = OBJPTR(ME) oGoHomeAndSleepTilRested = CLASS "cGoHomeAndSleepTilRested" 'We can't just do an assignment because they are not the same interface but 'we can do a little trick that works fine POKE DWORD,VARPTR(oNewState),OBJPTR(oGoHomeAndSleepTilRested) 'This is undocumented but works fine. just make sure either out of scope or = NOTHIMG's 'match the AddRef. For debugging you can check the Count which is returned from AddRef. RefC = oNewState.AddRef TrStr = "oNewState RefC in UpDate = " + FORMAT$(Refc) END IF IF ISOBJECT(oNewState) THEN 'No oCurrentState if First Update IF ISOBJECT(oCurrentState) THEN oCurrentState.Exit(Agent) oCurrentState = NOTHING END IF oCurrentState = oNewState oNewState = NOTHING oCurrentState.Enter(Agent) END IF IF ISOBJECT(oCurrentState) THEN oCurrentState.Execute(Agent) END IF END METHOD '------------------------------------------------------------------------------ METHOD AddToGoldCarried(BYVAL Amount AS LONG) GoldCarried += Amount END METHOD '------------------------------------------------------------------------------ METHOD IncreaseFatigue() Fatigue += 1 END METHOD '------------------------------------------------------------------------------ METHOD DecreaseFatigue() Fatigue -= 1 END METHOD '------------------------------------------------------------------------------ METHOD PocketsFull() AS LONG METHOD = (GoldCarried >= MaxNuggets) END METHOD '------------------------------------------------------------------------------ METHOD Thirsty() AS LONG METHOD = (Thirst >= ThirstLevel) END METHOD '------------------------------------------------------------------------------ METHOD Wealth() AS LONG METHOD = MoneyInBank END METHOD '------------------------------------------------------------------------------ METHOD SetWealth(BYVAL Param AS LONG) MoneyInBank = Param END METHOD '------------------------------------------------------------------------------ METHOD AddToWealth(BYVAL Param AS LONG) MoneyInBank += Param IF MoneyInBank < 0 THEN MoneyInBank = 0 END IF END METHOD '------------------------------------------------------------------------------ METHOD BuyAndDrinkAWhiskey() Thirst = 0 MoneyInBank -= 2 END METHOD '------------------------------------------------------------------------------ METHOD Fatigued() AS LONG METHOD = (Fatigue > TirednessThreshold) END METHOD '------------------------------------------------------------------------------ END INTERFACE END CLASS '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* 'Virtual State Class 'This is the Class/Interface that is the base for all Locations and in C++ 'terms in is a Virtual Class with no functionality on it's own. '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* CLASS cState INTERFACE iState : INHERIT IUNKNOWN METHOD Enter(BYVAL Agent AS tAgent) END METHOD METHOD Execute(BYVAL Agent AS tAgent) END METHOD METHOD EXIT(BYVAL Agent AS tAgent) END METHOD END INTERFACE END CLASS '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* CLASS cEnterMineAndDigForNugget INTERFACE iEnterMineAndDigForNugget : INHERIT cState,iState '------------------------------------------------------------------------------ OVERRIDE METHOD Enter(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner LOCAL RefC AS LONG ,TrStr AS STRING IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj IF ISNOTHING(oMiner) THEN EXIT METHOD END IF RefC = oMiner.AddRef TrStr = "oMiner RefC in cEnterMineAndDigForNugget Enter = " + FORMAT$(RefC) MyTrace(TrStr) IF oMiner.Location <> %goldmine THEN PRINT GetAgentName(1); ": Walking to the Glodmine" oMiner.ChangeLocation(%goldmine) END IF 'ELSEIF Agent.AgentType = Something Else END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Execute(BYVAL Agent AS tAgent) LOCAL Id AS LONG LOCAL oMiner AS iMiner LOCAL oVisitBankAndDepositGold AS iVisitBankAndDepositGold LOCAL oQuenchThirst AS iQuenchThirst LOCAL RefC AS LONG ,TrStr AS STRING IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj IF ISNOTHING(oMiner) THEN EXIT METHOD END IF ReFc=oMiner.AddRef TrStr = "oMiner RefC in cEnterMineAndDigForNugget Execute = " + FORMAT$(RefC) MyTrace(TrStr) Id = oMiner.m_id oMiner.AddToGoldCarried(1) oMiner.IncreaseFatigue() PRINT GetAgentName(Id); ": Picking Up a Nugget" IF oMiner.PocketsFull() THEN oVisitBankAndDepositGold = CLASS "cVisitBankAndDepositGold" IF ISNOTHING(oVisitBankAndDepositGold) THEN EXIT METHOD END IF oVisitBankAndDepositGold.AddRef oMiner.oNewState = OBJPTR(oVisitBankAndDepositGold) PRINT GetAgentName(Id); ": Pockets Full" END IF IF oMiner.Thirsty THEN oQuenchThirst = CLASS "cQuenchThirst" oQuenchThirst.Addref oMiner.oNewState = (OBJPTR(oQuenchThirst)) END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Exit(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner LOCAL Id AS LONG LOCAL RefC AS LONG,TrStr AS STRING IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj Id = oMiner.m_id RefC=oMiner.AddRef TrStr = "oMiner RefC in cEnterMineAndDigForNugget Exit = " + FORMAT$(RefC) MyTrace(TrStr) PRINT GetAgentName(Id); ": Ah'm leavin' the goldmine with mah pockets full o' sweet gold" END IF END METHOD END INTERFACE END CLASS '****************************************************************************** CLASS cVisitBankAndDepositGold INSTANCE oGoHomeAndSleepTilRested AS iGoHomeAndSleepTilRested INSTANCE oEnterMineAndDigForNugget AS iEnterMineAndDigForNugget INSTANCE Id AS LONG INTERFACE iVisitBankAndDepositGold : INHERIT cState,iState OVERRIDE METHOD Enter(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef IF oMiner.Location <> %Bank THEN PRINT GetAgentName(1); ": Walking to the Bank" oMiner.ChangeLocation(%Bank) END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Execute(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef oMiner.AddToWealth(oMiner.GoldCarried) oMiner.GoldCarried = 0 PRINT GetAgentName(1); ": Depositing gold. Total savings now: ";oMiner.Wealth IF oMiner.Wealth >= oMiner.ComfortLevel THEN PRINT GetAgentName(1); ": WooHoo! Rich enough for now. Back home to mah li'lle lady" oGoHomeAndSleepTilRested = CLASS "cGoHomeAndSleepTilRested" oMiner.oNewState = OBJPTR(oGoHomeAndSleepTilRested) ELSE oEnterMineAndDigForNugget = CLASS "cEnterMineAndDigForNugget" oMiner.oNewState = OBJPTR(oEnterMineAndDigForNugget) END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Exit(BYVAL Agent AS tAgent) PRINT GetAgentName(1); ": Leaving The Bank" END METHOD '------------------------------------------------------------------------------ END INTERFACE END CLASS '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* CLASS cGoHomeAndSleepTilRested INSTANCE oEnterMineAndDigForNugget AS iEnterMineAndDigForNugget INSTANCE Id AS LONG INTERFACE iGoHomeAndSleepTilRested : INHERIT cState,iState OVERRIDE METHOD Enter(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef IF oMiner.Location <> %Shack THEN PRINT GetAgentName(1); ": Walking to the Shack" oMiner.ChangeLocation(%Shack) END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Execute(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef IF ISFALSE(oMiner.Fatigued) THEN PRINT GetAgentName(1); ": What a Gol darn fantastic nap! Time to find more gold" oEnterMineAndDigForNugget = CLASS "cEnterMineAndDigForNugget" oMiner.oNewState = OBJPTR(oEnterMineAndDigForNugget) ELSE oMiner.DecreaseFatigue PRINT GetAgentName(1); ": ZZZZ....." END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Exit(BYVAL Agent AS tAgent) PRINT GetAgentName(1); ": Leaving the House" END METHOD '------------------------------------------------------------------------------ END INTERFACE END CLASS '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* CLASS cQuenchThirst INSTANCE oEnterMineAndDigForNugget AS iEnterMineAndDigForNugget INTERFACE iQuenchThirst : INHERIT cState,iState OVERRIDE METHOD Enter(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef IF oMiner.Location <> %Saloon THEN oMiner.ChangeLocation(%Saloon) PRINT GetAgentName(1); ": Boy, ah sure is thusty! Walking to the saloon" END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Execute(BYVAL Agent AS tAgent) LOCAL oMiner AS iMiner IF Agent.AgentType = %AGENT_MINERBOB THEN POKE DWORD,VARPTR(oMiner),Agent.AgentObj oMiner.AddRef IF oMiner.Thirsty THEN oMiner.BuyAndDrinkAWhiskey PRINT GetAgentName(1); ": That's mighty fine sippin liquer" oEnterMineAndDigForNugget = CLASS "cEnterMineAndDigForNugget" oMiner.oNewState =OBJPTR(oEnterMineAndDigForNugget) ELSE PRINT "Error" PRINT "Error" PRINT "Error" END IF END IF END METHOD '------------------------------------------------------------------------------ OVERRIDE METHOD Exit(BYVAL Agent AS tAgent) PRINT GetAgentName(1); ": Leaving the Saloon" END METHOD END INTERFACE END CLASS '=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=* FUNCTION PBMAIN () AS LONG LOCAL oMiner AS iMiner LOCAL i AS LONG TRACE NEW "WW15_TRACE.TXT" COLOR 11 oMiner = CLASS "cMiner" oMiner.m_Id = 1 FOR i = 1 TO 20 IF oMiner.UpDate() < 0 THEN PRINT "Bad UpDate" EXIT FOR END IF Sleep 1000 NEXT i PRINT "Any Key To Exit" WAITKEY$ TRACE CLOSE END FUNCTION