23Apr
Multiple intensities
Multiple intensities

In this article, I want to make the task more complex. Last time, I implemented an organism that selected the maximum intensity around it and moved on. By intensity, I mean a circular gradient; the organism’s task is to reach the maximum point in the shortest way possible. There was only one center of intensity, and consequently, there was only one path of upward movement along the increasing gradient. This time, there will be multiple intensities, the path will be more winding, and the organism will make decisions based on the average intensity values around it.

Implementation of multiple intensity sources

So, I have a desire to infinitely add the number of possible sources, and I decided to describe the intensity with its control elements in the form of a class. Accordingly, next time, we will only need to create a new object.

Multiple Intensities' Controls
Multiple Intensities’ Controls

The main method, which will create the intensity, remains almost unchanged:

applyGradient(xCenter, yCenter) {
    const newGradient = [];
    const radius = this.selectedIntensity;

    const minX = Math.max(0, xCenter - radius);
    const maxX = Math.min(this.numberOfColumns - 1, xCenter + radius);
    const minY = Math.max(0, yCenter - radius);
    const maxY = Math.min(this.numberOfRows - 1, yCenter + radius);

    for (let y = minY; y <= maxY; y++) {
      for (let x = minX; x <= maxX; x++) {
        const xDistance = xCenter - x;
        const yDistance = yCenter - y;
        const distance = Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
        const intensity = Number(Math.max(0, this.selectedIntensity - distance).toFixed(2));
        const color = this.selectedColor;

        if (intensity > 0) {
          newGradient.push({
            x: x,
            y: y,
            color: color,
            intensity: intensity
          });
        }
      }
    }

    if (_.isEmpty(this.gradientIntensityMap)) {
      this.gradientIntensityMap = newGradient;
      this.gradientIndex = this.gridInstance.registerNewIntensity(newGradient);
      return;
    }

    this.gradientIntensityMap = newGradient;
    this.gridInstance.updateIntensity(this.gradientIndex, newGradient);
  }

Next, since there will be multiple intensities, we face the task of combining the intensity results together on the field. We need to calculate the sum of colors and intensities for each cell of the field where the intersection will occur. First, I register each new intensity using the Grid class method: registerNewIntensity. After that, I perform the calculation of the combined intensities.

Combined intensities
Combined intensities

The method that combines the intensities is extracted into the class that creates the field grid. Here’s what it looks like:

mergeIntensities() {
  this.intensityMap = {};

    for (const key in this.intensityInstances) {
      const instanceIntensityArray = this.intensityInstances[key];
      for (const cell of instanceIntensityArray) {
        const numberOfTheCell = cell.y * this.numberOfColumns + cell.x;

        const currentIntensityInstance = this.intensityMap[numberOfTheCell];

        if (cell.organism) {
          this.intensityMap[numberOfTheCell] = {
            color: cell.color,
            organism: true,
            intensity: currentIntensityInstance.intensity
          };
          continue;
        }

        let mergedIntensities;
        if (!currentIntensityInstance) {
          mergedIntensities = cell.intensity;
          this.intensityMap[numberOfTheCell] = {
            color: cell.color,
            intensity: cell.intensity,
          };
          continue;
        }

        mergedIntensities = (Number(currentIntensityInstance.intensity) + Number(cell.intensity));
        if (mergedIntensities > this.maxIntensity) {
            mergedIntensities = this.maxIntensity;
        }
        const mergedColors = mergeRGBColors(currentIntensityInstance.color, cell.color);
        this.intensityMap[numberOfTheCell] = {
          color: mergedColors,
          intensity: mergedIntensities,
        };
      }
    }

    this.reDrawTheGrid();
  }

Another interesting innovation in the new approach is choosing the next cell step based on the highest average value of the intensity sum of the 9 surrounding cells instead of choosing the cell with the highest intensity.

8 Neighbour cells
8 Neighbour cells

In the previous implementation, I selected the cell with the highest intensity and moved forward. In the new implementation, I choose the cell with the highest average intensity value around it. In this case, we have a higher probability that we will end up in the area with the highest possible intensity around the new cell for the next step. At the same time, the organism can only see one cell deep around itself. That is, when choosing the next cell, the organism will rely on the average value of the visible cells.

setDecisionMap (){
    const dataMap = this.getKeyDataMap();
    console.log ("dataMap", dataMap);
    const neighbors = [
      [-1, -1], [-1, 0], [-1, 1],
      [0, -1], [0, 0], [0, 1],
      [1, -1], [1, 0], [1, 1],
    ];

    let keyDecisionMap = {};
    let decisionMap = [];

    for (const dataKey in dataMap) {
      const dataInstance = dataMap[dataKey];
      let numberOfneighbours = 0;
      let neighboursIntensitySum = 0;

      for (const [dx, dy] of neighbors) {
        let x = dataInstance.x + dx;
        let y = dataInstance.y + dy;
        let neighbourIntsance = dataMap[y * this.numberOfColumns + x];
        if (neighbourIntsance){
          numberOfneighbours++;
          neighboursIntensitySum = neighboursIntensitySum + neighbourIntsance.intensity;
        }
      }

      const decisionValue = Math.round(neighboursIntensitySum / numberOfneighbours * 1000) / 1000;
      const decision = {
        x: dataInstance.x,
        y: dataInstance.y,
        value: decisionValue
      };
      keyDecisionMap[dataKey] = decision;
      decisionMap.push(decision);
      }

      this.keyDecisionMap = keyDecisionMap;
      this.decisionMap = decisionMap;
    }

Why did we need a new approach? Because with intersecting intensities, the organism may face a choice between two cells with the same intensity. In the case of a single intensity, this problem did not arise. In the new approach, we will calculate the average intensity value for each area of the potential step. This way, the decision will be more balanced, and the organism will end up in an area with a higher potential on average across the surrounding field. Using the previous logic, the organism could easily end up in an area with a high central intensity and low intensity around it.

Although the new solution for choosing the next step is more balanced, the choice based on the average value in its current form does not solve the uncertainty problem. There are still many areas with the same average value, which is not much different from the first case, and the organism will go in circles, ending up in zones with the same average intensity for neighboring cells. We will try to address this issue more thoroughly next time.

Link to the source: https://github.com/brnikita/Cellular-Automata/tree/Step-2.-Multiple-Intensities
Previous article: https://soshace.com/my-way-to-agi-infusoria-slipper/

Leave a Reply