Example #1
0
  /**
   * Generate a char[][] dungeon with extra features given a baseDungeon that has already been
   * generated, and that already has staircases represented by greater than and less than signs.
   * Typically, you want to call generate with a TilesetType or no argument for the easiest
   * generation; this method is meant for adding features like water and doors to existing simple
   * maps. This uses '#' for walls, '.' for floors, '~' for deep water, ',' for shallow water, '^'
   * for traps, '+' for doors that provide horizontal passage, and '/' for doors that provide
   * vertical passage. Use the addDoors, addWater, addGrass, and addTraps methods of this class to
   * request these in the generated map. Also sets the fields stairsUp and stairsDown to null, and
   * expects stairs to be already handled.
   *
   * @param baseDungeon a pre-made dungeon consisting of '#' for walls and '.' for floors, with
   *     stairs already in; may be modified in-place
   * @return a char[][] dungeon
   */
  public char[][] generateRespectingStairs(char[][] baseDungeon) {
    if (!seedFixed) {
      rebuildSeed = rng.getState();
    }
    seedFixed = false;
    char[][] map = DungeonUtility.wallWrap(baseDungeon);
    DijkstraMap dijkstra = new DijkstraMap(map);
    stairsUp = null;
    stairsDown = null;

    dijkstra.clearGoals();
    ArrayList<Coord> stairs = DungeonUtility.allMatching(map, '<', '>');
    for (int j = 0; j < stairs.size(); j++) {
      dijkstra.setGoal(stairs.get(j));
    }
    dijkstra.scan(null);
    for (int i = 0; i < width; i++) {
      for (int j = 0; j < height; j++) {
        if (dijkstra.gradientMap[i][j] >= DijkstraMap.FLOOR) {
          map[i][j] = '#';
        }
      }
    }
    return innerGenerate(map);
  }
Example #2
0
 /** Make a DungeonGenerator with a LightRNG using a random seed, height 40, and width 40. */
 public DungeonGenerator() {
   rng = new StatefulRNG();
   gen = new DungeonBoneGen(rng);
   utility = new DungeonUtility(rng);
   rebuildSeed = rng.getState();
   height = 40;
   width = 40;
   fx = new EnumMap<>(FillEffect.class);
 }
Example #3
0
 /**
  * Copies all fields from copying and makes a new DungeonGenerator.
  *
  * @param copying the DungeonGenerator to copy
  */
 public DungeonGenerator(DungeonGenerator copying) {
   rng = new StatefulRNG(copying.rng.getState());
   gen = new DungeonBoneGen(rng);
   utility = new DungeonUtility(rng);
   rebuildSeed = rng.getState();
   height = copying.height;
   width = copying.width;
   fx = new EnumMap<>(copying.fx);
   dungeon = copying.dungeon;
 }
Example #4
0
  /**
   * Generate a char[][] dungeon with extra features given a baseDungeon that has already been
   * generated. Typically, you want to call generate with a TilesetType or no argument for the
   * easiest generation; this method is meant for adding features like water and doors to existing
   * simple maps. This uses '#' for walls, '.' for floors, '~' for deep water, ',' for shallow
   * water, '^' for traps, '+' for doors that provide horizontal passage, and '/' for doors that
   * provide vertical passage. Use the addDoors, addWater, addGrass, and addTraps methods of this
   * class to request these in the generated map. Also sets the fields stairsUp and stairsDown to
   * two randomly chosen, distant, connected, walkable cells. <br>
   * Special behavior here: If tab characters are present in the 2D char array, they will be
   * replaced with '.' in the final dungeon, but will also be tried first as valid staircase
   * locations (with a high distance possible to travel away from the starting staircase). If no tab
   * characters are present this will search for '.' floors to place stairs on, as normal. This
   * tab-first behavior is useful in conjunction with some methods that establish a good path in an
   * existing dungeon; an example is {@code DungeonUtility.ensurePath(dungeon, rng, '\t', '#');}
   * then passing dungeon (which that code modifies) in as baseDungeon to this method.
   *
   * @param baseDungeon a pre-made dungeon consisting of '#' for walls and '.' for floors; may be
   *     modified in-place
   * @return a char[][] dungeon
   */
  public char[][] generate(char[][] baseDungeon) {
    if (!seedFixed) {
      rebuildSeed = rng.getState();
    }
    seedFixed = false;
    char[][] map = DungeonUtility.wallWrap(baseDungeon);
    width = map.length;
    height = map[0].length;
    DijkstraMap dijkstra = new DijkstraMap(map);
    int frustrated = 0;
    do {
      dijkstra.clearGoals();
      stairsUp = utility.randomMatchingTile(map, '\t');
      if (stairsUp == null) {
        stairsUp = utility.randomFloor(map);
        if (stairsUp == null) {
          frustrated++;
          continue;
        }
      }
      dijkstra.setGoal(stairsUp);
      dijkstra.scan(null);
      frustrated++;
    } while (dijkstra.getMappedCount() < width + height && frustrated < 8);
    if (frustrated >= 8) {
      return generate();
    }
    double maxDijkstra = 0.0;
    for (int i = 0; i < width; i++) {
      for (int j = 0; j < height; j++) {
        if (dijkstra.gradientMap[i][j] >= DijkstraMap.FLOOR) {
          map[i][j] = '#';
        } else if (dijkstra.gradientMap[i][j] > maxDijkstra) {
          maxDijkstra = dijkstra.gradientMap[i][j];
        }
        if (map[i][j] == '\t') {
          map[i][j] = '.';
        }
      }
    }
    stairsDown =
        new GreasedRegion(dijkstra.gradientMap, maxDijkstra * 0.7, DijkstraMap.FLOOR)
            .singleRandom(rng);

    return innerGenerate(map);
  }
