The FFNN-based AI is very simple. It's no big magic against it. The AI has the following inputs (mainly raycast distances):
Code:
{$ifdef FewerRays}
CountSensorRays=9;
CountSensorOpponentRays=5;
{$else}
CountSensorWallRays=19;
CountSensorFloorRays=3;
CountSensorCeilRays=3;
CountSensorOpponentRays=36;
{$endif}
PGameShipSensors=^TGameShipSensors;
TGameShipSensors=record
WayPointAngle:single;
Speed:single;
{$ifdef FewerRays}
Rays:array[0..CountSensorRays-1] of single;
{$else}
WallRays:array[0..CountSensorWallRays-1] of single;
FloorRays:array[0..CountSensorFloorRays-1] of single;
CeilRays:array[0..CountSensorCeilRays-1] of single;
{$endif}
OpponentRays:array[0..CountSensorOpponentRays-1] of single;
end;
and the following output:
Code:
PGameShipControllerInput=^TGameShipControllerInput;
TGameShipControllerInput=record
Steering:single;
Pitch:single;
Accelerate:single;
AirBrakeLeft:single;
AirBrakeRight:single;
Nitro:single;
Fire:single;
end;
All input & output values are in the 0.0 .. 1.0 range, which'll converted to or from the -1.0 .. 1.0 range, depending of the input/output value kind.
Code:
procedure TGameShip.UpdateAI(DeltaTime:double);
var Counter,Index:longint;
AnalogControlLowPassFilterCoef:single;
begin
if TGame(Game).Started then begin
AnalogControlLowPassFilterCoef:=1.0-exp(-(DeltaTime*100.0));
FFNN.Inputs[0]:=Sensors.WayPointAngle;
FFNN.Inputs[1]:=Sensors.Speed;
Index:=2;
{$ifndef FewerRays}
for Counter:=0 to CountSensorWallRays-1 do begin
FFNN.Inputs[Index]:=Sensors.WallRays[Counter];
inc(Index);
end;
for Counter:=0 to CountSensorFloorRays-1 do begin
FFNN.Inputs[Index]:=Sensors.FloorRays[Counter];
inc(Index);
end;
for Counter:=0 to CountSensorCeilRays-1 do begin
FFNN.Inputs[Index]:=Sensors.CeilRays[Counter];
inc(Index);
end;
{$endif}
for Counter:=0 to CountSensorOpponentRays-1 do begin
FFNN.Inputs[Index]:=Sensors.OpponentRays[Counter];
inc(Index);
end;
FFNN.Calculate;
ControllerInput.Steering:=FloatLerp(ControllerInput.Steering,Min(Max((FFNN.Outputs[0]*2.0)-1.0,-1.0),1.0),AnalogControlLowPassFilterCoef);
ControllerInput.Accelerate:=FloatLerp(ControllerInput.Accelerate,Min(Max((FFNN.Outputs[1]*2.0)-1.0,-1.0),1.0),AnalogControlLowPassFilterCoef);
ControllerInput.Pitch:=FloatLerp(ControllerInput.Pitch,Min(Max((FFNN.Outputs[2]*2.0)-1.0,-1.0),1.0),AnalogControlLowPassFilterCoef);
ControllerInput.AirBrakeLeft:=Min(Max(FFNN.Outputs[3],0.0),1.0);
ControllerInput.AirBrakeRight:=Min(Max(FFNN.Outputs[4],0.0),1.0);
end else begin
FillChar(ControllerInput,SizeOf(TGameShipControllerInput),#0);
end;
end;
So the FFNN-based AI simulates just human analog-controller input. And the training code is:
Code:
procedure TrainAI;
const TeachIterations=1024;
var i,Inputs,Hidden,Outputs,TeachIteration,Index,SubIndex,TimeStatesIndex,i32,
Counter:longint;
TimeStates:array of TGameShipTimeStates;
TimeState:PGameShipTimeState;
pn,fn:ansistring;
fs:TFileStream;
FFNN:TGameFFNN;
begin
FFNN:=nil;
TimeStates:=nil;
try
{$ifdef FewerRays}
Inputs:=2+CountSensorRays+CountSensorOpponentRays;
{$else}
Inputs:=2+CountSensorWallRays+CountSensorFloorRays+CountSensorCeilRays+CountSensorOpponentRays;
{$endif}
Outputs:=3;
Hidden:=(Inputs+Outputs)*4;
FFNN:=TGameFFNN.Create([Inputs,Hidden,Hidden,Outputs],0.3,0.9);
SetLength(TimeStates,0);
pn:=IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)))+'aitrainingdata';
if DirectoryExists(pn) then begin
pn:=IncludeTrailingPathDelimiter(pn);
i:=0;
repeat
fn:=pn+'dataset'+IntToStr(i)+'.bin';
if FileExists(fn) then begin
inc(i);
end else begin
break;
end;
until i>$40000000;
SetLength(TimeStates,i);
i:=0;
repeat
fn:=pn+'dataset'+IntToStr(i)+'.bin';
if FileExists(fn) then begin
if (i+1)>length(TimeStates) then begin
SetLength(TimeStates,i+1);
end;
fs:=TFileStream.Create(fn,fmOpenRead);
try
if fs.Read(i32,SizeOf(longint))=SizeOf(longint) then begin
TimeStates[i].Count:=Max(0,i32);
SetLength(TimeStates[i].Items,TimeStates[i].Count);
if TimeStates[i].Count>0 then begin
for Index:=0 to TimeStates[i].Count-1 do begin
fs.Read(TimeStates[i].Items[Index].Sensors,SizeOf(TGameShipSensors));
fs.Read(TimeStates[i].Items[Index].ControllerInput,SizeOf(TGameShipControllerInput));
end;
end;
end;
finally
fs.Free;
end;
inc(i);
end else begin
break;
end;
until i>$40000000;
for TeachIteration:=1 to TeachIterations do begin
write(#13,'Training AI... ',(TeachIteration/TeachIterations)*100.0:6:2,' %');
for TimeStatesIndex:=0 to length(TimeStates)-1 do begin
for Index:=0 to TimeStates[TimeStatesIndex].Count-1 do begin
TimeState:=@TimeStates[TimeStatesIndex].Items[Index];
begin
FFNN.Inputs[0]:=TimeState^.Sensors.WayPointAngle;
FFNN.Inputs[1]:=TimeState^.Sensors.Speed;
SubIndex:=2;
{$ifdef FewerRays}
for Counter:=0 to CountSensorRays-1 do begin
FFNN.Inputs[SubIndex]:=TimeState^.Sensors.Rays[Counter];
inc(SubIndex);
end;
{$else}
for Counter:=0 to CountSensorWallRays-1 do begin
FFNN.Inputs[SubIndex]:=TimeState^.Sensors.WallRays[Counter];
inc(SubIndex);
end;
for Counter:=0 to CountSensorFloorRays-1 do begin
FFNN.Inputs[SubIndex]:=TimeState^.Sensors.FloorRays[Counter];
inc(SubIndex);
end;
for Counter:=0 to CountSensorCeilRays-1 do begin
FFNN.Inputs[SubIndex]:=TimeState^.Sensors.CeilRays[Counter];
inc(SubIndex);
end;
{$endif}
for Counter:=0 to CountSensorOpponentRays-1 do begin
FFNN.Inputs[SubIndex]:=TimeState^.Sensors.OpponentRays[Counter];
inc(SubIndex);
end;
end;
FFNN.Calculate;
begin
FFNN.DesiredOutputs[0]:=Min(Max((TimeState^.ControllerInput.Steering+1.0)*0.5,0.0),1.0);
FFNN.DesiredOutputs[1]:=Min(Max((TimeState^.ControllerInput.Accelerate+1.0)*0.5,0.0),1.0);
FFNN.DesiredOutputs[2]:=Min(Max((TimeState^.ControllerInput.Pitch+1.0)*0.5,0.0),1.0);
FFNN.DesiredOutputs[3]:=Min(Max(TimeState^.ControllerInput.AirBrakeLeft,0.0),1.0);
FFNN.DesiredOutputs[4]:=Min(Max(TimeState^.ControllerInput.AirBrakeRight,0.0),1.0);
end;
FFNN.BackPropagate;
end;
end;
write(#13,'Training AI... ');
write(#13,'Training AI... ');
fn:=IncludeTrailingPathDelimiter(IncludeTrailingPathDelimiter(ExtractFilePath(ParamStr(0)))+'assets')+'aibrain.bin';
fs:=TFileStream.Create(fn,fmCreate);
try
FFNN.SaveToStream(fs);
finally
fs.Free;
end;
end;
end;
finally
FFNN.Free;
SetLength(TimeStates,0);
end;
end;
Overall very simple, but it works very well for the small size and simplicity of the code, since the FFNN-based AI is driving almost without any accident and in a very human-style behaviour way (since the recorded training datasets are by-human-drived track rounds). 
And Supraleiter supports already most all input possibles, keyboard, gamepad controller, touchscreen (but due to a SDL 2.0 bug, only under Win7, with Win8(.1) has SDL 2.0 some problems yet, due to some small multitouch system API changes from Win7 to Win
, accelometer, mouse, touchpad, etc. etc. etc,.
Bookmarks