Unit globals_types_and_constants

Global types and constants of artificial life simulator

Introduction

See the Decide function of TAgent to understand how the weights and biases take effect. The function returns an integer code that determines the subsequent action which may be turn left, turn right, move forward or eat.

We are concerned about the difficulties of testing this type of program and have reviewed the code closely. Please let us know if you detect any errors.

The Code

unit globals_types_and_constants;
{
    Copyright (c) 2014 Steven Binns

    Licensed under the Apache License, Version 2.0 (the "License"); you may not
    use this file except in compliance with the License, as described at
    http://www.apache.org/licenses/ and http://www.pp4s.co.uk/licenses/
}
interface

uses
  Classes, SysUtils, SDL, SDL_GFX, crt, INIFiles;

const
  INHERITANCE_LEVEL = 0.2;  //80% of offspring inherit fully
  //Grid Constants
  PLANT_PLANE = 0;     //Reference to types in GridList array
  HERBIVORE_PLANE = 1; //             "  "
  CARNIVORE_PLANE = 2; //             "  "

  //Agent input references
  PLANTS_LEFT = 0;
  PLANTS_RIGHT = 1;
  PLANTS_FRONT = 2;
  PLANTS_PROXIMITY = 3;
  HERBIVORES_LEFT = 4;
  HERBIVORES_RIGHT = 5;
  HERBIVORES_FRONT = 6;
  HERBIVORES_PROXIMITY = 7;
  CARNIVORES_LEFT = 8;
  CARNIVORES_RIGHT = 9;
  CARNIVORES_FRONT = 10;
  CARNIVORES_PROXIMITY = 11;
  //General references for perception
  LEFT = 0;
  RIGHT = 1;
  FRONT = 2;
  PROXIMITY = 3;
  //Actions
  TURN_RIGHT = 0;
  TURN_LEFT = 1;
  GO_FORWARD = 2;
  EAT = 3;
  //Directions
  FACING_RIGHT = 0;
  FACING_UP = 1;
  FACING_LEFT = 2;
  FACING_DOWN = 3;
var
  MAX_STEPS: integer = 250000; //The number of steps the sim runs for
  FAST_STEPS: integer = 200000; //Number of steps before displaying the action
  DELAY: integer = 80; //Delay between frames in ms
  MAX_GRID: integer = 50; //The width/height of the grid

  //Maximum numbers for each type of agent
  MAX_PLANTS: integer = 100;
  MAX_HERBIVORES: integer = 100;
  MAX_CARNIVORES: integer = 50;

  //Energy constants
  MAX_ENERGY: integer = 500; //Maximum amount of energy a creature can store at one time
  FOOD_ENERGY: integer = 100; //Amount of energy a meal will provide
  REPRODUCTION_ENERGY: integer = 250; //Energy a creature must acquire before it can reproduce (asexually)

  HERB_REPRODUCTION_LIMIT: integer = 60; //Number of herbivores below which they can reproduce
  CARN_REPRODUCTION_LIMIT: integer = 30;  //Number of carnivores below which they can reproduce

  steps: integer; // counts the number of simulation steps that have been performed
  screen: pSDL_SURFACE;
  back: pSDL_RECT;
  cmd, tempstr: string;
  savefile : TINIFile;  //variable for files storing weights

type
  TPlant = class(TObject)
  public
    x, y: integer;
    constructor Create(CurrentListPosition: integer);
  end;

type
  TAgent = class
  public
    x, y, direction, energy, age: integer;
    inputs: array [0..11] of integer;
    weights: array[0..3, 0..11] of integer;
    outputBiases: array [0..3] of integer;
    fed: Boolean;
    procedure Move(Plane, NewX, NewY: integer);
    procedure UpdateInputs(RelPos, PerceptionX, PerceptionY: integer);
    procedure Sense;
    function Decide: integer;
  end;

   THerbivore = class(TAgent)
   public
     procedure Reproduce;
     procedure EatPlant(foodX, foodY : integer);
     procedure Act(action: integer);
     constructor Create(CurrentListPosition: integer);
   end;

   TCarnivore = class(TAgent)
   public
     procedure Reproduce;
     procedure EatHerb(foodX, foodY : integer);
     procedure Act(action: integer);
     constructor Create(CurrentListPosition: integer);
   end;

