Results 1 to 10 of 13

Thread: Supraleiter - an anti-gravity futuristic racing indie game

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    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,.

  2. #2
    Why are you saving the AI information in so many files (1073741824)?
    I'm willing to bet that opening and closing so many file handles is actually the biggest deadlock.
    Wouldn't it be better if you would have all this data saved in a single file instead?

  3. #3
    You should read the code again and then this time better 1073741824 is only the theoretical maximum count, see the "break;" in the if-FileExists-else-branch.

    Every training data set file is a single own game total round, and these files can by different human players from the developer team, so the training routine searchs all data set files in the input directory and then processes these one after the other many times (<= TeachIterations).

    Did you understand it now?

  4. #4
    Quote Originally Posted by BeRo View Post
    Did you understand it now?
    OK now I understand better.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •