mourning-ember / src / main / java / dev / llan / model / board / generation / MapInitialization.java
MapInitialization.java
Raw
package dev.llan.model.board.generation;

import dev.llan.model.Game;
import dev.llan.model.board.*;
import dev.llan.model.board.Tile.TileType;
import dev.llan.model.board.generation.Room.RoomType;
import dev.llan.model.board.generation.hallways.Hallway;
import dev.llan.model.pieces.GamePieces;
import dev.llan.model.pieces.enemies.EnemyFactory;
import dev.llan.model.pieces.inanimates.Door;
import dev.llan.model.pieces.inanimates.Inanimate;
import dev.llan.model.pieces.inanimates.TreasureChest;
import dev.llan.model.pieces.player.MainCharacter;
import dev.llan.model.pieces.enemies.Enemy;
import dev.llan.model.pieces.player.Player;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Random;

import static dev.llan.model.board.generation.Constants.*;

public class MapInitialization {

  private Player player;
  private List<Enemy> enemies;
  private List<Inanimate> inanimates;
  private final Grid grid;
  private final Game game;

  private Room start;

  public MapInitialization(Game game, GameMap map) {
    this.game = game;
    this.grid = map.getGrid();
    enemies = new ArrayList<>();
    inanimates = new ArrayList<>();
  }

  private void reset() {
    enemies = new ArrayList<>();
    inanimates = new ArrayList<>();
    player = null;
  }

  public GamePieces generateMap(Optional<Player> player) {
    System.out.println("[INIT] Generating new map");
    reset();
    int rowSize = grid.getRowSize();
    int colSize = grid.getColSize();
    Tile[][] newMap = new Tile[rowSize][colSize];

    List<Point> viablePoints = getAllViablePoints(rowSize, colSize);

    int numRooms = generateNumberOfRooms();

    List<Room> rooms = new ArrayList<>();

    for (int i = 0; i < numRooms; i++) {
      Room room = generateRandomRoom(viablePoints);
      rooms.add(room);
      if(viablePoints.isEmpty()) {
        break;
      }
    }

    placeRooms(newMap, rooms);

    selectStartRoom(rooms, grid.getRowSize(), grid.getColSize());

    List<Hallway> hallways = generateHallways(rooms);

    placeHallways(hallways, newMap);

    grid.setGrid(newMap);

    setDisplays(newMap);

    generateObjectClusters(rooms, player);

    fillRemainingTiles(newMap);

    return new GamePieces(this.player, enemies, inanimates);
  }

  private int generateNumberOfRooms() {
    return new Random().nextInt(MAX_ROOMS - MIN_ROOMS + 1) + MIN_ROOMS;
  }

  private List<Point> getAllViablePoints(int rowSize, int colSize) {
    List<Point> points = new ArrayList<>();
    for (int row = MIN_SIDE_PADDING; row < rowSize - MIN_SIDE_PADDING; row++) {
      for (int col = MIN_SIDE_PADDING; col < colSize - MIN_SIDE_PADDING; col++) {
        points.add(new Point(row, col));
      }
    }

    return points;
  }

  private Point selectRandomPoint(List<Point> viablePoints) {
    Random random = new Random();
    int idx = random.nextInt(viablePoints.size());
    return viablePoints.remove(idx);
  }

  private Room generateRandomRoom(List<Point> viablePoints) {
    Random random = new Random();
    int rowRadius = random.nextInt(MIN_ROOM_RADIUS, MAX_ROOM_RADIUS + 1);
    int colRadius = random.nextInt(MIN_ROOM_RADIUS, MAX_ROOM_RADIUS + 1);
    Point point = selectRandomPoint(viablePoints);

    Room room = new Room(rowRadius, colRadius, point);
    room.removePointsWithinRadius(viablePoints);
    return room;
  }

  private void placeRooms(Tile[][] map, List<Room> rooms) {
    for (Room room : rooms) {
      room.placeTiles(map);
    }
  }

  private void selectStartRoom(List<Room> rooms, int rowSize, int colSize) {
    Point corner = getRandomCorner(rowSize, colSize);

    double minDistance = Double.POSITIVE_INFINITY;
    Room min = rooms.getFirst();

    for(Room room : rooms) {
      double distance = Point.getDistance(corner, room.getCenter());
      if(distance < minDistance) {
        minDistance = distance;
        min = room;
      }
    }

    start = min;
    start.setType(RoomType.START);

    rooms.sort((o1, o2) -> {
      double dist1 = Point.getDistance(start.getCenter(), o1.getCenter());
      double dist2 = Point.getDistance(start.getCenter(), o2.getCenter());
      return Double.compare(dist1, dist2);
    });
  }

  private Point getRandomCorner(int rowSize, int colSize) {
    List<Point> corners = new ArrayList<>();
    corners.add(new Point(0, 0));
    corners.add(new Point(0, colSize));
    corners.add(new Point(rowSize, 0));
    corners.add(new Point(rowSize, colSize));

    int index = new Random().nextInt(4);
    return corners.get(index);
  }

  private List<Hallway> generateHallways(List<Room> rooms) {
    List<Room> toAdd = new ArrayList<>(rooms);
    List<Room> added = new ArrayList<>();
    added.add(toAdd.removeFirst());

    List<Hallway> hallways = new ArrayList<>();

    while(!toAdd.isEmpty()) {
      if(toAdd.size() == 1) {
        toAdd.getFirst().setType(RoomType.END);
      }

      RoomConnection connection = RoomConnection.getMinimalDistance(added, toAdd);
      hallways.add(new Hallway(connection.from(), connection.to()));
      toAdd.remove(connection.to());
      added.add(connection.to());
    }

    return hallways;
  }

  private void placeHallways(List<Hallway> hallways, Tile[][] map) {
    for(Hallway hallway : hallways) {
      hallway.placeTiles(map);
    }
  }

  private void setDisplays(Tile[][] map) {
    for(int row = 0; row < grid.getRowSize(); row++) {
      for(int col = 0; col < grid.getColSize(); col++) {
        if(map[row][col] == null) {
          continue;
        }
        DisplayType type = DisplayHelper.getDisplayType(row, col, map);
        map[row][col].setDisplayType(type);
      }
    }
  }

  private void generateObjectClusters(List<Room> rooms, Optional<Player> character) {
    if(character.isPresent()) {
      player = character.get();
      player.setPosition(start.getCenter());
    } else {
      player = new MainCharacter(start.getCenter(), grid);
    }
    grid.tileAt(start.getCenter()).addEntity(player);

    for(Room room : rooms) {
      if (room.getType() == RoomType.ENEMY) {
        TreasureChest chest = new TreasureChest(game, room.getCenter(), player, grid);
        grid.tileAt(room.getCenter()).addInanimate(chest);
        inanimates.add(chest);

        List<Enemy> enemies = EnemyFactory.generateCluster(room.getCenter(), game.getFloor(), grid);

        this.enemies.addAll(enemies);
      } else if (room.getType() == RoomType.END){

        Door door = new Door(room.getCenter(), game);
        grid.tileAt(room.getCenter()).addInanimate(door);

        inanimates.add(door);
      }
    }
  }

  private void fillRemainingTiles(Tile[][] newMap) {
    for(int row = 0; row < grid.getRowSize(); row++) {
      for(int col = 0; col < grid.getColSize(); col++) {
        if(newMap[row][col] == null) {
          newMap[row][col] = new Tile(new Point(row, col), TileType.UNMOVABLE);
          newMap[row][col].setDisplayType(DisplayType.VOID);
        }
      }
    }
  }
}