Example #5
0
  private char[][] innerGenerate(char[][] map) {
    OrderedSet<Coord> doorways;
    OrderedSet<Coord> hazards = new OrderedSet<>();
    Coord temp;
    boolean doubleDoors = false;
    int floorCount = DungeonUtility.countCells(map, '.'),
        doorFill = 0,
        waterFill = 0,
        grassFill = 0,
        trapFill = 0,
        boulderFill = 0,
        islandSpacing = 0;
    if (fx.containsKey(FillEffect.DOORS)) {
      doorFill = fx.get(FillEffect.DOORS);
      if (doorFill < 0) {
        doubleDoors = true;
        doorFill *= -1;
      }
    }
    if (fx.containsKey(FillEffect.GRASS)) {
      grassFill = fx.get(FillEffect.GRASS);
    }
    if (fx.containsKey(FillEffect.WATER)) {
      waterFill = fx.get(FillEffect.WATER);
    }
    if (fx.containsKey(FillEffect.BOULDERS)) {
      boulderFill = fx.get(FillEffect.BOULDERS) * floorCount / 100;
    }
    if (fx.containsKey(FillEffect.ISLANDS)) {
      islandSpacing = fx.get(FillEffect.ISLANDS);
    }
    if (fx.containsKey(FillEffect.TRAPS)) {
      trapFill = fx.get(FillEffect.TRAPS);
    }

    doorways = viableDoorways(doubleDoors, map);

    OrderedSet<Coord> obstacles = new OrderedSet<>(doorways.size() * doorFill / 100);
    if (doorFill > 0) {
      int total = doorways.size() * doorFill / 100;

      BigLoop:
      for (int i = 0; i < total; i++) {
        Coord entry = rng.getRandomElement(doorways);
        if (map[entry.x][entry.y] == '<' || map[entry.x][entry.y] == '>') continue;
        if (map[entry.x - 1][entry.y] != '#' && map[entry.x + 1][entry.y] != '#') {
          map[entry.x][entry.y] = '+';
        } else {
          map[entry.x][entry.y] = '/';
        }
        obstacles.add(entry);
        Coord[] adj =
            new Coord[] {
              Coord.get(entry.x + 1, entry.y),
              Coord.get(entry.x - 1, entry.y),
              Coord.get(entry.x, entry.y + 1),
              Coord.get(entry.x, entry.y - 1)
            };
        for (Coord near : adj) {
          if (doorways.contains(near)) {
            map[near.x][near.y] = '#';
            obstacles.add(near);
            doorways.remove(near);
            i++;
            doorways.remove(entry);
            continue BigLoop;
          }
        }
        doorways.remove(entry);
      }
    }
    if (boulderFill > 0.0) {
      /*
      short[] floor = pack(map, '.');
      short[] viable = retract(floor, 1, width, height, true);
      ArrayList<Coord> boulders = randomPortion(viable, boulderFill, rng);
      for (Coord boulder : boulders) {
          map[boulder.x][boulder.y] = '#';
      }
      */
      Coord[] boulders = new GreasedRegion(map, '.').retract8way(1).randomPortion(rng, boulderFill);
      Coord t;
      for (int i = 0; i < boulders.length; i++) {
        t = boulders[i];
        map[t.x][t.y] = '#';
      }
    }

    if (trapFill > 0) {
      for (int x = 1; x < map.length - 1; x++) {
        for (int y = 1; y < map[x].length - 1; y++) {
          temp = Coord.get(x, y);
          if (map[x][y] == '.' && !obstacles.contains(temp)) {
            int ctr = 0;
            if (map[x + 1][y] != '#') ++ctr;
            if (map[x - 1][y] != '#') ++ctr;
            if (map[x][y + 1] != '#') ++ctr;
            if (map[x][y - 1] != '#') ++ctr;
            if (map[x + 1][y + 1] != '#') ++ctr;
            if (map[x - 1][y + 1] != '#') ++ctr;
            if (map[x + 1][y - 1] != '#') ++ctr;
            if (map[x - 1][y - 1] != '#') ++ctr;
            if (ctr >= 5) hazards.add(Coord.get(x, y));
          }
        }
      }
    }
    short[] floors = pack(map, '.'), working;
    floorCount = count(floors);
    float waterRate = waterFill / 100.0f, grassRate = grassFill / 100.0f;
    if (waterRate + grassRate > 1.0f) {
      waterRate /= (waterFill + grassFill) / 100.0f;
      grassRate /= (waterFill + grassFill) / 100.0f;
    }
    int targetWater = Math.round(floorCount * waterRate),
        targetGrass = Math.round(floorCount * grassRate),
        sizeWaterPools = targetWater / rng.between(3, 6),
        sizeGrassPools = targetGrass / rng.between(2, 5);

    Coord[] scatter;
    int remainingWater = targetWater, remainingGrass = targetGrass;
    if (targetWater > 0) {
      scatter = fractionPacked(floors, 7);
      scatter = rng.shuffle(scatter, new Coord[scatter.length]);
      ArrayList<Coord> allWater = new ArrayList<>(targetWater);
      for (int i = 0; i < scatter.length; i++) {
        if (remainingWater > 5) // remainingWater >= targetWater * 0.02 &&
        {
          if (!queryPacked(floors, scatter[i].x, scatter[i].y)) continue;
          working =
              spill(
                  floors,
                  packOne(scatter[i]),
                  rng.between(4, Math.min(remainingWater, sizeWaterPools)),
                  rng);

          floors = differencePacked(floors, working);
          Coord[] pts = allPacked(working);
          remainingWater -= pts.length;
          Collections.addAll(allWater, pts);
        } else break;
      }

      for (Coord pt : allWater) {
        hazards.remove(pt);
        // obstacles.add(pt);
        if (map[pt.x][pt.y] != '<' && map[pt.x][pt.y] != '>') map[pt.x][pt.y] = '~';
      }
      for (Coord pt : allWater) {
        if (map[pt.x][pt.y] != '<'
            && map[pt.x][pt.y] != '>'
            && (map[pt.x - 1][pt.y] == '.'
                || map[pt.x + 1][pt.y] == '.'
                || map[pt.x][pt.y - 1] == '.'
                || map[pt.x][pt.y + 1] == '.')) map[pt.x][pt.y] = ',';
      }
    }
    if (targetGrass > 0) {
      scatter = fractionPacked(floors, 7);
      scatter = rng.shuffle(scatter, new Coord[scatter.length]);
      Coord p;
      for (int i = 0; i < scatter.length; i++) {
        if (remainingGrass > 5) // remainingGrass >= targetGrass * 0.02 &&
        {
          working =
              spill(
                  floors,
                  packOne(scatter[i]),
                  rng.between(4, Math.min(remainingGrass, sizeGrassPools)),
                  rng);
          if (working.length == 0) continue;
          floors = differencePacked(floors, working);
          Coord[] pts = allPacked(working);
          remainingGrass -= pts.length;
          for (int c = 0; c < pts.length; c++) {
            p = pts[c];
            map[p.x][p.y] = '"';
          }
        } else break;
      }
    }

    if (islandSpacing > 1 && targetWater > 0) {
      ArrayList<Coord> islands =
          PoissonDisk.sampleMap(
              map, 1f * islandSpacing, rng, '#', '.', '"', '+', '/', '^', '<', '>');
      for (Coord c : islands) {
        map[c.x][c.y] = '.';
        if (map[c.x - 1][c.y] != '#' && map[c.x - 1][c.y] != '<' && map[c.x - 1][c.y] != '>')
          map[c.x - 1][c.y] = ',';
        if (map[c.x + 1][c.y] != '#' && map[c.x + 1][c.y] != '<' && map[c.x + 1][c.y] != '>')
          map[c.x + 1][c.y] = ',';
        if (map[c.x][c.y - 1] != '#' && map[c.x][c.y - 1] != '<' && map[c.x][c.y - 1] != '>')
          map[c.x][c.y - 1] = ',';
        if (map[c.x][c.y + 1] != '#' && map[c.x][c.y + 1] != '<' && map[c.x][c.y + 1] != '>')
          map[c.x][c.y + 1] = ',';
      }
    }

    if (trapFill > 0) {
      int total = hazards.size() * trapFill / 100;

      for (int i = 0; i < total; i++) {
        Coord entry = rng.getRandomElement(hazards);
        if (map[entry.x][entry.y] == '<' || map[entry.x][entry.y] == '<') continue;
        map[entry.x][entry.y] = '^';
        hazards.remove(entry);
      }
    }

    dungeon = map;
    return map;
  }
Example #6
0
 /**
  * Generate a char[][] dungeon given a TilesetType; the comments in that class provide some
  * opinions on what each TilesetType value could be used for in a game. This uses '#' for walls,
  * '.' for floors, '~' for deep water, ',' for shallow water, '^' for traps, '+' for doors that
  * provide horizontal passage, and '/' for doors that provide vertical passage. Use the addDoors,
  * addWater, addGrass, and addTraps methods of this class to request these in the generated map.
  * Also sets the fields stairsUp and stairsDown to two randomly chosen, distant, connected,
  * walkable cells.
  *
  * @see squidpony.squidgrid.mapping.styled.TilesetType
  * @param kind a TilesetType enum value, such as TilesetType.DEFAULT_DUNGEON
  * @return a char[][] dungeon
  */
 public char[][] generate(TilesetType kind) {
   seedFixed = true;
   rebuildSeed = rng.getState();
   return generate(gen.generate(kind, width, height));
 }