Code archives/3D Graphics - Misc/Finite State Machine (FSM)

This code has been declared by its author to be Public Domain code.

Download source code

Finite State Machine (FSM) by kevin8084
(Posted 20 years ago)
This is just a simple example of a Finite State Machine. It is primitive but is meant to be easy to understand.
; **************************************************************************************************
; Finite State Machine tutorial by Kevin Lee Legge (kevin8084@gmail.com)
; **************************************************************************************************
;
; ************************************** DEFINITION ************************************************
;
; A Finite State Machine (FSM) is an artificial intelligence method where the AI is in a series
; of "states". For game playing some of the most common states are: IDLE, HUNGRY, SEARCHING
; ATTACKING, SUPPORTING, RUNNING, DYING.
; There are two stages in the FSM - the "transistion" stage and the "behavior" stage.
; The transistion stage is where the AI's state is checked to see if the AI is in transistion
; from one state to another. That is, if the AI is in the IDLE state and is being attacked, then
; its state will be changing to ATTACKING or RUNNING or, perhaps, DYING.
; The behavior stage is where the action is. This part of the code instructs the AI on
; what to do according to what state it's currently in. For example, if the AI is currently in the
; SEARCHING state, then the behavior stage calls whatever searching functions are set up in the
; code.
; There are currently two schools of thought regarding FSM. The first regards the Behavior stage as
; completely adequate by itself, without bothering with any transistions. The second regards
; the transistion AND behavior stages as part of a whole.
; I, personally, subscribe to the idea that the FSM must have both transistion and behavior stages.
;
; **************************************************************************************************
Graphics3D 800,600
SetBuffer BackBuffer()

SeedRnd MilliSecs()

; **************************************************************************************************
;                                          CONSTANTS
; **************************************************************************************************

Const IDLE      = 0
Const SEARCHING = 1
Const ATTACKING = 2
Const DYING     = 3

Const PLAYER_TYPE = 1
Const ENEMY_TYPE  = 2
Const CUBE_TYPE   = 3
Const GROUND_TYPE = 4


; **************************************************************************************************
;                                            TYPES
; **************************************************************************************************

Type enemy
	Field entity	  ; the AI's mesh
	Field state		  ; what state the AI is currently in
	Field life#		  ; how much life the AI has
	Field range#	  ; the AI's search range
	Field bad.player  ; use to store handle of AI's enemy (ie., the player)
	Field x#,y#,z#	  ; the AI's location
	Field vx#,vy#,vz# ; the AI's velocity
End Type

Type player
	Field entity
	Field x#,y#,z#
End Type

; **************************************************************************************************
;                                      GLOBAL VARIABLES
; **************************************************************************************************

Global age	; use to age the enemy so that it transistions into its DYING state
Global alpha# = 1
Global eState$ ; holds enemy's state for printing purposes

; Create the ground
Global plane = CreatePlane()
EntityType plane,GROUND_TYPE
EntityColor plane,85,85,85
PositionEntity plane,0,0,0

; Create the enemy
Global enemy.enemy = New enemy
enemy\entity = CreateSphere()
EntityRadius enemy\entity,1
EntityType enemy\entity,ENEMY_TYPE
EntityShininess enemy\entity,1
enemy\x# = Rnd(-200,200)
enemy\y# = 2
enemy\z# = Rnd(-200,200)
enemy\state = IDLE
enemy\life# = 100.0
enemy\range# = 50.0
age = MilliSecs()
PositionEntity enemy\entity,enemy\x#,enemy\y#,enemy\z#

; Create the player
Global player.player = New player
player\entity = CreateCamera()
EntityRadius player\entity,1
EntityType player\entity,PLAYER_TYPE
player\x#=0
player\y#=2
player\z#=0
PositionEntity player\entity,player\x#,player\y#,player\z#

; Create the light
Global light = CreateLight()
PositionEntity light,100,100,100
RotateEntity light,90,0,0

; Populate the world with some cubes
For c=1 To 25
	cube=CreateCube()
	EntityColor cube,0,87,0
	EntityType cube,CUBE_TYPE
	PositionEntity cube,Rand(-200,200),1,Rand(-200,200)
Next
HidePointer

Collisions ENEMY_TYPE,CUBE_TYPE,2,2
Collisions ENEMY_TYPE,GROUND_TYPE,2,2
Collisions PLAYER_TYPE,GROUND_TYPE,2,21
Collisions PLAYER_TYPE,CUBE_TYPE,2,2

MoveMouse GraphicsWidth()/2,GraphicsHeight()/2

While Not KeyHit(1)
If KeyDown(200) Then MoveEntity player\entity,0,0,.3
If KeyDown(208) Then MoveEntity player\entity,0,0,-.3
If KeyDown(203) Then TurnEntity player\entity,0,1,0
If KeyDown(205) Then TurnEntity player\entity,0,-1,0

