I have links to C++
http://www.ai-junkie.com/ann/evolved/nnt1.html
http://www.adit.co.uk/html/programming_a_neural_netw
and VB
http://paraschopra.com/tutorials/nn/index.php
And the code that I adapted...
http://forum.thegamecreators.com/?m=forum_view&t=68559&b=6
Here's my source code. As you can see, I tried to add more neurons by shifting 4 bits instead of 2. Not sure if that is right. I need a better way to add more neurons as I don't understand bits.
set display mode 1024,768,32
Sync On: Sync Rate 0: Autocam Off
Null=Make Vector3(1)
Randomize Timer()
`Sync on
set normalization on
`Autocam off
load object "Tank.dbo",1,1
Rotate object 1,0,270,0
Fix object pivot 1
Make object plain 2,1024,768
load image "Backdrop.bmp",1,1
texture object 2,1
position object 2,0,130,0
Rotate object 2,270,180,0
scale object 2, 200,200,200
set object 2,1,1,0
position camera 0,1400,0
xrotate camera 90
Make object plain 3,1576,1182
load image "Backdrop2.bmp",2,1
texture object 3,2
position object 3,0,0,0
Rotate object 3,270,180,0
scale object 3, 300,300,300
`The Curve type has been added as it is clearly something we will want to "evolve".
Type Neurons
Value as byte
Curve as byte
Weight as byte
Threshold as byte
Endtype
Type Genes
Curve as Integer
Weight as Integer
Threshold as Integer
Endtype
Dim Neuron(5) as Neurons
`Gene is the Tank's "genetic code". Typically, the genetic code would record and consider the population.
`In this case though, we only have one Tank and one player, and that type of evolution would take a really long time.
`So we are going to take the population of posibilities and use those instead. Since a neuron can hold a value of
`0 to 15, we will track how well each setting does and keep track over many Epochs.
`We are tracking 3 neurons ( Array Count(Neurons()) ), 2 nibbles (0-1), and 16 possible settings (0-15)
`So our Dim is:
Dim Gene(Array Count(Neuron()),5,15) as Genes
Dim BestGenes(Array Count(Neuron()),5) as Genes
Global Tank_Score#
Global Tank_Average#
Global Epoch_Timer
Global Epoch_Counter
Global count
Global T
Global Rotate
Global Turn#
Global LeftEye
Global RightEye
Global NearestFuel
Global ed#
Global Lasted#
LeftEye = 4
RightEye = 3
#Constant Tank 1
#Constant Fuel 50
For count = 10 to Fuel
Make Object Sphere count,20
Position Object count,rnd(1600)-800,0,rnd(1600)-800
Color Object count,Rgb(255,255,0)
Set Object Emissive count,Rgb(100,100,0)
Next count
NearestFuel = 10
Initialize_AI()
Do
Input_Data()
Process_Data()
Output()
lasted# = 9999.0
for count = 10 to fuel
Game()
next count
Scoring()
Epoch()
Set Cursor 0,0
SET TEXT FONT "verdana"
set text size 12
text 10,15, " Tank Score: " + str$(int(Tank_Score#))
text 10,30, " Tank Avg: " + str$ (int(Tank_Average#))
`Just to play, the player's score will be opposite of the Tank's.
text 10,50, " Epoch: " + str$(Epoch_Counter)
text 10,65, " FPS: " + str$(Screen FPS())
A = object position x(1)
B = Object position z(1)
if object screen x(1) > 890 then position object 1,-750,0,B
if object screen x(1) < 130 then position object 1,750,0,B
if object screen y(1) < 0 then position object 1,A,0,-768
if object screen y(1) > 768 then position object 1,A,0,768
Sync
Loop
`This function is added to set, or reset, our AI randomly.
Function Initialize_AI()
For i = 0 to Array Count(Neuron())
Neuron(i).Curve=Set_Left_Nibble(0,rnd(14)+1)
Neuron(i).Curve=Set_Right_Nibble(Neuron(i).Curve,rnd(14)+1)
Neuron(i).Weight=Set_Left_Nibble(0,rnd(14)+1)
Neuron(i).Weight=Set_Right_Nibble(Neuron(i).Weight,rnd(14)+1)
Neuron(i).Threshold=Set_Left_Nibble(0,rnd(15))
Neuron(i).Threshold=Set_Right_Nibble(Neuron(i).Threshold,rnd(15))
Neuron(i).Curve=Set_Left_Nibble(3,rnd(14)+1)
Neuron(i).Curve=Set_Right_Nibble(Neuron(i).Curve,rnd(14)+1)
Neuron(i).Weight=Set_Left_Nibble(3,rnd(14)+1)
Neuron(i).Weight=Set_Right_Nibble(Neuron(i).Weight,rnd(14)+1)
Neuron(i).Threshold=Set_Left_Nibble(3,rnd(15))
Neuron(i).Threshold=Set_Right_Nibble(Neuron(i).Threshold,rnd(15))
Next i
Endfunction
`We need a "game" that our Tank can learn to play.
Function Game()
Set Vector3 1,(Object Position X(Count)-Object Position X(Tank)),0,(Object Position Z(count)-Object Position Z(Tank))
ed#=Length Vector3(1):`Fuel(n) Distance
if ed# < Lasted#
Lasted# = ed#
NearestFuel = count
endif
Endfunction
Function Scoring()
if Lasted# > 150.0 then Tank_Score# = Tank_Score# - 1
if Lasted#>140.0
Tank_Score# = Tank_Score#-(0.08+(Lasted#/10000.0)) :`The further the fuel gets, the more points lost.
Endif
If Lasted# <= 60.0
Tank_Score# = Tank_Score# + 200.0
Position Object NearestFuel,rnd(1600)-800,0,rnd(1600)-800
Endif
`Each Epoch (100 frames), the mosnter will update it's learning.
Endfunction
Function Epoch()
`Inc Epoch_Timer
If T <= Timer()
Inc Epoch_Counter
Inc Epoch_Timer
T = Timer()+ 500
`Endif
`Every 100 frames, the Tank will evaluate its performance and start over. This will eventually be the genetic evolution
`Here's where we score our neuron settings and store them for later:
For i = 0 to Array Count(Neuron())
For j = 0 to 1
n=Get_nibble(neuron(i).curve,j)
Gene(i,j,n).curve=Gene(i,j,n).curve+Int(Sigmoidal(Tank_Score#-Tank_Average#,500)*10)
n=Get_nibble(neuron(i).weight,j)
Gene(i,j,n).weight=Gene(i,j,n).weight+Int(Sigmoidal(Tank_Score#-Tank_Average#,500)*10)
n=Get_nibble(neuron(i).threshold,j)
Gene(i,j,n).threshold=Gene(i,j,n).threshold+Int(Sigmoidal(Tank_Score#-Tank_Average#,500)*10)
Next j
Next i
`Initialize the AI with some random values.
Initialize_AI()
`Now populate the AI with some best scores. The more Epochs that go by, the less mutation occurs.
`Note: this is sort of an inverse mutation function. The gene starts off by default as mutated.
` Then if the random selection comes up, the mutation is replaced by a successful gene.
` The more Epochs that go by, the more likely the mutations will be replaced, but never 100%.
Get_Best_Genes()
For i = 0 to Array Count(Neuron())
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).curve=Set_Left_Nibble(0,BestGenes(i,0).curve)
Neuron(i).curve=Set_Right_Nibble(Neuron(i).curve,BestGenes(i,0).curve)
Endif
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).weight=Set_Left_Nibble(0,BestGenes(i,0).weight)
Neuron(i).weight=Set_Right_Nibble(Neuron(i).weight,BestGenes(i,0).weight)
Endif
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).threshold=Set_Left_Nibble(0,BestGenes(i,0).threshold)
Neuron(i).threshold=Set_Right_Nibble(Neuron(i).threshold,BestGenes(i,0).threshold)
Endif
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).curve=Set_Left_Nibble(3,BestGenes(i,3).curve)
Neuron(i).curve=Set_Right_Nibble(Neuron(i).curve,BestGenes(i,3).curve)
Endif
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).weight=Set_Left_Nibble(3,BestGenes(i,3).weight)
Neuron(i).weight=Set_Right_Nibble(Neuron(i).weight,BestGenes(i,3).weight)
Endif
If Rnd(100+Epoch_Counter)<Epoch_Counter
Neuron(i).threshold=Set_Left_Nibble(3,BestGenes(i,3).threshold)
Neuron(i).threshold=Set_Right_Nibble(Neuron(i).threshold,BestGenes(i,3).threshold)
Endif
Next i
`Calculate the new average score
`Inc Epoch_Counter
`Weighted average so the current performance is based on past performance.
Tank_Average#=((Tank_Average# * (Epoch_Counter-1))+Tank_Score#)/Epoch_Counter
Tank_Score#=0
`Epoch_Timer=0
Initialize_AI()
endif
Endfunction
`This function chooses the "best" genes, the genes with the highest scores.
`A feature has been added to typically select 1 of the best 3 (usually) to avoid getting stuck in a niche.
Function Get_Best_Genes()
For i = 1 to Array Count(Neuron())
For j=0 to 1
best_curve1=-9999
best_weight1=-9999
best_threshold1=-9999
For k=0 to 15
If best_curve1<Gene(i,j,k).curve and rnd(3)=1 :`Typically will choose one of the best 3, but not always.
best_curve1=Gene(i,j,k).curve
BC1=k
Endif
If best_weight1<Gene(i,j,k).weight and rnd(3)=1
best_weight1=Gene(i,j,k).weight
BW1=k
Endif
If best_threshold1<Gene(i,j,k).threshold and rnd(3)=1
best_threshold1=Gene(i,j,k).threshold
BT1=k
Endif
Next k
BestGenes(i,j).curve=BC1
BestGenes(i,j).weight=BW1
BestGenes(i,j).threshold=BT1
Next j
Next i
For i = 1 to Array Count(Neuron())
For j=3 to 4
best_curve2=-9999
best_weight2=-9999
best_threshold2=-9999
For k=0 to 15
If best_curve2<Gene(i,j,k).curve and rnd(3)=1 :`Typically will choose one of the best 3, but not always.
best_curve2=Gene(i,j,k).curve
BC=k
Endif
If best_weight2<Gene(i,j,k).weight and rnd(3)=1
best_weight2=Gene(i,j,k).weight
BW=k
Endif
If best_threshold2<Gene(i,j,k).threshold and rnd(3)=1
best_threshold2=Gene(i,j,k).threshold
BT=k
Endif
Next k
BestGenes(i,j).curve=BC2
BestGenes(i,j).weight=BW2
BestGenes(i,j).threshold=BT2
Next j
Next i
Endfunction
Function Input_Data()
nearestfuel = 10
if Get_Left_Nibble(Neuron(2).value)-4 >0 :
Look_at_Fuel_Right()
Turn Object Right 1,turn#
scroll limb texture 1, 3,-0.01,0
move object 1,1.5
scroll limb texture 1, 3,-0.01,0
scroll limb texture 1, 4,-0.01,0
else
Look_at_Fuel_Right()
Turn Object Left 1,turn#
scroll limb texture 1, 4,-0.01,0
move object 1,1.5
scroll limb texture 1, 3,-0.01,0
scroll limb texture 1, 4,-0.01,0
Endif
Set Vector3 1,(Object Position X(NearestFuel)-Object Position X(Tank)),0,(Object Position Z(NearestFuel)-Object Position Z(Tank))
ed#=Length Vector3(1):`Fuel(n) Distance
range#=Sigmoidal((ed#-20.0),curve)*15.0
Neuron(1).value=Set_Left_Nibble(0,range#)
curve=Get_Right_Nibble(Neuron(1).Curve)
range#=Sigmoidal((ed#-20.0),curve)*15.0
Neuron(1).value=Set_Right_Nibble(Neuron(1).value,range#)
if Get_Left_Nibble(Neuron(5).value)-6.5 >0 :
Look_at_Fuel_Right()
`
move object 1,2
scroll limb texture 1, 3,-0.005,0
scroll limb texture 1, 4,-0.005,0
else
Look_at_Fuel_Right()
Endif
Set Vector3 1,(Object Position X(NearestFuel)-Object Position X(Tank)),0,(Object Position Z(NearestFuel)-Object Position Z(Tank))
ed#=Length Vector3(1):`Fuel(n) Distance
`Here we set the player vs. Tank range Neuron
`curve=Get_Left_Nibble(Neuron(1).Curve)
range#=Sigmoidal((ed#-20.0),curve)*15.0
`Notice the curve variable has been added to our Sigmoidal function. This will be part of how our AI learns.
`range#=Sigmoidal((ed#-pd#),Curve)*15.0
Neuron(4).value=Set_Left_Nibble(3,range#)
`Here we set the Tank vs. Fuel(n) range Neuron
curve=Get_Right_Nibble(Neuron(4).Curve)
range#=Sigmoidal((ed#-20.0),curve)*15.0
Neuron(4).value=Set_Right_Nibble(Neuron(4).value,range#)
Endfunction
Function Process_Data()
n1=Get_Left_Nibble(Neuron(1).value)
n2=Get_Right_Nibble(Neuron(1).value)
curve=Get_Left_Nibble(Neuron(2).Curve)
factor#=Sigmoidal((n1-n2),curve)*15.0
Neuron(2).value=Set_Left_Nibble(0,factor#)
n1=Get_Left_Nibble(Neuron(4).value)
n2=Get_Right_Nibble(Neuron(4).value)
curve=Get_Left_Nibble(Neuron(5).Curve)
factor#=Sigmoidal((n1-n2),curve)*15.0
Neuron(5).value=Set_Left_Nibble(3,factor#)
Endfunction
Function Output()
Neuron(0).value=0
Neuron(3).value=0
Endfunction
Function Look_at_Fuel_Right()
best# = 500000.0
For Nearest = 10 to 50
Set Vector3 1,(Object Position X(Nearest)-Limb Position X(Tank,LeftEye)),0,(Object Position Z(Nearest)-Limb Position Z(Tank,LeftEye))
ln#=Length Vector3(1)
Set Vector3 1,(Object Position X(Nearest)-Limb Position X(Tank,RightEye)),0,(Object Position Z(Nearest)-Limb Position Z(Tank,RightEye))
rn#=Length Vector3(1)
if ln# < best#
best# = ln#
NearestFuel = Nearest
Rotate = 1
Endif
if rn# < ln#
best# = rn#
NearestFuel = Nearest
Rotate = 2
Endif
Next Nearest
Set Vector3 1,(Object Position X(NearestFuel)-Limb Position X(Tank,LeftEye)),0,(Object Position Z(NearestFuel)-Limb Position Z(Tank,LeftEye))
ld#=Length Vector3(1)
Set Vector3 1,(Object Position X(NearestFuel)-Limb Position X(Tank,RightEye)),0,(Object Position Z(NearestFuel)-Limb Position Z(Tank,RightEye))
rd#=Length Vector3(1)
curve#=Get_Left_Nibble(Neuron(0).Curve)
Turn#=Sigmoidal((ld#-rd#)*2.0,curve#/10.0)*15.0
Neuron(0).value=Set_Left_Nibble(Neuron(0).value,turn#)
turn#=((Get_Left_Nibble(Neuron(0).value)-8.0)/7.0)*4.0
Rotate = rnd(1)+1
if Rotate = 1
Turn Object Left 1,turn#+1
Turn Object Left 1,0.01
scroll limb texture 1, 4,-0.01,0
else
Turn Object Right 1,turn#+1
Turn Object Right 1,0.01
scroll limb texture 1, 3,-0.01,0
endif
Endfunction
Function Sigmoidal(value#,curve#)
`the sigmoidal formula. Graph this on a spreadsheet to see how it works.
result#=1.0/(1.0+2.7183^-(value#/curve#))
Endfunction result#
`Simple Nibble Functions.
Function Set_Nibble(bt as byte, value as byte, nibble as byte)
if nibble=0 then bt = Set_Right_Nibble(bt,value)
if nibble=1 then bt = Set_Left_Nibble(bt,value)
Endfunction bt
Function Set_Left_Nibble(bt as byte,value as byte)
value = value << 4
bt = bt || value
Endfunction bt
Function Set_Right_Nibble(bt as byte,value as byte)
value = value << 4
value = value >> 4
bt = bt || value
Endfunction bt
Function Get_Nibble(bt as byte, nibble as byte)
if nibble=0 then bt=Get_Right_Nibble(bt)
if nibble=1 then bt=Get_Left_Nibble(bt)
Endfunction bt
Function Get_Left_Nibble(bt as byte)
bt = bt >> 4
Endfunction bt
Function Get_Right_Nibble(bt as byte)
bt = bt << 4
bt = bt >> 4
Endfunction bt