var
  PlantList: array of TPlant;
  HerbivoreList: array of THerbivore;
  CarnivoreList: array of TCarnivore;

  herbivoreCount, carnivoreCount, oldestHerbivore, oldestCarnivore:  integer;
  bestHerbivoreWeights, bestCarnivoreWeights: array [0..3, 0..11] of integer;
  bestHerbivoreBiases, bestCarnivoreBiases: array [0..3] of integer;
  GridList: array[0..2, 0..999, 0..999] of integer;
  //GridList[Type,x,y] array containing positions of the various agents in the world

  HerbNeuralBase: string; //file path of file holding base neural network for herbivores
  HerbBaseInputs: array[0..11] of integer;
  HerbBaseWeights: array[0..3, 0..11] of integer;
  HerbBaseOutputBiases: array[0..3] of integer;

  CarnNeuralBase: string; //file path of file holding base neural network for carnivores
  CarnBaseInputs: array[0..11] of integer;
  CarnBaseWeights: array[0..3, 0..11] of integer;
  CarnBaseOutputBiases: array[0..3] of integer;

function getID(plane, xin, yin: integer): integer;
procedure SetUp;
procedure Simulate;
procedure Render;
procedure Save;
procedure DisplayParameters;
procedure Finish;

implementation

//TPlant procedure
constructor TPlant.Create(CurrentListPosition: integer);
var
  i: integer;
  free_: boolean = False;
begin
  repeat
    free_ := True;
    x := Random(MAX_GRID);
    y := Random(MAX_GRID);
    for i := 0 to MAX_PLANTS - 1 do
      if PlantList[i] <> nil then
        if i <> CurrentListPosition then
          if (x = PlantList[i].x) and (y = PlantList[i].y) then  //Plant already at x,y
            free_ := False;
  until free_ = True;

  GridList[PLANT_PLANE, x, y] := 1;
end;

//TAgent procedures
procedure TAgent.UpdateInputs(RelPos, PerceptionX, PerceptionY: integer);
//Relative position RelPos can be LEFT (0), RIGHT (1), FRONT (2), PROXIMITY (3)
//Used many times by procedure sense
begin
  if GridList[PLANT_PLANE, PerceptionX, PerceptionY] = 1 then
    inputs[RelPos] += 1;
  if GridList[HERBIVORE_PLANE, PerceptionX, PerceptionY] = 1 then
    inputs[RelPos + 4] += 1;
  if GridList[CARNIVORE_PLANE, PerceptionX, PerceptionY] = 1 then
    inputs[RelPos + 8] += 1;
end;

procedure TAgent.Move(Plane, NewX, NewY: integer);
begin
  if (GridList[HERBIVORE_PLANE, NewX, NewY] = 0) and (GridList[CARNIVORE_PLANE, NewX, NewY] = 0) then
    begin
      GridList[Plane, x, y] := 0;
      x := NewX;
      y := NewY;
      GridList[Plane, x, y] := 1;
    end;
end;

procedure TAgent.Sense;
//Calculates total of inputs for each of the 12 types of input
var
  tempx, tempy: integer;