TranslateEntity player\entity,0,-1,0 ; give player some gravity
If enemy <> Null Then TranslateEntity enemy\entity,0,-1,0 ; same with the enemy

mxs#=-MouseXSpeed()*.25
mys#=MouseYSpeed()*.25
MoveMouse GraphicsWidth()/2,GraphicsHeight()/2
TurnEntity player\entity,mys#,mxs#,0
RotateEntity player\entity,EntityPitch#(player\entity),EntityYaw#(player\entity),0
If EntityPitch#(player\entity)>80 Then RotateEntity player\entity,80,0,0
If EntityPitch#(player\entity)<-80 Then RotateEntity player\entity,-80,0,0

FSM_Transistion() ; check to see if there are any state transistions
FSM_Behavior()    ; now do the code, depending on the state the AI is in

UpdateWorld
RenderWorld
Text 0,0,"I'm "+eState$
Flip
Delay 10
Wend
FreeEntity player\entity
Delete player
End


; **************************************************************************************************
;                                             FUNCTIONS
; **************************************************************************************************

Function FSM_Transistion()
; this function determines whether or not the AI is in transistion from one state to another
For this.enemy = Each enemy ; iterate through each enemy AI
	Select this\state
		Case IDLE           
			eState$="Idle"
			EntityColor this\entity,0,255,0
			p.player = getEnemyDistance(this) ; check to see if player is in sensory range
			If p <> Null  Then                ; we see the player!
				this\state = ATTACKING        ; we transistion to the attack state
				this\bad = p                  ; make sure that we store player internally
			ElseIf MilliSecs() > age+50000 Then ; are we about to die of old age (50 seconds)?
				this\state = DYING				; yes? Then transistion to dying state
				age = MilliSecs()               ; not needed, really, but what the heck :)
			End If
		Case SEARCHING
			EntityColor this\entity,2550,0,255
			p.player = getEnemyDistance(this) ; again, check to see if player is nearby
			If p <> Null Then                 ; yes? 
				this\state = ATTACKING        ; then transistion to attack state
				this\bad = p
			ElseIf MilliSecs() > age+50000 Then ; are we dying of old age?
				this\state = DYING              ; yep...then we transistion
				age = MilliSecs()
			End If
		Case ATTACKING
			EntityColor this\entity,255,0,0
			p.player = getEnemyDistance(this)   ; even though we are in attacking state, we need to
										        ; make sure that player is still in range
			If p <> Null Then                   ; yes?
				this\state = ATTACKING          ; Houston, we have liftoff...we attack!
				this\bad = p
			ElseIf MilliSecs() > age+50000 Then ; else, are we dying?
				this\state = DYING              ; if so, then die already!
				age = MilliSecs()
			Else
				this\state = SEARCHING          ; else let's go to the searching state
				this\bad = Null                 ; no bad player here, so make it null
			End If
		Case DYING                              ; we are dying
			EntityColor this\entity,45,45,45
	End Select
Next
End Function

Function FSM_Behavior()
; this function reroutes according to which state the AI is in
For this.enemy = Each enemy ; iterate through the enemy
	Select this\state
		Case IDLE
			this\state = SEARCHING ; idle is so boring...let's search, instead
		Case SEARCHING
			patrol(this) ; do our patrol
		Case ATTACKING
			attack(this) ; attack!
		Case DYING
			die(this)    ; well, it's been real
			alpha#=alpha# - .001 ; this is to fade out the enemy
	End Select
Next

End Function

Function patrol(e.enemy)
; put patrol code here
eState$ = "patrolling"
; let's have the enemy move around a little bit
MoveEntity e\entity,0,0,.2
If Rand(255)=17 Then RotateEntity e\entity,0,EntityYaw#(e\entity)+5,0
End Function

Function attack(e.enemy)
; put attack code here
eState$ = "attacking"
; just to show how to implement the attack state
; this is NOT my normal attack function! :)
PointEntity e\entity,player\entity
MoveEntity e\entity,0,0,.2
End Function

Function die(e.enemy)
; we are dying now
eState$ = "dying"
EntityAlpha e\entity,alpha#
If alpha# <= 0 Then
	FreeEntity e\entity
	Delete e
	eState$="Nothing...no more enemies left"
End If
End Function

Function getEnemyDistance.player(e.enemy)
; function to determine if player is in AI's sensory range
For p.player = Each player ; iterate through all the players
	If p <> Null Then      ; hey, do we have a player?
		If EntityDistance#(e\entity,p\entity)<=e\range# Then ; yep...is he/she in range?
			Return p   ; yes? Then return the player
		End If
	End If
Next
End Function