Browse Source

Startet nun im Hauptmenü, überarbeiteter Code für die Weltgeneration

Jan 5 months ago
parent
commit
4e174f35bf

+ 34 - 12
src/main/java/controller/GameController.java

@@ -18,9 +18,11 @@ import model.items.ITEM_NAME;
 import model.tiles.InteractiveTileModel;
 import model.tiles.InteractiveTileType;
 import util.GAMESTATE;
+import util.WorldGenerator;
 import view.GamePanel;
 import view.sound.SoundManager;
 
+import javax.swing.*;
 import java.io.Serializable;
 import java.util.ArrayList;
 
@@ -29,7 +31,7 @@ import java.util.ArrayList;
  */
 public class GameController implements Runnable, Serializable {
 
-    public final String worldPath = "gamefiles/worlds/world_0.txt";
+    public final String worldPath = "gamefiles/worlds/world.txt";
     private GameModel gameModel;
     private boolean running = true;
     public final int fps = 60;
@@ -42,20 +44,13 @@ public class GameController implements Runnable, Serializable {
     public transient ArrayList<EntityController> entityControllers;
     public transient ArrayList<InteractiveTileController> interactiveTileControllers;
     private transient boolean gameActive;
+    private transient JFrame window;
 
     /**
      * Constructs a GameController, sets up model, view, input, inventory, entities, and interactive tiles.
      */
     public GameController() {
-        initModel();
-        initView();
-        initInputHandler();
-        initControllerLists();
-        initializeStartInventory();
-        initializeInteractiveTiles();
-        initializeEntities();
-        initResourceManager();
-        initShopController();
+        initEverything();
         view.loadMap(worldPath);
     }
 
@@ -70,6 +65,7 @@ public class GameController implements Runnable, Serializable {
      * Creates a new ResourceManager
      */
     private void initResourceManager() {
+        if(ressourceManager != null) ressourceManager.stop();
         ressourceManager = new RessourceManager(this);
         ressourceManager.start();
     }
@@ -91,7 +87,7 @@ public class GameController implements Runnable, Serializable {
     /**
      * Initializes the key handler.
      */
-    private void initInputHandler() {
+    private void initKeyHandler() {
         this.keyHandler = new KeyHandler(this);
     }
 
@@ -221,7 +217,8 @@ public class GameController implements Runnable, Serializable {
     /**
      * Starts the main game loop and resource manager thread.
      */
-    public void startGameLoop() {
+    public void startGameLoop(JFrame window) {
+        this.window = window;
         gameActive = true;
         gameThread = new Thread(this, "GameLoopThread");
         gameThread.start();
@@ -475,5 +472,30 @@ public class GameController implements Runnable, Serializable {
     public void toggleShop() {
         getView().toggleShop();
     }
+
+    public void makeNewGame() {
+        window.remove(view);
+        initEverything();
+
+        WorldGenerator.generateNewWorld(view.maxScreenCol, view.maxScreenRow);
+        view.gameState = GAMESTATE.PLAY;
+
+        view.loadMap(worldPath);
+        window.add(view);
+        window.pack();
+        view.requestFocusInWindow();
+    }
+    private void initEverything(){
+        initModel();
+        initView();
+        initKeyHandler();
+        initControllerLists();
+        initializeStartInventory();
+        initializeInteractiveTiles();
+        initializeEntities();
+        initResourceManager();
+        initShopController();
+    }
+
 }
 

+ 2 - 1
src/main/java/main/Main.java

@@ -39,6 +39,7 @@ public class Main {
         window.setTitle(Translator.translate("game.title"));
 
         window.setPreferredSize(gamePanel.getPreferredSize());
+
         window.add(gamePanel);
 
         window.pack();
@@ -46,6 +47,6 @@ public class Main {
         window.setLocationRelativeTo(null);
         window.setVisible(true);
 
-        gameController.startGameLoop();
+        gameController.startGameLoop(window);
     }
 }

+ 1 - 1
src/main/java/util/GAMESTATE.java

@@ -5,5 +5,5 @@ public enum GAMESTATE {
     PAUSED,
     INVENTORY,
     QUIT,
-    MAIN_MENU, SHOP, SETTINGS, SAVE
+    MAIN_MENU, SHOP, SETTINGS, NEW_GAME, LOAD_GAME, SAVE
 }

+ 173 - 128
src/main/java/util/WorldGenerator.java

@@ -8,179 +8,224 @@ import java.util.*;
 
 public class WorldGenerator {
     private static int worldGenNumber = 0;
+    private static final int TILE_GRASS = 0;
+    private static final int TILE_PATH = 5;
+    private static final int TILE_WATER = 2;
+    private static final int TILE_TREE = 4;
+    private static final int TILE_EARTH = 3;
+
+    public static int getWorldGenNumber(){
+        return worldGenNumber;
+    }
 
-    public static String generateNewWorld(int worldWidth, int worldHeight, int seed) {
+    public static String generateNewWorld(int width, int height, long seed) {
         Random rand = new Random(seed);
-        int[][] world = new int[worldHeight][worldWidth];
-        initializeWorld(world);
-        createMainPath(world, worldWidth, worldHeight, rand);
-        addSecondaryPaths(world, worldWidth, worldHeight, rand);
-        addWater(world, worldWidth, worldHeight, rand);
-        addTrees(world, worldWidth, worldHeight, rand);
-        addEarthPatches(world, worldWidth, worldHeight, rand);
-        return writeWorldToFile(world, worldWidth, worldHeight);
+        int[][] world = new int[height][width];
+        fill(world, TILE_GRASS);
+
+        // 1) Roads: random walks for realistic winding one-tile paths
+        generateTrails(world, width, height, rand);
+
+        // 2) Earth clusters (fewer patches)
+        generateEarthClusters(world, width, height, rand, 10, 4);
+
+        // 3) Tree clusters (denser forests)
+        addTreeClusters(world, width, height, rand, 15);
+
+        // 4) Coastline & lakes
+        generateCoastline(world, width, height, rand);
+        addInteriorLakes(world, width, height, rand, 3);
+
+        return writeToFile(world, width, height);
     }
 
-    private static void initializeWorld(int[][] world) {
-        for (int[] row : world) {
-            Arrays.fill(row, 0); // Fill with grass
-        }
+    public static String generateNewWorld(int width, int height) {
+        return generateNewWorld(width, height, System.currentTimeMillis());
     }
 
-    private static void createMainPath(int[][] world, int width, int height, Random rand) {
-        int startX = width / 2;
-        int startY = height / 2;
-        // Create a winding main path with multiple segments
-        drunkardsWalk(world, startX, startY, 300, width, height, rand, true);
+    private static void fill(int[][] world, int tile) {
+        for (int[] row : world) Arrays.fill(row, tile);
     }
 
-    private static void addSecondaryPaths(int[][] world, int width, int height, Random rand) {
-        int numBranches = 10;
-        for (int i = 0; i < numBranches; i++) {
-            // Find a random point on the existing path
-            int startX, startY;
+    // ===== TRAILS =====
+    private static void generateTrails(int[][] world, int width, int height, Random rand) {
+        int numTrails = 6; // increased number of trails
+        for (int i = 0; i < numTrails; i++) {
+            int sx = rand.nextInt(width);
+            int sy = rand.nextInt(height);
+            // end point another random but ensure sufficient distance
+            int tx, ty;
             do {
-                startX = rand.nextInt(width);
-                startY = rand.nextInt(height);
-            } while (world[startY][startX] != 5);
+                tx = rand.nextInt(width);
+                ty = rand.nextInt(height);
+            } while (Math.abs(sx - tx) + Math.abs(sy - ty) < Math.max(width, height) / 5);
+            carveTrail(world, sx, sy, tx, ty, rand);
+        }
+    }
 
-            drunkardsWalk(world, startX, startY, 50 + rand.nextInt(50), width, height, rand, false);
+    private static void carveTrail(int[][] world, int sx, int sy, int tx, int ty, Random rand) {
+        int cx = sx, cy = sy;
+        int maxSteps = (Math.abs(sx - tx) + Math.abs(sy - ty)) * 2;
+        for (int i = 0; i < maxSteps; i++) {
+            world[cy][cx] = TILE_PATH;
+            if (Math.abs(cx - tx) + Math.abs(cy - ty) < 2) break;
+            // direction toward target
+            int dx = Integer.compare(tx, cx);
+            int dy = Integer.compare(ty, cy);
+            // random offset more frequently
+            if (rand.nextDouble() < 0.4) {
+                if (rand.nextBoolean()) dx = 0;
+                else dy = 0;
+            }
+            // apply move
+            cx = Math.max(0, Math.min(world[0].length - 1, cx + dx));
+            cy = Math.max(0, Math.min(world.length - 1, cy + dy));
+            // increased chance for side branches
+            if (rand.nextDouble() < 0.1) carveShortBranch(world, cx, cy, rand, 15 + rand.nextInt(15));
         }
     }
 
-    private static void drunkardsWalk(int[][] world, int startX, int startY, int steps,
-                                      int width, int height, Random rand, boolean isMain) {
-        int x = startX;
-        int y = startY;
-        for (int i = 0; i < steps; i++) {
-            int dir = rand.nextInt(4);
-            switch (dir) {
-                case 0 -> x = Math.min(x + 1, width - 1);
-                case 1 -> x = Math.max(x - 1, 0);
-                case 2 -> y = Math.min(y + 1, height - 1);
-                case 3 -> y = Math.max(y - 1, 0);
+    private static void carveShortBranch(int[][] world, int cx, int cy, Random rand, int steps) {
+        int bx = cx, by = cy;
+        for (int b = 0; b < steps; b++) {
+            world[by][bx] = TILE_PATH;
+            int d = rand.nextInt(4);
+            switch (d) {
+                case 0 -> bx = Math.min(bx + 1, world[0].length - 1);
+                case 1 -> bx = Math.max(bx - 1, 0);
+                case 2 -> by = Math.min(by + 1, world.length - 1);
+                case 3 -> by = Math.max(by - 1, 0);
             }
-            world[y][x] = 5; // Set to path
-
-            // Randomly widen path
-            if (rand.nextDouble() < (isMain ? 0.4 : 0.3)) {
-                int widen = rand.nextInt(2) + 1;
-                for (int dx = -widen; dx <= widen; dx++) {
-                    for (int dy = -widen; dy <= widen; dy++) {
-                        if (x + dx >= 0 && x + dx < width && y + dy >= 0 && y + dy < height) {
-                            if (world[y + dy][x + dx] == 0) {
-                                world[y + dy][x + dx] = 5;
-                            }
-                        }
+        }
+    }
+
+    // ===== EARTH CLUSTERS =====
+    private static void generateEarthClusters(int[][] world, int width, int height,
+                                              Random rand, int clusters, int maxSize) {
+        for (int i = 0; i < clusters; i++) {
+            int cx = rand.nextInt(width);
+            int cy = rand.nextInt(height);
+            int size = 1 + rand.nextInt(maxSize);
+            for (int dx = -size; dx <= size; dx++) {
+                for (int dy = -size; dy <= size; dy++) {
+                    if (cx + dx < 0 || cx + dx >= width || cy + dy < 0 || cy + dy >= height) continue;
+                    if (Math.hypot(dx, dy) < size && rand.nextDouble() < 0.4) {
+                        if (world[cy + dy][cx + dx] == TILE_GRASS) world[cy + dy][cx + dx] = TILE_EARTH;
                     }
                 }
             }
         }
     }
 
-    private static void addWater(int[][] world, int width, int height, Random rand) {
-        for (int i = 0; i < 5; i++) {
-            int x = rand.nextInt(width);
-            int y = rand.nextInt(height);
-            if (world[y][x] == 0) {
-                floodFill(world, x, y, 2, 50 + rand.nextInt(50), width, height);
+    // ===== TREE CLUSTERS =====
+    private static void addTreeClusters(int[][] world, int width, int height,
+                                        Random rand, int clusters) {
+        for (int i = 0; i < clusters; i++) {
+            int tx = rand.nextInt(width);
+            int ty = rand.nextInt(height);
+            int size = 3 + rand.nextInt(3);
+            for (int dx = -size; dx <= size; dx++) {
+                for (int dy = -size; dy <= size; dy++) {
+                    if (tx + dx < 0 || tx + dx >= width || ty + dy < 0 || ty + dy >= height) continue;
+                    if (world[ty + dy][tx + dx] == TILE_GRASS && rand.nextDouble() < 0.75) world[ty + dy][tx + dx] = TILE_TREE;
+                }
             }
         }
     }
 
-    private static void floodFill(int[][] world, int x, int y, int tile, int maxSize, int width, int height) {
-        Queue<Point> queue = new LinkedList<>();
-        Set<Point> visited = new HashSet<>();
-        queue.add(new Point(x, y));
-        int filled = 0;
-
-        while (!queue.isEmpty() && filled < maxSize) {
-            Point p = queue.poll();
-            if (visited.contains(p)) continue;
-            visited.add(p);
-
-            if (world[p.y][p.x] == 0) {
-                world[p.y][p.x] = tile;
-                filled++;
-                // Add neighbors
-                if (p.x > 0) queue.add(new Point(p.x - 1, p.y));
-                if (p.x < width - 1) queue.add(new Point(p.x + 1, p.y));
-                if (p.y > 0) queue.add(new Point(p.x, p.y - 1));
-                if (p.y < height - 1) queue.add(new Point(p.x, p.y + 1));
+    // ===== COASTLINE =====
+    private static void generateCoastline(int[][] world, int width, int height, Random rand) {
+        int borderMax = Math.min(width, height) / 40;
+        double centerX = width / 2.0, centerY = height / 2.0;
+        double baseRadius = Math.min(width, height) / 2.0;
+        double maxRadius = baseRadius - borderMax * 0.75;
+        double[] radiusMap = generateRadiusMap(720, borderMax, rand);
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                double dx = x - centerX, dy = y - centerY;
+                double dist = Math.hypot(dx, dy);
+                int angle360 = (int) (Math.toDegrees(Math.atan2(dy, dx)) + 360) % 360;
+                int idx = (int) (angle360 * (radiusMap.length / 360.0));
+                double jitter = radiusMap[idx] * 0.8;
+                double noise = (rand.nextDouble() - 0.5) * borderMax * 1.5;
+                double threshold = maxRadius - jitter + noise;
+                if (dist > threshold) world[y][x] = TILE_WATER;
+                else if (rand.nextDouble() < 0.015 && dist > threshold * 0.85) world[y][x] = TILE_WATER;
             }
         }
     }
 
-    private static void addTrees(int[][] world, int width, int height, Random rand) {
-        for (int i = 0; i < 10; i++) {
-            int x = rand.nextInt(width);
-            int y = rand.nextInt(height);
-            if (world[y][x] == 0) {
-                generateTreeCluster(world, x, y, 4 + rand.nextInt(4), width, height, rand);
-            }
+    private static double[] generateRadiusMap(int resolution, int maxOffset, Random rand) {
+        double[] map = new double[resolution];
+        map[0] = map[resolution/2] = maxOffset;
+        subdivide(map, 0, resolution/2, maxOffset, rand);
+        subdivide(map, resolution/2, resolution, maxOffset, rand);
+        for (int i = 0; i < resolution; i += resolution/8) {
+            int spike = rand.nextInt(resolution);
+            map[spike] += rand.nextDouble() * maxOffset * 1.2;
         }
+        return map;
     }
 
-    private static void generateTreeCluster(int[][] world, int x, int y, int size,
-                                            int width, int height, Random rand) {
-        for (int dx = -size; dx <= size; dx++) {
-            for (int dy = -size; dy <= size; dy++) {
-                if (x + dx >= 0 && x + dx < width && y + dy >= 0 && y + dy < height) {
-                    double dist = Math.sqrt(dx*dx + dy*dy);
-                    if (dist <= size && rand.nextDouble() < 0.7 - (dist / size) * 0.4) {
-                        if (world[y + dy][x + dx] == 0) {
-                            world[y + dy][x + dx] = 4;
-                        }
-                    }
-                }
-            }
+    private static void subdivide(double[] arr, int left, int right, double offset, Random rand) {
+        if (right - left < 2) return;
+        int mid = (left + right) / 2;
+        double a = arr[left];
+        double b = arr[right % arr.length];
+        arr[mid] = (a + b) / 2 + (rand.nextDouble() * 2 - 1) * offset;
+        double newOffset = offset * 0.7;
+        subdivide(arr, left, mid, newOffset, rand);
+        subdivide(arr, mid, right, newOffset, rand);
+    }
+
+    // ===== INTERIOR LAKES =====
+    private static void addInteriorLakes(int[][] world, int width, int height, Random rand, int lakes) {
+        for (int i = 0; i < lakes; i++) {
+            int lx = rand.nextInt(width);
+            int ly = rand.nextInt(height);
+            int size = 5 + rand.nextInt(10);
+            floodFill(world, lx, ly, TILE_WATER, size);
         }
     }
 
-    private static void addEarthPatches(int[][] world, int width, int height, Random rand) {
-        for (int i = 0; i < width * height / 20; i++) {
-            int x = rand.nextInt(width);
-            int y = rand.nextInt(height);
-            if (world[y][x] == 0) {
-                world[y][x] = 3; // Earth
+    private static void floodFill(int[][] world, int x, int y, int tile, int maxSize) {
+        Queue<int[]> q = new LinkedList<>();
+        Set<Long> visited = new HashSet<>();
+        q.add(new int[]{x, y});
+        int filled = 0;
+        int w = world[0].length, h = world.length;
+        while (!q.isEmpty() && filled < maxSize) {
+            int[] p = q.poll();
+            int px = p[0], py = p[1];
+            long key = (((long) px) << 32) | py;
+            if (visited.contains(key)) continue;
+            visited.add(key);
+            if (px < 0 || px >= w || py < 0 || py >= h) continue;
+            if (world[py][px] == TILE_GRASS) {
+                world[py][px] = tile;
+                filled++;
+                q.add(new int[]{px-1, py}); q.add(new int[]{px+1, py});
+                q.add(new int[]{px, py-1}); q.add(new int[]{px, py+1});
             }
         }
     }
 
-    private static String writeWorldToFile(int[][] world, int width, int height) {
-        String directoryName = "gamefiles/worlds";
-        String fileName = directoryName + "/world_" + worldGenNumber++ + ".txt";
-        new File(directoryName).mkdirs();
-
+    // ===== OUTPUT =====
+    private static String writeToFile(int[][] world, int width, int height) {
+        String dir = "gamefiles/worlds";
+        new File(dir).mkdirs();
+        String fileName = dir + "/world.txt";
         try (PrintWriter writer = new PrintWriter(new File(fileName))) {
             for (int[] row : world) {
-                StringBuilder sb = new StringBuilder();
-                for (int i = 0; i < row.length; i++) {
-                    sb.append(i > 0 ? " " : "").append(row[i]);
+                for (int i = 0; i < width; i++) {
+                    writer.print(row[i] + (i < width-1 ? " " : ""));
                 }
-                writer.println(sb);
+                writer.println();
             }
         } catch (IOException e) {
             e.printStackTrace();
         }
         return fileName;
     }
-
-    private static class Point {
-        int x, y;
-        Point(int x, int y) { this.x = x; this.y = y; }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            Point point = (Point) o;
-            return x == point.x && y == point.y;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(x, y);
-        }
-    }
 }
+

+ 8 - 2
src/main/java/view/GamePanel.java

@@ -86,7 +86,7 @@ public class GamePanel extends JPanel{
         this.addKeyListener(controller.getKeyHandler()); // now safe
         this.setFocusable(true);
 
-        gameState = GAMESTATE.PLAY;
+        gameState = GAMESTATE.MAIN_MENU;
     }
     public void loadMap(String worldPath){
         tileManager.loadMap(worldPath);
@@ -137,7 +137,13 @@ public class GamePanel extends JPanel{
         super.paintComponent(g);
         g2 = (Graphics2D) g;
 
-
+        if(gameState == GAMESTATE.NEW_GAME){
+            gameController.makeNewGame();
+            gameState = GAMESTATE.PLAY;
+        }else if(gameState == GAMESTATE.LOAD_GAME){
+            gameController.loadGame();
+            gameState = GAMESTATE.PLAY;
+        }
 
         tileManager.draw(g2);
         entityManager.draw(g2);

+ 0 - 1
src/main/java/view/tile/TileManager.java

@@ -65,7 +65,6 @@ public class TileManager implements RenderingManager {
         }catch (Exception e){
             System.out.println("Couldn't find world path... Creating new World...");
             gamePanel.generateNewWorld();
-
         }
     }
 

+ 6 - 1
src/main/java/view/ui/menu/MainMenu.java

@@ -14,8 +14,13 @@ public class MainMenu extends AbstractMenu {
     public void draw(Graphics2D g2){
         this.g2 = g2;
         Button newGameButton = new Button(100, 100, "New Game", () -> {
-            gp.gameState = GAMESTATE.PLAY;
+            gp.gameState = GAMESTATE.NEW_GAME;
         });
         drawAndRegisterButton(newGameButton, 10, 10);
+
+        Button savedGameButton = new Button(100, 100, "Load Game", () -> {
+            gp.gameState = GAMESTATE.PLAY;
+        });
+        drawAndRegisterButton(savedGameButton, 120, 10);
     }
 }

+ 0 - 1
src/main/java/view/ui/menu/PauseMenu.java

@@ -4,7 +4,6 @@ import util.GAMESTATE;
 import util.Translator;
 import view.GamePanel;
 import view.components.Button;
-import view.ui.UI;
 
 import java.awt.*;