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);
}
}
}
}
}