begin
  //Reset inputs
  inputs[PLANTS_LEFT] := 0;
  inputs[PLANTS_RIGHT] := 0;
  inputs[PLANTS_FRONT] := 0;
  inputs[PLANTS_PROXIMITY] := 0;

  inputs[HERBIVORES_LEFT] := 0;
  inputs[HERBIVORES_RIGHT] := 0;
  inputs[HERBIVORES_FRONT] := 0;
  inputs[HERBIVORES_PROXIMITY] := 0;

  inputs[CARNIVORES_LEFT] := 0;
  inputs[CARNIVORES_RIGHT] := 0;
  inputs[CARNIVORES_FRONT] := 0;
  inputs[CARNIVORES_PROXIMITY] := 0;

  case direction of
      FACING_RIGHT:
        begin
          //Left zone
          if y <= 1 then
            tempy := y - 2 + MAX_GRID
          else
            tempy := y - 2;
          UpdateInputs(LEFT, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(LEFT, tempx, tempy);

          //Right zone
          if y >= MAX_GRID - 2 then
            tempy := y - (MAX_GRID - 2)
          else
            tempy := y + 2;
          UpdateInputs(RIGHT, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(RIGHT, tempx, tempy);

          //Front zone
          if y <= 1 then
            tempy := y - 2 + MAX_GRID
          else
            tempy := y - 2;
          UpdateInputs(FRONT, x, tempy);

          if x >= MAX_GRID - 2 then
            tempx := x - (MAX_GRID - 2)
          else
            tempx := x + 2;
          UpdateInputs(FRONT, tempx, tempy);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(FRONT, tempx, tempy);

          if y >= MAX_GRID - 2 then
            tempy := y - (MAX_GRID - 2)
          else
            tempy := y + 2;
          UpdateInputs(FRONT, tempx, tempy);

          //Proximity zone
          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(PROXIMITY, x, tempy);
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, tempx, y);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, x, tempy);
        end;
      FACING_UP:
        begin
          //Left zone
          if x <= 1 then
            tempx := x - 2 + MAX_GRID
          else
            tempx := x - 2;
          UpdateInputs(LEFT, tempx, y);

          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;
          UpdateInputs(LEFT, tempx, tempy);

          //Right zone
          if x >= MAX_GRID - 2 then
            tempx := x - (MAX_GRID - 2)
          else
            tempx := x + 2;
          UpdateInputs(RIGHT, tempx, y);

          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;
          UpdateInputs(RIGHT, tempx, tempy);

          //Front zone
          if y <= 1 then
            tempy := (MAX_GRID - 1) - (1 - y)
          else
            tempy := y - 2;
          if x <= 1 then
            tempx := (MAX_GRID - 1) - (1 - x)
          else
            tempx := x - 2;
          UpdateInputs(FRONT, tempx, tempy);

          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          UpdateInputs(FRONT, tempx, tempy);
          UpdateInputs(FRONT, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(FRONT, tempx, tempy);

          if x >= MAX_GRID - 2 then
            tempx := x - (MAX_GRID - 2)
          else
            tempx := x + 2;
          UpdateInputs(FRONT, tempx, tempy);

          //Proximity zone
          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;
          UpdateInputs(PROXIMITY, tempx, y);
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, tempx, y);
        end;
      FACING_LEFT:
        begin
          //Left zone
          if y >= MAX_GRID - 2 then
            tempy := y - (MAX_GRID - 2)
          else
            tempy := y + 2;
          UpdateInputs(LEFT, x, tempy);

          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          UpdateInputs(LEFT, tempx, tempy);

          //Right zone
          if y <= 1 then
            tempy := (MAX_GRID - 1) - (1 - y)
          else
            tempy := y - 2;
          UpdateInputs(RIGHT, x, tempy);

          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          UpdateInputs(RIGHT, tempx, tempy);

          //Front zone
          if y <= 1 then
            tempy := (MAX_GRID - 1) - (1 - y)
          else
            tempy := y - 2;
          if x <= 1 then
            tempx := (MAX_GRID - 1) - (1 - x)
          else
            tempx := x - 2;
          UpdateInputs(FRONT, tempx, tempy);

          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;
          UpdateInputs(FRONT, tempx, tempy);
          UpdateInputs(FRONT, tempx, y);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(FRONT, tempx, tempy);

          if y >= MAX_GRID - 2 then
            tempy := y - (MAX_GRID - 2)
          else
            tempy := y + 2;
          UpdateInputs(FRONT, tempx, tempy);

          //Proximity zone
          if y = 0 then
            tempy := MAX_GRID - 1
          else
            tempy := y - 1;
          if x = 0 then
           tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          UpdateInputs(PROXIMITY, x, tempy);
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, tempx, y);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, x, tempy);
        end;
      FACING_DOWN:
        begin //If facing down
          //Left zone
          if x >= MAX_GRID - 2 then
            tempx := x - (MAX_GRID - 2)
          else
            tempx := x + 2;
          UpdateInputs(LEFT, tempx, y);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(LEFT, tempx, tempy);

          //Right zone
          if x <= 1 then
            tempx := (MAX_GRID - 1) - (1 - x)
          else
            tempx := x - 2;
          UpdateInputs(RIGHT, tempx, y);

          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y + 1;
          UpdateInputs(RIGHT, tempx, tempy);

          //Front zone
          if y >= MAX_GRID - 2 then
            tempy := y - (MAX_GRID - 2)
          else
            tempy := y + 2;
          if x <= 1 then
            tempx := (MAX_GRID - 1) - (1 - x)
          else
            tempx := x - 2;
          UpdateInputs(FRONT, tempx, tempy);

          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          UpdateInputs(FRONT, tempx, tempy);
          UpdateInputs(FRONT, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(FRONT, tempx, tempy);

          if x >= MAX_GRID - 2 then
            tempx := x - (MAX_GRID - 2)
          else
            tempx := x + 2;
          UpdateInputs(FRONT, tempx, tempy);

          //Proximity zone
          if x = 0 then
            tempx := MAX_GRID - 1
          else
            tempx := x - 1;
          if y = MAX_GRID - 1 then
            tempy := 0
          else
            tempy := y - 1;
          UpdateInputs(PROXIMITY, tempx, y);
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, x, tempy);

          if x = MAX_GRID - 1 then
            tempx := 0
          else
            tempx := x + 1;
          UpdateInputs(PROXIMITY, tempx, tempy);
          UpdateInputs(PROXIMITY, tempx, y);
        end;
    end;
end;

function TAgent.Decide: integer;
var
  output, input, i: integer;
  PropagationResults: array [0..3] of integer;
  largest: integer;
