docs: def eval_fitness(self, chromosome):
          """
          Return the number of plants reached by the sprinkler.
  
          Returns a large penalty for sprinkler locations outside the map.
          """
          # convert DNA to represented sprinkler coordinates
          sx, sy = self.translator.translate_chromosome(chromosome)
  
          # check for invalid points
          penalty = 0
          if sx >= self.w:
              penalty += self.w * self.h
          if sy >= self.h:
              penalty += self.w * self.h
  
          if penalty > 0:
              self.fitness_cache[chromosome.dna] = -penalty
              return -penalty
  
          # calculate number of crop cells watered by sprinkler
          crops_watered = 0
  
          # check bounding box around sprinkler for crop cells
          # circle guaranteed to be within square with side length 2*r around sprinkler
          row_start_idx = max(0, sy - self.r)
          row_end_idx = min(sy + self.r + 1, self.h)
          col_start_idx = max(0, sx - self.r)
          col_end_idx = min(sx + self.r + 1, self.w)
  
          for y, row in enumerate(self.maplist[row_start_idx:row_end_idx], row_start_idx):
              for x, cell in enumerate(row[col_start_idx:col_end_idx], col_start_idx):
                  if cell == 'x' and sprinkler_reaches_cell(x, y, sx, sy, self.r):
                      crops_watered += 1
  
          # reduce score by 1 if sprinkler placed on a crop cell
          if self.maplist[sy][sx] == 'x':
              crops_watered -= 1
  
          self.fitness_cache[chromosome.dna] = crops_watered
          return crops_watered