public void act(BucketbotBase self) {
      setDrawBolded(true);

      if (curTime < cruiseUntil) return;

      MersenneTwisterFast rand = SimulationWorld.rand;

      // if doing something else (stateQueue isn't empty), are trying to move to a new location,
      // but there's another bucketbot at that location, then sit and wait most of the time
      if (stateQueue.size() > 1
          && stateQueue.get(1).getClass() == BucketbotMove.class
          && !SimulationWorldSimpleExample.getSimulationWorld()
              .map
              .isBucketbotMoveValid(
                  self,
                  ((BucketbotMove) stateQueue.get(1)).moveToX,
                  ((BucketbotMove) stateQueue.get(1)).moveToY)) {

        // usually sit and wait
        if (rand.nextFloat() < .7f) return;
      }

      setTargetSpeed(getMaxVelocity());

      float min_visible_distance = 3 * getRadius();
      float new_direction = getBestEvadeDirection(min_visible_distance);
      if (getDirection() != new_direction) setDirection(new_direction);

      if (rand.nextFloat() < .5f) {
        stateQueue.remove(0);
        setDrawBolded(false);
        if (stateQueue.size() > 0) stateQueue.get(0).act(self);
      }
    }
  public String addError(
      String str, double insertionRate, double deletionRate, double substitutionRate) {
    if (insertionRate < 0.0 || deletionRate < 0.0 || substitutionRate < 0.0)
      throw new MhapRuntimeException("Error rate cannot be negative.");

    if (insertionRate + deletionRate + substitutionRate > 1.00001)
      throw new MhapRuntimeException("Error rate must be less than or equal to 1.0.");

    double errorRate = insertionRate + deletionRate + substitutionRate;

    // use a linked list for insertions
    LinkedList<Character> modifiedSequence = new LinkedList<>();
    for (char a : str.toCharArray()) modifiedSequence.add(a);

    // now mutate
    ListIterator<Character> iter = modifiedSequence.listIterator();
    while (iter.hasNext()) {
      char i = iter.next();

      if (randGenerator.nextDouble() < errorRate) {
        double errorType = randGenerator.nextDouble();
        if (errorType < substitutionRate) { // mismatch
          // switch base

          iter.set(getRandomBase(i));

          i++;
        } else if (errorType < insertionRate + substitutionRate) { // insert

          iter.previous();
          iter.add(getRandomBase(null));
        } else { // delete

          iter.remove();
        }
      } else {
        // i++;
      }
    }

    StringBuilder returnedString = new StringBuilder(modifiedSequence.size());
    for (char c : modifiedSequence) returnedString.append(c);

    return returnedString.toString();
  }
  /**
   * Generate a random value from this distribution.
   *
   * @param rng random number generator
   * @return random value from this distribution
   */
  public double generate(MersenneTwisterFast rng) {
    try {
      return gammaDist.inverseCumulativeProbability(rng.nextDouble());
    } catch (MathException e) {
      System.out.println(e.getMessage());
      e.printStackTrace();
      System.exit(-1);
    }

    return -1d;
  }
  /**
   * Generates random squares for the pieces of the provided tablebase.
   *
   * @param tablebase computerized database containing all possible legal chess positions and their
   *     evaluations, given the set of specific chess pieces
   * @return the bidirectional map from the pieces present in this chess position, to the squares
   *     they occupy
   */
  static BiMap<Piece, Square> generateRandomSquaresForPieces(Tablebase tablebase) {
    BiMap<Piece, Square> piecesWithSquares = EnumBiMap.create(Piece.class, Square.class);
    List<Piece> pieces = tablebase.getAllPieces();

    MersenneTwisterFast numberGenerator = new MersenneTwisterFast();

    Square randSquare = null;
    for (Piece piece : pieces) {
      do {
        if (piece.getPieceType() == PieceType.PAWN) {
          int pRand = numberGenerator.nextInt(Squares.PAWN_SQUARES.numberOfSquares());
          randSquare = Squares.PAWN_SQUARES.getSquaresAsList().get(pRand);
        } else {
          int allRand = numberGenerator.nextInt(Squares.ALL_SQUARES.numberOfSquares());
          randSquare = Squares.ALL_SQUARES.getSquaresAsList().get(allRand);
        }
      } while (piecesWithSquares.containsValue(randSquare));

      piecesWithSquares.put(piece, randSquare);
    }
    return piecesWithSquares;
  }
  int[] findEmptySiteCancer(int x, int y) {
    LinkedList vacantSitesCancer = new LinkedList();
    int[] tp1 = new int[2];
    int[] tp2 = new int[2];
    int[] tp3 = new int[2];
    int[] tp4 = new int[2];
    int[] tp5 = new int[2];
    int[] tp6 = new int[2];
    int[] tp7 = new int[2];
    int[] tp8 = new int[2];

    tp1 = convertCoordinatesNoFlux(x + 1, y - 1);
    if ((Cells[tp1[0]][tp1[1]] == 0) || (Cells[tp1[0]][tp1[1]] == 4)) vacantSitesCancer.add(tp1);
    tp2 = convertCoordinatesNoFlux(x + 1, y);
    if ((Cells[tp2[0]][tp2[1]] == 0) || (Cells[tp2[0]][tp2[1]] == 4)) vacantSitesCancer.add(tp2);
    tp3 = convertCoordinatesNoFlux(x + 1, y + 1);
    if ((Cells[tp3[0]][tp3[1]] == 0) || (Cells[tp3[0]][tp3[1]] == 4)) vacantSitesCancer.add(tp3);
    tp4 = convertCoordinatesNoFlux(x, y - 1);
    if ((Cells[tp4[0]][tp4[1]] == 0) || (Cells[tp4[0]][tp4[1]] == 4)) vacantSitesCancer.add(tp4);
    tp5 = convertCoordinatesNoFlux(x, y + 1);
    if ((Cells[tp5[0]][tp5[1]] == 0) || (Cells[tp5[0]][tp5[1]] == 4)) vacantSitesCancer.add(tp5);
    tp6 = convertCoordinatesNoFlux(x - 1, y - 1);
    if ((Cells[tp6[0]][tp6[1]] == 0) || (Cells[tp6[0]][tp6[1]] == 4)) vacantSitesCancer.add(tp6);
    tp7 = convertCoordinatesNoFlux(x - 1, y);
    if ((Cells[tp7[0]][tp7[1]] == 0) || (Cells[tp7[0]][tp7[1]] == 4)) vacantSitesCancer.add(tp7);
    tp8 = convertCoordinatesNoFlux(x - 1, y + 1);
    if ((Cells[tp8[0]][tp8[1]] == 0) || (Cells[tp8[0]][tp8[1]] == 4)) vacantSitesCancer.add(tp8);

    // Now let's see where.
    if (vacantSitesCancer.size()
        > 0) { // Now choose a vacant one, otherwise return the original location
      // pick a vacant site and return it
      int vacantElemIndexCancer = random.nextInt(vacantSitesCancer.size());
      int[] p = (int[]) vacantSitesCancer.get(vacantElemIndexCancer);
      return (int[]) p;
    } else {
      int[] p = new int[2];
      p[0] = x;
      p[1] = y; // Just return the original
      System.out.println("wrong!:" + vacantSitesCancer(x, y) + " - " + vacantSitesCancer.size());
      return p;
    }
  }
  // main CELL CA loop************
  public boolean iterateCells() {
    /*
    	// modify consumption matrix
    	    	for (int i=0;i<size;i++)
    	        for (int j=0;j<size;j++) consumption[i][j] = consumptionBasal[Cells[i][j]];
    */
    //
    if (cellList == null) cellList = new Bag(size * size);
    for (int i = 0; i < size; i++)
      for (int j = 0; j < size; j++) {
        if (Cells[i][j]
            < 4) { // All tumour cell types have Cell > 0, now 0 corresponds to 'healthy cells' that
          // consume at basal rate only
          int[] p = new int[2];
          p[0] = i;
          p[1] = j;
          cellList.add(p);
          if (Cells[i][j] == 1) {
            stem_cells_this_TS++;
          } else if (Cells[i][j] == 2 || Cells[i][j] == 3) {
            non_stem_cells_this_TS++;
          }
        }
      }

    while (cellList.size() != 0) {
      // Select the next lattice element at random
      int randomElemIndex = 0;
      if (cellList.size() > 1) randomElemIndex = random.nextInt(cellList.size() - 1);
      int[] point = (int[]) cellList.get(randomElemIndex);
      int rI = point[0];
      int rJ = point[1];

      cellList.remove(randomElemIndex); // Remove it from the cell list
      int cell = Cells[rI][rJ];

      // Cell death
      // if ((Oxygen[rI][rJ]<hypoxia)) {
      if ((random.nextFloat() < deathprob) && Cells[rI][rJ] > 0) { // x% chances of dying
        Age[rI][rJ] = 0;
        if (Cells[rI][rJ] == 1) stemDeathCounter[rI][rJ]++;
        if (Cells[rI][rJ] < 4) {
          // TACDeathCounter[rI][rJ]++;
          Cells[rI][rJ] = 4; // was 0, now making necrotic area (truly empty)
          deaths++;
          stemBirthCounter[rI][rJ] = 0;
          carriedmutation[rI][rJ] = 0; // empty space now has no mutations
          carriedGenome[rI][rJ] = initGenome; // empty space now has no mutations
        }
      } else if ((cell == 3)
          && (Age[rI][rJ]
              > 100 * maxMatureCellAge)) { // added * to allow for an update each celltimestep/x
        // **************************
        Age[rI][rJ] = 0;
        Cells[rI][rJ] = 4; // was 0, now making necrotic area (truly empty)
        // TACDeathCounter[rI][rJ]++;
        deaths++;
      } else if ((radiotherapy) && (cell == 2) && (random.nextFloat() > Oxygen[rI][rJ])) {
        // Radiotherapy
        Age[rI][rJ] = 0;
        if (Cells[rI][rJ] == 1) stemDeathCounter[rI][rJ]++;
        if ((Cells[rI][rJ] == 2) || (Cells[rI][rJ] == 3))
          // TACDeathCounter[rI][rJ]++;
          Cells[rI][rJ] = 4; // make necrotic
        stemBirthCounter[rI][rJ] = 0;
        deaths++;
      }

      // healthy division
      else if ((cell == 0) && (vacantSites(rI, rJ) > 0)) {
        if (proliferation[cell]
            >= random.nextFloat()) { // If tossing the coin we are to proliferate...
          // if (Oxygen[rI][rJ]>prolifThreshold) { // AND the oxygen concentration is enough for
          // division..
          // consumption[rI][rJ]=consumptionDivision[Cells[rI][rJ]];
          int[] daughter = findEmptySite(rI, rJ);
          births++;
          Cells[daughter[0]][daughter[1]] = 0;
          carriedmutation[daughter[0]][daughter[1]] = 0;
          carriedGenome[daughter[0]][daughter[1]] =
              initGenome; // resetting space to healthy cell with no mutations
        }
      }
      // }

      // cancer division
      else if ((vacantSitesCancer(rI, rJ) > 0) && (cell > 0))
        if (proliferation[cell]
            >= random.nextFloat()) { // If tossing the coin we are to proliferate...
          if ((cell == 1)
              || ((cell == 2)
                  && (Age[rI][rJ] < maxProDivisions))) { // AND the cell is stem or TAC ...
            // if (Oxygen[rI][rJ]>prolifThreshold) { // AND the oxygen concentration is enough for
            // division..
            // consumption[rI][rJ]=consumptionDivision[Cells[rI][rJ]];
            int[] daughter = findEmptySiteCancer(rI, rJ); // and there is space (for cancer)
            //    if ((daughter[0]==0) || (daughter[0]==size) ||
            // (daughter[1]==0)||(daughter[1]==size)) {simulationFinished=true;} // stop sim if a
            // cell hits the edge
            births++;
            if (cell == 1) { // stem cell
              stemBirthsTotal[rI][rJ]++;
              stemBirthCounter[rI][rJ]++;
              if (asymmetricRatio > random.nextFloat()) {
                Cells[daughter[0]][daughter[1]] = 1; // placing the stem daughter
                stemBirthCounter[daughter[0]][daughter[1]] =
                    stemBirthCounter[rI][rJ]; // update stem birth counter
                carriedmutation[daughter[0]][daughter[1]] =
                    carriedmutation[rI][rJ]; // inherit mutational status of parent
                carriedGenome[daughter[0]][daughter[1]] =
                    carriedGenome[rI][rJ]; // inherit mutational status of parent
                if (mutfreq > random.nextFloat()) { // small chance of mutation
                  mutationNum++; // advance mutation number
                  System.out.println(
                      +carriedmutation[rI][rJ]
                          + ", "
                          + mutationNum
                          + ", "
                          + stem_cells_this_TS
                          + ", "
                          + non_stem_cells_this_TS
                          + ", "
                          + timestep); // print (parent,child) pair
                  // tree.put(carriedmutation[rI][rJ], mutationNum);
                  // timeTree.put(mutationNum, timestep); // hash table stuff
                  if (0.5 > random.nextFloat()) {
                    carriedmutation[daughter[0]][daughter[1]] = mutationNum;
                    genomeToMod = new StringBuilder(carriedGenome[daughter[0]][daughter[1]]);
                    genomeToMod.setCharAt(
                        mutationNum - 1, '1'); //  daughter carries new carriedGenome
                    carriedGenome[daughter[0]][daughter[1]] = genomeToMod;
                    // System.out.println (carriedGenome[rI][rJ]);
                    // System.out.println (carriedGenome[daughter[0]][daughter[1]]);
                  } // 50:50 mutate new position daughter
                  else {
                    carriedmutation[rI][rJ] = mutationNum; // else mutate original position daughter
                    genomeToMod = new StringBuilder(carriedGenome[rI][rJ]);
                    // genomeToMod = carriedGenome[rI][rJ];
                    carriedGenome[rI][rJ] = genomeToMod;
                    genomeToMod.setCharAt(
                        mutationNum - 1, '1'); //  original carries new carriedGenome
                    // System.out.println (carriedGenome[rI][rJ]);
                    // System.out.println (carriedGenome[daughter[0]][daughter[1]]);
                  }
                }
              } else {
                Cells[daughter[0]][daughter[1]] = 2; // asymmetric division, daughter is TAC
                stemBirthCounter[daughter[0]][daughter[1]] = 0; // reset stem counter
                carriedmutation[daughter[0]][daughter[1]] =
                    carriedmutation[rI][rJ]; // TAC carries parental mutation flag
                carriedGenome[daughter[0]][daughter[1]] = carriedGenome[rI][rJ];
              } // TAC carries parental genome
              // } // redundant from above

              // else { // Only if there's hypoxia induced change of symmetric division ratio
              //    float newASR=asymmetricRatio+0*(hypoxia-Oxygen[rI][rJ]);
              // currently OFF by way of 0 the ^^ multiplier.
              //    if (newASR>random.nextFloat())
              // {Cells[daughter[0]][daughter[1]]=1;stemBirthCounter[daughter[0]][daughter[1]]=stemBirthCounter[rI][rJ];}
              //    else
              // {Cells[daughter[0]][daughter[1]]=2;stemBirthCounter[daughter[0]][daughter[1]]=0;}
              // // Otherwise differentiate
              // }
            } else if (cell == 2) { // non-stem division
              // TACBirthCounter[rI][rJ]++;
              if (Age[rI][rJ] < maxProDivisions - 1) {
                Cells[daughter[0]][daughter[1]] = 2;
                Age[rI][rJ]++;
                Age[daughter[0]][daughter[1]] = Age[rI][rJ];
                carriedmutation[daughter[0]][daughter[1]] =
                    carriedmutation[rI][rJ]; // TAC carries parental mutation flag
                carriedGenome[daughter[0]][daughter[1]] =
                    carriedGenome[rI][rJ]; // TAC carries parental genome
              } else {
                Cells[daughter[0]][daughter[1]] = 3;
                Cells[rI][rJ] = 3;
                Age[rI][rJ] = 0;
                Age[daughter[0]][daughter[1]] = Age[rI][rJ];
                carriedmutation[daughter[0]][daughter[1]] =
                    carriedmutation[rI][rJ]; // TAC carries parental mutation flag
                carriedGenome[daughter[0]][daughter[1]] =
                    carriedGenome[rI][rJ]; // TAC carries parental genome
              }
            }
          }
        } else if (pMotility > random.nextFloat()) { // Migration = not in use
          int[] daughter = findEmptySite(rI, rJ);
          Cells[daughter[0]][daughter[1]] = cell;
          Cells[rI][rJ] = 0;
          Age[daughter[0]][daughter[1]] = Age[rI][rJ];
          Age[rI][rJ] = 0;
          System.err.println("moving " + rI + ", " + rJ);
        }
      // Aging for mature cells
      if (cell == 3) Age[rI][rJ]++;
    }
    return true;
  }