begin
  //Forward propagate the inputs through the neural network
  for output := 0 to 3 do
    begin
      //initialise using the bias
      PropagationResults[output] := outputBiases[output];
      for input := 0 to 11 do
        PropagationResults[output] += (inputs[input] * weights[output, input]);
      //multiply the input by the weight for that i/o connection and add to the propagation result
    end;

  largest := -9;
  Result := -1;

  for i := 0 to 3 do
    if PropagationResults[i] >= largest then
      begin
        largest := PropagationResults[i];
        Result := i;
      end;
end;

//THerbivore procedures
constructor THerbivore.Create(CurrentListPosition: integer);
//Don't put new herbivore where there is already a herbivore or carnivore.
var
  i, j: integer;
  free_: boolean = False;
begin
  repeat
    free_ := True;
    x := Random(MAX_GRID);
    y := Random(MAX_GRID);
    for i := 0 to MAX_HERBIVORES - 1 do
      if i <> CurrentListPosition then
        if HerbivoreList[i] <> nil then   // otherwise next line could fail
          if (x = HerbivoreList[i].x) and (y = HerbivoreList[i].y) then
            free_ := False;
    for i := 0 to MAX_CARNIVORES - 1 do
      if CarnivoreList[i] <> nil then
        if (x = CarnivoreList[i].x) and (y = CarnivoreList[i].y) then
          free_ := False;
  until free_ = True;

  energy := 2 * FOOD_ENERGY;
  direction := Random(4);

  if HerbNeuralBase = '' then
    begin
      for i := 0 to 3 do
        begin
          outputBiases[i] := random(9) - 1;
          for j := 0 to 11 do
            weights[i, j] := random(9) - 1;
        end;
    end
  else
    begin
      for i := 0 to 3 do
        outputBiases[i] := random(9) - 1;
      weights := HerbBaseWeights;
    end;

  GridList[HERBIVORE_PLANE, x, y] := 1;
  herbivoreCount += 1;
end;

procedure THerbivore.Reproduce;
var
  i, j, k : integer;
begin
  for i := 0 to MAX_HERBIVORES - 1 do
    if HerbivoreList[i] = nil then
      begin
        energy := energy div 2;
        HerbivoreList[i] := THerbivore.Create(i);
        HerbivoreList[i].energy := energy;
        HerbivoreList[i].outputBiases := outputBiases;
        for j := 0 to 3 do
          if random < INHERITANCE_LEVEL then
            begin
              HerbivoreList[i].outputBiases[j] := random(9) - 1;
              break;
            end;
        HerbivoreList[i].weights := weights;
        for j := 0 to 3 do
          for k := 0 to 11 do
            if random < INHERITANCE_LEVEL then
              begin
                HerbivoreList[i].weights[j, k] := random(9) - 1;
                break;
                break;
              end;
        break;//one birth only
      end;
end;

procedure THerbivore.EatPlant(foodX, foodY : integer);
var
  id : integer;
begin
  if (fed = False) and (GridList[PLANT_PLANE, foodX, foodY] = 1) then
    begin
      id := getID(PLANT_PLANE, foodX, foodY);
      GridList[PLANT_PLANE, foodX, foodY] := 0;
      energy += FOOD_ENERGY;
      if energy > MAX_ENERGY then
        energy := MAX_ENERGY;
      PlantList[id].Free;
      PlantList[id] := nil;
      PlantList[id] := TPlant.Create(id);
      fed := True;
    end;
end;

procedure THerbivore.Act(action: integer);
var
  tempx, tempy: integer;
begin
  case action of
    TURN_RIGHT:
      begin
        direction -= 1;
        if direction = -1 then
          direction := 3;
      end;
    TURN_LEFT:
      begin
        direction += 1;
        if direction = 4 then
          direction := 0;
      end;
    GO_FORWARD:
      begin
        case direction of
            FACING_RIGHT:
              begin
                if x >= MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                Move(HERBIVORE_PLANE, tempx, y);
              end;
            FACING_UP:
              begin
                if y <= 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;
                Move(HERBIVORE_PLANE, x, tempy);
              end;
            FACING_LEFT:
              begin
                if x <= 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                Move(HERBIVORE_PLANE, tempx, y);
              end;
            FACING_DOWN:
              begin
                if y >= MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                Move(HERBIVORE_PLANE, x, tempy);
              end;
          end;
      end;
    EAT:
      begin
        case direction of
            FACING_RIGHT:
              begin //facing right
                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;   //Immediately in front
                EatPlant(tempx, y);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;    //In front and 1 to left
                EatPlant(tempx, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;      //In front and one to right
                EatPlant(tempx, tempy);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;
                EatPlant(x, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                EatPlant(x, tempy);
              end;
            FACING_UP:
              begin
                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;    //Immediately in front
                EatPlant(x, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;      //In front and 1 place to left
                EatPlant(tempx, tempy);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;     //In front and 1 place to right
                EatPlant(tempx, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                EatPlant(tempx, y);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                EatPlant(tempx, y);
              end;
            FACING_LEFT:
              begin
                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;   //Immediately in front
                EatPlant(tempx, y);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;     //In front and one place to left
                EatPlant(tempx, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;     //In front and one place to right
                EatPlant(tempx, tempy);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;   //(facing left )
                EatPlant(x, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                EatPlant(x, tempy);
              end;
            FACING_DOWN:
              begin
                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;  //Immediately in front
                EatPlant(x, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;    //In front and 1 to right (facing down)
                EatPlant(tempx, tempy);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;   //In front and 1 to left (facing down)
                EatPlant(tempx, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                EatPlant(tempx, y);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                EatPlant(tempx, y);  //to left
              end;
          end;
      end;
    end;
  energy -= 1;
  if (energy > REPRODUCTION_ENERGY) and (herbivoreCount < HERB_REPRODUCTION_LIMIT) then
    Reproduce;
end;

//TCarnivore procedures
constructor TCarnivore.Create(CurrentListPosition: integer);
var
  i, j: integer;
  free_: boolean = False;
begin
  repeat
    x := Random(MAX_GRID);
    y := Random(MAX_GRID);
    free_ := True;
    for i := 0 to MAX_HERBIVORES - 1 do
      if i <> CurrentListPosition then
        if HerbivoreList[i] <> nil then
          if (x = HerbivoreList[i].x) and (y = HerbivoreList[i].y) then
            free_ := False;
    for i := 0 to MAX_CARNIVORES - 1 do
      if i <> CurrentListPosition then
        if CarnivoreList[i] <> nil then
          if (x = CarnivoreList[i].x) and (y = CarnivoreList[i].y) then
            free_ := False;
  until free_ = True;

  energy := 2 * FOOD_ENERGY;
  direction := Random(4);
  if CarnNeuralBase = '' then
    begin
      for i := 0 to 3 do
        begin
          outputBiases[i] := random(9) - 1;
          for j := 0 to 11 do
            weights[i, j] := random(9) - 1;
        end;
    end
  else
    begin
      for i := 0 to 3 do
        begin
          outputBiases[i] := random(9) - 1;
          for j := 0 to 11 do
            weights[i, j] := CarnBaseWeights[i, j];
        end;
    end;

  GridList[CARNIVORE_PLANE, x, y] := 1;
  carnivoreCount += 1;
end;

procedure TCarnivore.Reproduce;
var
  i, j, k : integer;
begin
  for i := 0 to MAX_CARNIVORES - 1 do
    if CarnivoreList[i] = nil then
      begin
        energy := energy div 2;
        CarnivoreList[i] := TCarnivore.Create(i);
        CarnivoreList[i].energy := energy;
        CarnivoreList[i].outputBiases := outputBiases;
        for j := 0 to 3 do
          if random < INHERITANCE_LEVEL then
            begin
              CarnivoreList[i].outputBiases[j] := random(9) - 1;
              break;
            end;
        CarnivoreList[i].weights := weights;
        for j := 0 to 3 do
          for k := 0 to 11 do
            if random < INHERITANCE_LEVEL then
              begin
                CarnivoreList[i].weights[j, k] := random(9) - 1;
                break;
                break;
              end;
        break;//one birth only
      end;
end;

procedure TCarnivore.EatHerb(foodX, foody : integer);
var
  id : integer;
begin
  if (fed = false) and (GridList[HERBIVORE_PLANE, foodX, foodY] = 1) then
    begin
      id := getID(HERBIVORE_PLANE, foodX, foodY);
      if HerbivoreList[id].age > oldestHerbivore then
        begin
          oldestHerbivore := HerbivoreList[id].age;
          bestHerbivoreWeights := HerbivoreList[id].weights;
          bestHerbivoreBiases := HerbivoreList[id].outputBiases;
        end;
      HerbivoreList[id].free;
      HerbivoreList[id] := nil;
      herbivoreCount -= 1;
      GridList[HERBIVORE_PLANE, foodX, foodY] := 0;
      energy += FOOD_ENERGY;
      if energy > MAX_ENERGY then
        energy := MAX_ENERGY;
      fed := True;
      if herbivoreCount < (MAX_HERBIVORES div 2) then
        HerbivoreList[id] := THerbivore.Create(id);
    end;
end;

procedure TCarnivore.Act(action: integer);
var
  tempx, tempy: integer;
begin
  case action of
    TURN_RIGHT:
      begin
        direction -= 1;
        if direction = -1 then
          direction := 3;
      end;
    TURN_LEFT:
      begin //Turn left
        direction += 1;
        if direction = 4 then
          direction := 0;
      end;
    GO_FORWARD:
      case direction of
            FACING_RIGHT:
              begin
                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                Move(CARNIVORE_PLANE, tempx, y);
              end;
            FACING_UP:
              begin
                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;
                Move(CARNIVORE_PLANE, x, tempy);
              end;
            FACING_LEFT:
              begin
                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                Move(CARNIVORE_PLANE, tempx, y);
              end;
            FACING_DOWN:
              begin
                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                Move(CARNIVORE_PLANE, x, tempy);
              end;
          end;//Move forwards

    EAT: case direction of
            FACING_RIGHT:
              begin
                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;    //Food directly in front
                EatHerb(tempx, y);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;       //Food in front and 1 to left
                EatHerb(tempx, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;      //Food in front and 1 to right
                EatHerb(tempx, tempy);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;
                EatHerb(x, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                EatHerb(x, tempy);   //to right
              end;
            FACING_UP:
              begin
                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;      //Immediately in front
                EatHerb(x, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;    //In front and 1 place to left
                EatHerb(tempx, tempy);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;          //in front and 1 place to right
                EatHerb(tempx, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                EatHerb(tempx, y);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                EatHerb(tempx, y);
              end;
            FACING_LEFT:
              begin
                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;        //Immediately in front
                EatHerb(tempx, y);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;     //In front and 1 to left
                EatHerb(tempx, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;    //In front and 1 to right
                EatHerb(tempx, tempy);

                if y = 0 then
                  tempy := MAX_GRID - 1
                else
                  tempy := y - 1;
                EatHerb(x, tempy);

                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;
                EatHerb(x, tempy);
              end;
            FACING_DOWN:
              begin
                if y = MAX_GRID - 1 then
                  tempy := 0
                else
                  tempy := y + 1;    //Straight in front
                EatHerb(x, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;     //In front and 1 to right
                EatHerb(tempx, tempy);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;   //In front and 1 to left
                EatHerb(tempx, tempy);

                if x = 0 then
                  tempx := MAX_GRID - 1
                else
                  tempx := x - 1;
                EatHerb(tempx, y);

                if x = MAX_GRID - 1 then
                  tempx := 0
                else
                  tempx := x + 1;
                EatHerb(tempx, y);
              end;  //facing down
            end;//case action of Eat
    end;  //case action
  energy -= 2;
  if (energy > REPRODUCTION_ENERGY) and (carnivoreCount < CARN_REPRODUCTION_LIMIT) then
    Reproduce;
end;

//Globally available routines
function getID(plane, xin, yin: integer): integer;
var
  i: integer;
begin
  Result := -1;
  case plane of
    PLANT_PLANE: for i := 0 to MAX_PLANTS - 1 do
                   if PlantList[i] <> nil then
                     if (PlantList[i].x = xin) and (PlantList[i].y = yin) then
                       begin
                         Result := i;
                         break;
                       end;
    HERBIVORE_PLANE: for i := 0 to MAX_HERBIVORES - 1 do
                       if HerbivoreList[i] <> nil then
                         if (HerbivoreList[i].x = xin) and (HerbivoreList[i].y = yin) then
                           begin
                             Result := i;
                             break;
                           end;
    CARNIVORE_PLANE: for i := 0 to MAX_CARNIVORES - 1 do
                       if CarnivoreList[i] <> nil then
                         if (CarnivoreList[i].x = xin) and (CarnivoreList[i].y = yin) then
                           begin
                             Result := i;
                             break;
                           end;
  end;
end;

procedure SetUp;
var
  i : integer;
begin
  setlength(PlantList, MAX_PLANTS);
  setlength(HerbivoreList, MAX_HERBIVORES);
  setlength(CarnivoreList, MAX_CARNIVORES);

  //Generate Plants
  for i := 0 to MAX_PLANTS - 1 do
    begin
      PlantList[i] := TPlant.Create(i);
    end;
  //Generate Herbivores
  for i := 0 to MAX_HERBIVORES - 1 do
    begin
      HerbivoreList[i] := THerbivore.Create(i);
    end;
  //Generate Carnivores
  for i := 0 to MAX_CARNIVORES - 1 do
    begin
      CarnivoreList[i] := TCarnivore.Create(i);
    end;
end;

procedure Render;
var
  j, k : integer;
begin
  sdl_fillrect(screen, back, $000000);
  for j := 0 to MAX_GRID - 1 do
    for k := 0 to MAX_GRID - 1 do
      begin
        if GridList[PLANT_PLANE, j, k] = 1 then
          filledCircleColor(screen, (j * (800 div MAX_GRID)) + ((800 div MAX_GRID) div 2),
                           (k * (600 div MAX_GRID)) +  ((600 div MAX_GRID) div 2),
                           (600 div MAX_GRID) div 2, $00FF00FF);
        if GridList[HERBIVORE_PLANE, j, k] = 1 then
          begin //Draw blue circles and black direction lines.
            filledCircleColor(screen, (j * (800 div MAX_GRID)) + ((800 div MAX_GRID) div 2),
                              (k * (600 div MAX_GRID)) + ((600 div MAX_GRID) div 2),
                              (600 div MAX_GRID) div 2, $0000FFFF);
            case HerbivoreList[getID(1, j, k)].direction of
              FACING_RIGHT: hlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), (j+1)*(800 DIV MAX_GRID),(k*(600 DIV MAX_GRID))+((600 DIV MAX_GRID) DIV 2),$000000FF);
              FACING_UP: vlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), k*(600 DIV MAX_GRID), k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, $000000FF);
              FACING_LEFT: hlinecolor(screen, j * (800 DIV MAX_GRID), j * (800 DIV MAX_GRID) + (800 DIV MAX_GRID) DIV 2, k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, $000000FF);
              FACING_DOWN: vlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, (k + 1) * (600 DIV MAX_GRID), $000000FF);
            end;
          end;

          if GridList[CARNIVORE_PLANE, j, k] = 1 then
            begin  //Draw red circles and black direction lines.
              filledCircleColor(screen, (j * (800 div MAX_GRID)) + ((800 div MAX_GRID) div 2),
                               (k * (600 div MAX_GRID)) + ((600 div MAX_GRID) div 2),
                               (600 div MAX_GRID) div 2, $FF0000FF);
              case CarnivoreList[getID(2, j, k)].direction of
                FACING_RIGHT: hlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), (j+1)*(800 DIV MAX_GRID),(k*(600 DIV MAX_GRID))+((600 DIV MAX_GRID) DIV 2),$000000FF);
                FACING_UP: vlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), k*(600 DIV MAX_GRID), k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, $000000FF);
                FACING_LEFT: hlinecolor(screen, j * (800 DIV MAX_GRID), j * (800 DIV MAX_GRID) + (800 DIV MAX_GRID) DIV 2, k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, $000000FF);
                FACING_DOWN: vlinecolor(screen, (j*(800 DIV MAX_GRID))+((800 DIV MAX_GRID) DIV 2), k * (600 DIV MAX_GRID) + (600 DIV MAX_GRID) DIV 2, (k + 1) * (600 DIV MAX_GRID), $000000FF);
              end;
            end;
        end;
  sdl_flip(screen);
  sdl_delay(DELAY);
end;

procedure Simulate;
var
  i : integer;
begin
  for steps := 0 to MAX_STEPS - 1 do
    begin
      //Simulate herbivores (herbivores first to give them a chance to run from predators)
      for i := 0 to MAX_HERBIVORES - 1 do
        begin
          if HerbivoreList[i] <> nil then
            begin
              HerbivoreList[i].Sense;
              HerbivoreList[i].fed:= false;
              HerbivoreList[i].Act(HerbivoreList[i].Decide);
              HerbivoreList[i].age += 1;
              if HerbivoreList[i].energy <= 0 then
                begin
                  if HerbivoreList[i].age > oldestHerbivore then
                    begin
                      oldestHerbivore := HerbivoreList[i].age;
                      bestHerbivoreWeights := HerbivoreList[i].weights;
                      bestHerbivoreBiases := HerbivoreList[i].outputBiases;
                    end;
                  GridList[HERBIVORE_PLANE, HerbivoreList[i].x, HerbivoreList[i].y] := 0;
                  HerbivoreList[i].Free;
                  HerbivoreList[i] := nil;
                  herbivoreCount -= 1;
                  if herbivoreCount < (MAX_HERBIVORES div 2) then
                    HerbivoreList[i] := THerbivore.Create(i);
                end;
            end;
        end;
      //Simulate carnivores
      for i := 0 to MAX_CARNIVORES - 1 do
        begin
          if CarnivoreList[i] <> nil then
            begin
              CarnivoreList[i].Sense;
              CarnivoreList[i].fed:= false;
              CarnivoreList[i].Act(CarnivoreList[i].Decide);
              CarnivoreList[i].age += 1;
              if CarnivoreList[i].energy <= 0 then
                begin
                  if CarnivoreList[i].age > oldestCarnivore then
                    begin
                       oldestCarnivore := CarnivoreList[i].age;
                       bestCarnivoreWeights := CarnivoreList[i].weights;
                       bestCarnivoreBiases := CarnivoreList[i].outputBiases;
                    end;
                  GridList[CARNIVORE_PLANE, CarnivoreList[i].x, CarnivoreList[i].y] := 0;
                  CarnivoreList[i].free;
                  CarnivoreList[i] := nil;
                  carnivoreCount -= 1;
                  if carnivoreCount < (MAX_CARNIVORES div 2) then
                    CarnivoreList[i] := TCarnivore.Create(i);
                end;
            end;
        end;
     if (cmd = 'run') and (steps = FAST_STEPS) then
       screen := sdl_setvideomode(800, 600, 32, SDL_SWSURFACE {or SDL_FULLSCREEN});
     if (cmd = 'run') and (steps > FAST_STEPS) then
       Render;
  end;
end;

procedure Save; //Saves neural network weights
var
  i : integer;
begin
  tempstr := DateTimeToStr(Now);
  tempstr := StringReplace(tempstr, '/', '-', [rfReplaceAll]);
  tempstr := StringReplace(tempstr, ':', '', [rfReplaceAll]);
  savefile := TINIFile.Create(GetCurrentDir() +
    '\Networks\Herbivores\' + tempstr + ' - Herbivore.ini');
  for i := 0 to 11 do
    savefile.WriteInteger('Turn Left', IntToStr(i), bestHerbivoreWeights[0, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Turn Right', IntToStr(i), bestHerbivoreWeights[1, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Move Forwards', IntToStr(i),  bestHerbivoreWeights[2, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Eat', IntToStr(i), bestHerbivoreWeights[3, i]);
  savefile.Free;

  savefile := TINIFile.Create(GetCurrentDir() +
    '\Networks\Carnivores\' + tempstr + ' - Carnivore.ini');
  for i := 0 to 11 do
    savefile.WriteInteger('Turn Left', IntToStr(i), bestCarnivoreWeights[0, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Turn Right', IntToStr(i), bestCarnivoreWeights[1, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Move Forwards', IntToStr(i), bestCarnivoreWeights[2, i]);
  for i := 0 to 11 do
    savefile.WriteInteger('Eat', IntToStr(i), bestCarnivoreWeights[3, i]);
  savefile.Free;
end;

procedure DisplayParameters;
begin
  writeln(#13#10'-- Parameters --'#13#10);
  writeln('Steps (s): ', MAX_STEPS);
  writeln('Fast Steps before Display (fs): ', FAST_STEPS);
  writeln('Delay in ms between frames (delay): ', DELAY);
  writeln('Max Plants (p): ', MAX_PLANTS);
  writeln('Max Herbivores (h): ', MAX_HERBIVORES);
  writeln('Max Carnivores (c): ', MAX_CARNIVORES);
  writeln('Grid Size (g): ', MAX_GRID, 'x', MAX_GRID);
  writeln('Max Energy (e): ', MAX_ENERGY);
  writeln('Energy per Food Unit (fe): ', FOOD_ENERGY);
  writeln('Reproduction Energy (re): ', REPRODUCTION_ENERGY);
end;

procedure Finish;
var
  i, j, k : integer;
begin
  writeln(#13#10'Oldest Herbivore: ', oldestHerbivore);
  writeln('Oldest Carnivore: ', oldestCarnivore);

  writeln(#13#10'Best herbivore bias for action turn left: ', BestHerbivoreBiases[0]);
  writeln('Best herbivore bias for action turn right: ', BestHerbivoreBiases[1]);
  writeln('Best herbivore bias for action move forward: ', BestHerbivoreBiases[2]);
  writeln('Best herbivore bias for action eat: ', BestHerbivoreBiases[3]);

  writeln(#13#10'Best carnivore bias for action turn left: ', BestCarnivoreBiases[0]);
  writeln('Best carnivore bias for action turn right: ', BestCarnivoreBiases[1]);
  writeln('Best carnivore bias for action move forward: ', BestCarnivoreBiases[2]);
  writeln('Best carnivore bias for action eat: ', BestCarnivoreBiases[3]);

  writeln(#13#10'Done!');
  oldestHerbivore := 0;
  oldestCarnivore := 0;
  herbivoreCount := 0;
  carnivoreCount := 0;

  for i := 0 to MAX_HERBIVORES - 1 do
    HerbivoreList[i] := nil;
  for i := 0 to MAX_CARNIVORES - 1 do
    CarnivoreList[i] := nil;
  for i := 0 to MAX_PLANTS - 1 do
    PlantList[i] := nil;

  for i := 0 to 2 do
    for j := 0 to MAX_GRID - 1 do
      for k := 0 to MAX_GRID - 1 do
        GridList[i, j, k] := 0;
  readln;
end;

end.
Programming - a skill for life!

by Steven Binns: Y13 Age ~18