Ver código fonte

generation of paths and ports, swamp / water border overlays, improved level difficulty scaling

Kajetan Johannes Hammerle 5 anos atrás
pai
commit
8954ffeb1e

BIN
resources/tiles.png


BIN
resources/tiles.xcf


+ 0 - 2
src/pathgame/PathGame.java

@@ -2,7 +2,6 @@ package pathgame;
 
 import me.hammerle.snuviengine.api.IGame;
 import me.hammerle.snuviengine.api.Renderer;
-import pathgame.algorithm.TravellingSalesAlg;
 import pathgame.gameplay.Gamestate;
 import pathgame.gameplay.Gamestates;
 import pathgame.gameplay.Keys;
@@ -36,7 +35,6 @@ public class PathGame implements IGame
         if(Keys.TEST_KEY.getTime() == 1)
         {
             level.nextLevel();
-            TravellingSalesAlg.calcSalesPathLen(level.getMap());
         }
     }
 

+ 9 - 9
src/pathgame/gameplay/Camera.java

@@ -19,17 +19,17 @@ public class Camera
         lastCamOffsetY = camOffsetY;
         lastScale = scale;
 
-        if(Keys.ZOOM_IN_KEY.isDown())
-        {
-            scale *= 1.1f;
-        }
-        else if(Keys.ZOOM_OUT_KEY.isDown())
-        {
-            scale /= 1.1f;
-        }
-
         if(!level.getPlayer().isMoving() && gamestate.is(Gamestates.GAMEPLAY))
         {
+            if(Keys.ZOOM_IN_KEY.isDown())
+            {
+                scale *= 1.1f;
+            }
+            else if(Keys.ZOOM_OUT_KEY.isDown())
+            {
+                scale /= 1.1f;
+            }
+            
             if(Keys.CAM_UP_KEY.isDown())
             {
                 camOffsetY += CAM_SPEED;

+ 11 - 3
src/pathgame/gameplay/Level.java

@@ -1,5 +1,6 @@
 package pathgame.gameplay;
 
+import pathgame.algorithm.TravellingSalesAlg;
 import pathgame.tilemap.TileMap;
 import pathgame.tilemap.TileMapGenerator;
 
@@ -42,10 +43,17 @@ public final class Level
 
     public void reset()
     {
-        player.reset();
-        map = TileMapGenerator.getMap(5 + 5 * level, 5 + 5 * level, level, 2 + level);
-        player.setEnergySupply(100); // ToDo: insert value of algorithm
+        int levelW = Math.round(16.0f + (level * 16.0f / 9.0f));
+        int levelH = Math.round(9.0f + (level * 16.0f / 16.0f));
+        int towns = Math.round(2.0f + level * 0.5f);
+        
+        map = TileMapGenerator.getMap(levelW, levelH, level, towns);
+        player.reset(map.getHomeX(), map.getHomeY());
         player.setObjectivesAmount(map.getNumberOfTowns());
+        
+        //TravellingSalesAlg.calcSalesPathLen(map);
+        
+        player.setEnergySupply(100); // ToDo: insert value of algorithm
     }
 
     public TileMap getMap()

+ 7 - 7
src/pathgame/gameplay/Player.java

@@ -246,12 +246,12 @@ public class Player
         return energyUsed >= energySupply;
     }
 
-    public void reset(int energySupply, int objectivesAmount, PlayerAbilities abilities)
+    public void reset(int sx, int sy, int energySupply, int objectivesAmount, PlayerAbilities abilities)
     {
-        lastX = 0;
-        lastY = 0;
-        x = 0;
-        y = 0;
+        lastX = sx;
+        lastY = sy;
+        x = sx;
+        y = sy;
 
         velX = 0;
         velY = 0;
@@ -267,9 +267,9 @@ public class Player
         this.abilities = abilities;
     }
 
-    public void reset()
+    public void reset(int sx, int sy)
     {
-        reset(1000, 10, PlayerAbilities.NORMAL);
+        reset(sx, sy, 1000, 10, PlayerAbilities.NORMAL);
     }
 
     public boolean isMoving()

+ 157 - 25
src/pathgame/rendering/TileMapRenderer.java

@@ -3,9 +3,12 @@ package pathgame.rendering;
 import me.hammerle.snuviengine.api.Renderer;
 import me.hammerle.snuviengine.api.Texture;
 import me.hammerle.snuviengine.api.TextureRenderer;
+import pathgame.tilemap.Tile;
 import pathgame.tilemap.TileMap;
+import pathgame.tilemap.TileRenderType;
 
-/** A renderer for tile maps.
+/**
+ * A renderer for tile maps.
  *
  * @author kajetan
  */
@@ -14,19 +17,23 @@ public class TileMapRenderer
     // prevents rendering artifacts especially on different zoom levels
     private final static float ERROR = 1.0f / 512.0F;
     private final static float T_ERROR = 1.0f / 4096.0F;
-    
+
     private final Texture tileTexture = new Texture("resources/tiles.png");
     private final TextureRenderer textureRenderer = new TextureRenderer(20 * 20 * 2); // default to 20x20 map
+    private final TextureRenderer swampWaterOverlayRenderer = new TextureRenderer(20 * 20 * 2); // default to 20x20 map
+    private final TextureRenderer grassOverlayRenderer = new TextureRenderer(20 * 20 * 2); // default to 20x20 map
     private float scale = 1.0f;
-    
-    /** Creates a new tile map renderer.
+
+    /**
+     * Creates a new tile map renderer.
      *
      */
     public TileMapRenderer()
     {
     }
 
-    /** Sets the scale of the map.
+    /**
+     * Sets the scale of the map.
      *
      * @param scale the scale of the map
      */
@@ -35,7 +42,8 @@ public class TileMapRenderer
         this.scale = scale;
     }
 
-    /** Returns the scale of the map.
+    /**
+     * Returns the scale of the map.
      *
      * @return the scale of the map
      */
@@ -43,8 +51,9 @@ public class TileMapRenderer
     {
         return scale;
     }
-    
-    /** Returns the scaled render width of a map.
+
+    /**
+     * Returns the scaled render width of a map.
      *
      * @param map a map
      * @return the scaled render width of a map
@@ -53,8 +62,9 @@ public class TileMapRenderer
     {
         return map.getWidth() * TileRenderer.TILE_SIZE * scale;
     }
-    
-    /** Returns the scaled render height of a map.
+
+    /**
+     * Returns the scaled render height of a map.
      *
      * @param map a map
      * @return the scaled render height of a map
@@ -63,38 +73,158 @@ public class TileMapRenderer
     {
         return map.getHeight() * TileRenderer.TILE_SIZE * scale;
     }
-    
+
+    private void addTile(TextureRenderer tr, int x, int y, float tMinX, float tMinY, float tMaxX, float tMaxY)
+    {
+        tr.addRectangle(
+                x * TileRenderer.TILE_SIZE - ERROR, y * TileRenderer.TILE_SIZE - ERROR,
+                (x + 1) * TileRenderer.TILE_SIZE + ERROR, (y + 1) * TileRenderer.TILE_SIZE + ERROR,
+                tMinX + T_ERROR, tMinY + T_ERROR,
+                tMaxX - T_ERROR, tMaxY - T_ERROR);
+    }
+
+    private boolean isInRange(TileMap map, int x, int y)
+    {
+        return x >= 0 && y >= 0 && x < map.getWidth() && y < map.getHeight();
+    }
+
+    private boolean isSwamp(TileMap map, int x, int y)
+    {
+        return isInRange(map, x, y) && map.getTile(x, y).getRenderType() == TileRenderType.SWAMP;
+    }
+
+    private boolean isSwampOrWaterOrBorder(TileMap map, int x, int y)
+    {
+        if(!isInRange(map, x, y))
+        {
+            return true;
+        }
+        TileRenderType type = map.getTile(x, y).getRenderType();
+        return type == TileRenderType.SWAMP || type == TileRenderType.WATER;
+    }
+
+    private void addWaterSwampOverlay(TileMap map, int x, int y)
+    {
+        boolean n = isSwamp(map, x, y - 1);
+        boolean e = isSwamp(map, x + 1, y);
+        boolean s = isSwamp(map, x, y + 1);
+        boolean w = isSwamp(map, x - 1, y);
+
+        if(!n && !w && isSwamp(map, x - 1, y - 1)) // upper, left corner
+        {
+            addTile(swampWaterOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(!n && !e && isSwamp(map, x + 1, y - 1)) // upper, right corner
+        {
+            addTile(swampWaterOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(!s && !w && isSwamp(map, x - 1, y + 1)) // lower, left corner
+        {
+            addTile(swampWaterOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(!s && !e && isSwamp(map, x + 1, y + 1)) // lower, right corner
+        {
+            addTile(swampWaterOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+
+        int index = 0;
+        index += n ? 0 : 8;
+        index += e ? 0 : 4;
+        index += s ? 0 : 2;
+        index += w ? 0 : 1;
+        if(index == 15)
+        {
+            return;
+        }
+
+        float tMinX = index * TileRenderer.TILE_SIZE / TileTexture.TEXTURE_SIZE;
+        float tMaxX = (index + 1) * TileRenderer.TILE_SIZE / TileTexture.TEXTURE_SIZE;
+
+        addTile(swampWaterOverlayRenderer, x, y, tMinX, 0.0f, tMaxX, 0.5f);
+    }
+
+    private void addGrassOverlay(TileMap map, int x, int y)
+    {
+        boolean n = isSwampOrWaterOrBorder(map, x, y - 1);
+        boolean e = isSwampOrWaterOrBorder(map, x + 1, y);
+        boolean s = isSwampOrWaterOrBorder(map, x, y + 1);
+        boolean w = isSwampOrWaterOrBorder(map, x - 1, y);
+
+        if(n && w && !isSwampOrWaterOrBorder(map, x - 1, y - 1)) // upper, left corner
+        {
+            addTile(grassOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(n && e && !isSwampOrWaterOrBorder(map, x + 1, y - 1)) // upper, right corner
+        {
+            addTile(grassOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(s && w && !isSwampOrWaterOrBorder(map, x - 1, y + 1)) // lower, left corner
+        {
+            addTile(grassOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+        if(s && e && !isSwampOrWaterOrBorder(map, x + 1, y + 1)) // lower, right corner
+        {
+            addTile(grassOverlayRenderer, x, y, 0.0f, 0.0f, 0.5f, 0.5f);
+        }
+
+        int index = 0;
+        index += !n ? 0 : 8;
+        index += !e ? 0 : 4;
+        index += !s ? 0 : 2;
+        index += !w ? 0 : 1;
+        if(index == 15)
+        {
+            return;
+        }
+
+        float tMinX = index * TileRenderer.TILE_SIZE / TileTexture.TEXTURE_SIZE;
+        float tMaxX = (index + 1) * TileRenderer.TILE_SIZE / TileTexture.TEXTURE_SIZE;
+        addTile(grassOverlayRenderer, x, y, tMinX, 0.0f, tMaxX, 0.5f);
+    }
+
     private void updateData(TileMap map)
     {
         textureRenderer.clear();
+        swampWaterOverlayRenderer.clear();
+        grassOverlayRenderer.clear();
         for(int x = 0; x < map.getWidth(); x++)
         {
             for(int y = 0; y < map.getHeight(); y++)
             {
-                TileTexture tt = TileRenderer.getTileTexture(map, map.getTile(x, y), x, y);
+                Tile t = map.getTile(x, y);
+                TileTexture tt = TileRenderer.getTileTexture(map, t, x, y);
                 if(tt == null)
                 {
                     continue;
                 }
-                textureRenderer.addRectangle(
-                        x * TileRenderer.TILE_SIZE - ERROR, y * TileRenderer.TILE_SIZE - ERROR,
-                        (x + 1) * TileRenderer.TILE_SIZE + ERROR, (y + 1) * TileRenderer.TILE_SIZE + ERROR,
-                        tt.getMinX() + T_ERROR, tt.getMinY() + T_ERROR, 
-                        tt.getMaxX() - T_ERROR, tt.getMaxY() - T_ERROR);
+                /*if(t.getRenderType() == TileRenderType.WATER)
+                {
+                    addWaterSwampOverlay(map, x, y);
+                    addGrassOverlay(map, x, y);
+                }
+                else if(t.getRenderType() == TileRenderType.SWAMP)
+                {
+                    addGrassOverlay(map, x, y);
+                }*/
+                addTile(textureRenderer, x, y, tt.getMinX(), tt.getMinY(), tt.getMaxX(), tt.getMaxY());
             }
         }
         textureRenderer.build();
+        swampWaterOverlayRenderer.build();
+        grassOverlayRenderer.build();
     }
-    
-    /** Ticks the renderer. Used for animated tiles.
+
+    /**
+     * Ticks the renderer. Used for animated tiles.
      *
      */
     public void tick()
     {
         // tick tile animations here
     }
-    
-    /** Draws the given map at the given offset.
+
+    /**
+     * Draws the given map at the given offset.
      *
      * @param map a map
      * @param r the renderer given by the engine
@@ -107,22 +237,24 @@ public class TileMapRenderer
         r.setTextureEnabled(true);
         r.setColorEnabled(false);
         r.setMixColorEnabled(false);
-        
+
         tileTexture.bind();
-        
+
         if(forceUpdate || map.isDirty())
         {
             updateData(map);
             map.clean();
         }
-        
+
         float viewScale = r.getViewScale();
         offX = (int) (offX * viewScale) / viewScale;
         offY = (int) (offY * viewScale) / viewScale;
-        
+
         r.translateTo(offX, offY);
         r.scale(scale, scale);
         r.updateMatrix();
         textureRenderer.draw();
+        swampWaterOverlayRenderer.draw();
+        grassOverlayRenderer.draw();
     }
 }

+ 17 - 0
src/pathgame/rendering/TileRenderer.java

@@ -48,6 +48,23 @@ public class TileRenderer
         register(Tiles.TOWN_BLOCKED_3, new StaticTextureProvider(TileTexture.fromTextureId(19)));
         register(Tiles.TOWN_BLOCKED_4, new StaticTextureProvider(TileTexture.fromTextureId(20)));
         register(Tiles.PORT, new StaticTextureProvider(TileTexture.fromTextureId(15)));
+        register(Tiles.HOME_TOWN, new StaticTextureProvider(TileTexture.fromTextureId(39)));
+        
+        register(Tiles.PATH_N, new StaticTextureProvider(TileTexture.fromTextureId(24)));
+        register(Tiles.PATH_E, new StaticTextureProvider(TileTexture.fromTextureId(25)));
+        register(Tiles.PATH_S, new StaticTextureProvider(TileTexture.fromTextureId(26)));
+        register(Tiles.PATH_W, new StaticTextureProvider(TileTexture.fromTextureId(27)));
+        register(Tiles.PATH_E_W, new StaticTextureProvider(TileTexture.fromTextureId(28)));
+        register(Tiles.PATH_N_S, new StaticTextureProvider(TileTexture.fromTextureId(29)));
+        register(Tiles.PATH_S_W, new StaticTextureProvider(TileTexture.fromTextureId(30)));
+        register(Tiles.PATH_N_E, new StaticTextureProvider(TileTexture.fromTextureId(31)));
+        register(Tiles.PATH_E_S, new StaticTextureProvider(TileTexture.fromTextureId(32)));
+        register(Tiles.PATH_N_W, new StaticTextureProvider(TileTexture.fromTextureId(33)));
+        register(Tiles.PATH_N_E_W, new StaticTextureProvider(TileTexture.fromTextureId(34)));
+        register(Tiles.PATH_N_S_W, new StaticTextureProvider(TileTexture.fromTextureId(35)));
+        register(Tiles.PATH_N_E_S, new StaticTextureProvider(TileTexture.fromTextureId(36)));
+        register(Tiles.PATH_E_S_W, new StaticTextureProvider(TileTexture.fromTextureId(37)));
+        register(Tiles.PATH_N_E_S_W, new StaticTextureProvider(TileTexture.fromTextureId(38)));
     }
     
     public static TileTexture getTileTexture(TileMap map, Tile t, int x, int y)

+ 1 - 1
src/pathgame/rendering/TileTexture.java

@@ -6,7 +6,7 @@ package pathgame.rendering;
  */
 public class TileTexture
 {
-    private final static float TEXTURE_SIZE = 512;
+    public final static float TEXTURE_SIZE = 512;
     private final static float TILE_TEXTURE_PERCENT = TileRenderer.TILE_SIZE / TEXTURE_SIZE;
     private final static int TILES_PER_LINE = (int) (TEXTURE_SIZE / TileRenderer.TILE_SIZE);
     

+ 57 - 2
src/pathgame/tilemap/Tile.java

@@ -18,6 +18,9 @@ public class Tile
         private boolean canHostTown = true;
         private boolean blocksMovement = false;
         private TileType type = TileType.LAND;
+        private boolean canHostPath = false;
+        private boolean path = false;
+        private TileRenderType renderType = TileRenderType.NORMAL;
         
         private TileBuilder()
         {
@@ -64,9 +67,27 @@ public class Tile
             return this;
         }
         
+        public TileBuilder pathHost()
+        {
+            this.canHostPath = true;
+            return this;
+        }
+        
+        public TileBuilder path()
+        {
+            this.path = true;
+            return this;
+        }
+        
+        public TileBuilder setRenderType(TileRenderType type)
+        {
+            this.renderType = type;
+            return this;
+        }
+        
         public Tile build()
         {
-            return new Tile(energyCost, forestReplaceChance, speedUp, canHostTown, blocksMovement, type);
+            return new Tile(energyCost, forestReplaceChance, speedUp, canHostTown, blocksMovement, type, canHostPath, path, renderType);
         }
     }
     
@@ -106,9 +127,13 @@ public class Tile
     private final boolean canHostTown;
     private final boolean blocksMovement;
     private final TileType type;
+    private final boolean canHostPath;
+    private final boolean path;
+    private final TileRenderType renderType;
     
     protected Tile(int energyCost, float forestReplaceChance, Function<PlayerAbilities, Integer> speedUp, 
-            boolean canHostTown, boolean blocksMovement, TileType type)
+            boolean canHostTown, boolean blocksMovement, TileType type, boolean canHostPath, boolean path,
+            TileRenderType renderType)
     {
         id = addTile(this);
         this.energyCost = energyCost;
@@ -117,6 +142,9 @@ public class Tile
         this.canHostTown = canHostTown;
         this.blocksMovement = blocksMovement;
         this.type = type;
+        this.canHostPath = canHostPath;
+        this.path = path;
+        this.renderType = renderType;
     }
 
     /** Returns the id of the tile.
@@ -192,4 +220,31 @@ public class Tile
     {
         return type;
     }
+
+    /** Returns true if this tile can be replaced by a path.
+     *
+     * @return true if this tile can be replaced by a path
+     */
+    public boolean canHostPath()
+    {
+        return canHostPath;
+    }
+    
+    /** Returns true if this tile is a path.
+     *
+     * @return true if this tile is a path
+     */
+    public boolean isPath()
+    {
+        return path;
+    }
+
+    /** Returns the rendering type for overlays.
+     *
+     * @return the rendering type for overlays
+     */
+    public TileRenderType getRenderType()
+    {
+        return renderType;
+    }
 }

+ 10 - 0
src/pathgame/tilemap/TileHomeTown.java

@@ -0,0 +1,10 @@
+package pathgame.tilemap;
+
+public class TileHomeTown extends Tile
+{
+    public TileHomeTown()
+    {
+        super(1, 0.0f, (pa) -> 0, false, false, TileType.LAND, false, false, TileRenderType.NORMAL);
+    }
+    
+}

+ 32 - 0
src/pathgame/tilemap/TileMap.java

@@ -25,6 +25,9 @@ public class TileMap
     private boolean dirty = true;
     private final LinkedList<TownConverter> townConverters = new LinkedList<>();
     
+    private int homeX;
+    private int homeY;
+    
     /** Creates a new tile map of the given size.
      *
      * @param width the width of the map
@@ -144,5 +147,34 @@ public class TileMap
     {
         return towns;
     }
+    
+    /** Sets the position of the home town.
+     *
+     * @param x the x coordinate of the home town 
+     * @param y the y coordinate of the home town
+     */
+    public void setHomeTown(int x, int y)
+    {
+        homeX = x;
+        homeY = y;
+    }
+
+    /** Returns the x coordinate of the home town.
+     *
+     * @return the x coordinate of the home town
+     */
+    public int getHomeX()
+    {
+        return homeX;
+    }
+
+    /** Returns the y coordinate of the home town.
+     *
+     * @return the y coordinate of the home town
+     */
+    public int getHomeY()
+    {
+        return homeY;
+    }
 }
 

+ 268 - 0
src/pathgame/tilemap/TileMapGenerator.java

@@ -1,5 +1,6 @@
 package pathgame.tilemap;
 
+import java.util.ArrayList;
 import java.util.Random;
 
 public class TileMapGenerator
@@ -60,12 +61,16 @@ public class TileMapGenerator
             }
         }
         
+        generateHomeTown(map, r);
+        
         int forestSize = ((width + height) / 2) / 10;
         forestSize *= forestSize;
         generateForest(map, r, forestSize, 10, 2, Tiles.FOREST);
         generateForest(map, r, forestSize, 5, 2, Tiles.SWAMP, Tiles.SWAMP, Tiles.SWAMP_DECO, Tiles.SWAMP_TREE);
         
         generateTowns(map, r, towns);
+        generatePorts(map, r);
+        generatePaths(map, r);
         return map;
     }
     
@@ -175,4 +180,267 @@ public class TileMapGenerator
         }
         return true;
     }
+    
+    private static boolean isWater(TileMap map, int x, int y)
+    {
+        TileType type = map.getTile(x, y).getType();
+        return type == TileType.DEEP_WATER || type == TileType.SHALLOW_WATER;
+    }
+    
+    private static boolean isNeighbourWater(TileMap map, int x, int y)
+    {
+        return (x - 1 >= 0 && isWater(map, x - 1, y)) ||
+                (x + 1 < map.getWidth() && isWater(map, x + 1, y)) ||
+                (y - 1 >= 0 && isWater(map, x, y - 1)) ||
+                (y + 1 < map.getHeight() && isWater(map, x, y + 1));
+    }
+    
+    private static void generatePorts(TileMap map, Random r)
+    {
+        boolean[][] visited = new boolean[map.getWidth()][map.getHeight()];
+        for(int x = 0; x < map.getWidth(); x++)
+        {
+            for(int y = 0; y < map.getHeight(); y++)
+            {
+                if(!visited[x][y] && isWater(map, x, y))
+                {
+                    getLake(map, r, x, y, visited);
+                }
+                visited[x][y] = true;
+            }
+        }
+    }
+    
+    private static int waterSize;
+    private static int waterMinX;
+    private static int waterMinY;
+    private static int waterMaxX;
+    private static int waterMaxY;
+    
+    private static class Location
+    {
+        private final int x;
+        private final int y;
+        
+        public Location(int x, int y)
+        {
+            this.x = x;
+            this.y = y;
+        }
+        
+        public double getQuaredDistance(int ox, int oy)
+        {
+            return (ox - x) * (ox - x) + (oy - y) * (oy - y);
+        }
+    }
+    
+    private static double minSquaredDistance(ArrayList<Location> locs, int x, int y)
+    {
+        double min = Double.MAX_VALUE;
+        for(Location loc : locs)
+        {
+            double d = loc.getQuaredDistance(x, y);
+            if(d < min)
+            {
+                min = d;
+            }
+        }
+        return min;
+    }
+    
+    private static void getLake(TileMap map, Random r, int x, int y, boolean[][] visited)
+    {
+        waterSize = 0;
+        waterMinX = x;
+        waterMinY = y;
+        waterMaxX = x;
+        waterMaxY = y;
+        scanWaterTiles(map, x, y, visited);
+        // water outlines can be a port too
+        waterMinX = Math.max(0, waterMinX - 1);
+        waterMinY = Math.max(0, waterMinY - 1);
+        waterMaxX = Math.min(map.getWidth() - 1, waterMaxX + 1);
+        waterMaxY = Math.min(map.getHeight() - 1, waterMaxY + 1);
+        
+        ArrayList<Location> locs = new ArrayList<>();
+        
+        //System.out.println(String.format("Lake Size: %d, (%d, %d) -> (%d, %d)", 
+        //        waterSize, waterMinX, waterMinY, waterMaxX, waterMaxY));
+        int ports = waterSize / 10;
+        int diffX = waterMaxX - waterMinX + 1;
+        int diffY = waterMaxY - waterMinY + 1;
+        int failCounter = 0;
+        while(ports > 0 && failCounter < 100)
+        {
+            int rx = waterMinX + r.nextInt(diffX);
+            int ry = waterMinY + r.nextInt(diffY);
+            if(map.getTile(rx, ry).canHostTown() && isNeighbourWater(map, rx, ry) && minSquaredDistance(locs, rx, ry) > 25)
+            {
+                locs.add(new Location(rx, ry));
+                map.setTile(rx, ry, Tiles.PORT);
+                ports--;
+                failCounter = 0;
+            }
+            else
+            {
+                failCounter++;
+            }
+        }
+    }
+    
+    private static void scanWaterTiles(TileMap map, int x, int y, boolean[][] visited)
+    {
+        if(!visited[x][y] && isWater(map, x, y))
+        {
+            visited[x][y] = true;
+            waterSize++;
+            waterMinX = Math.min(x, waterMinX);
+            waterMinY = Math.min(y, waterMinY);
+            waterMaxX = Math.max(x, waterMaxX);
+            waterMaxY = Math.max(y, waterMaxY);
+            
+            if(x - 1 >= 0)
+            {
+                scanWaterTiles(map, x - 1, y, visited);
+            }
+            if(x + 1 < map.getWidth())
+            {
+                scanWaterTiles(map, x + 1, y, visited);
+            }
+            if(y - 1 >= 0)
+            {
+                scanWaterTiles(map, x, y - 1, visited);
+            }
+            if(y + 1 < map.getHeight())
+            {
+                scanWaterTiles(map, x, y + 1, visited);
+            }
+        }
+    }
+    
+    private static void generateHomeTown(TileMap map, Random r)
+    {
+        int failCounter = 0;
+        while(failCounter < 100)
+        {
+            int x = r.nextInt(map.getWidth());
+            int y = r.nextInt(map.getHeight());
+            if(map.getTile(x, y).canHostTown())
+            {
+                map.setTile(x, y, Tiles.HOME_TOWN);
+                map.setHomeTown(x, y);
+                return;
+            }
+            else
+            {
+                failCounter++;
+            }
+        }
+        map.setTile(0, 0, Tiles.HOME_TOWN);
+        map.setHomeTown(0, 0);
+    }
+    
+    private static boolean isPath(TileMap map, int x, int y)
+    {
+        return x >= 0 && y >= 0 && x < map.getWidth() && y < map.getHeight() && map.getTile(x, y).isPath();
+    }
+    
+    private static void generatePaths(TileMap map, Random r)
+    {
+        int paths = (map.getHeight() + map.getWidth()) / 6 + 2;
+        
+        // generate paths with random direction
+        for(int i = 0; i < paths; i++)
+        {
+            int x = r.nextInt(map.getWidth());
+            int y = r.nextInt(map.getHeight());
+            while(!map.getTile(x, y).canHostPath())
+            {
+                x = r.nextInt(map.getWidth());
+                y = r.nextInt(map.getHeight());
+            }
+
+            float dx = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
+            float dy = (r.nextFloat() * 0.75f + 0.25f) * (r.nextBoolean() ? -1 : 1);
+            generatePathDiretion(map, r, x, y, dx, dy);
+        }
+        // destroy path 2x2 blocks
+        destroyPathBlocks(map);
+        destroyPathBlocks(map);
+        
+        // swap paths depending on neighbours
+        for(int x = 0; x < map.getWidth(); x++)
+        {
+            for(int y = 0; y < map.getHeight(); y++)
+            {
+                if(map.getTile(x, y).isPath())
+                {
+                    map.setTile(x, y, Tiles.getPath(
+                            isPath(map, x, y - 1), isPath(map, x + 1, y), 
+                            isPath(map, x, y + 1), isPath(map, x - 1, y)));
+                }
+            }
+        }
+    }
+    
+    private static void destroyPathBlocks(TileMap map)
+    {
+        for(int x = 0; x < map.getWidth() - 1; x++)
+        {
+            for(int y = 0; y < map.getHeight() - 1; y++)
+            {
+                if(map.getTile(x, y).isPath() && map.getTile(x + 1, y).isPath() && 
+                        map.getTile(x, y + 1).isPath() && map.getTile(x + 1, y + 1).isPath())
+                {
+                    if(!isPath(map, x - 1, y) && !isPath(map, x, y - 1))
+                    {
+                        map.setTile(x, y, Tiles.GRASS);
+                        continue;
+                    }
+                    if(!isPath(map, x + 1, y - 1) && !isPath(map, x + 2, y))
+                    {
+                        map.setTile(x + 1, y, Tiles.GRASS);
+                        continue;
+                    }
+                    if(!isPath(map, x - 1, y + 1) && !isPath(map, x, y + 2))
+                    {
+                        map.setTile(x, y + 1, Tiles.GRASS);
+                        continue;
+                    }
+                    if(!isPath(map, x + 2, y + 1) && !isPath(map, x + 1, y + 2))
+                    {
+                        map.setTile(x + 1, y + 1, Tiles.GRASS);
+                    }
+                }
+            }
+        }
+    }
+    
+    private static void generatePathDiretion(TileMap map, Random r, float x, float y, float dx, float dy)
+    {
+        while(true)
+        {
+            int tileX = (int) x;
+            int tileY = (int) y;
+            
+            if(tileX < 0 || tileY < 0 || tileX >= map.getWidth() || tileY >= map.getHeight() || !map.getTile(tileX, tileY).canHostPath())
+            {
+                break;
+            }
+            
+            map.setTile(tileX, tileY, Tiles.PATH_N_E_S_W);
+            
+            while(tileX == (int) x && tileY == (int) y)
+            {
+                if(r.nextBoolean())
+                {
+                    x += dx;
+                }
+                else
+                {
+                    y += dy;
+                }
+            }
+        }
+    }
 }

+ 1 - 1
src/pathgame/tilemap/TilePort.java

@@ -6,7 +6,7 @@ public class TilePort extends Tile
 {
     public TilePort()
     {
-        super(1, 0.0f, (pa) -> 0, false, false, TileType.PORT);
+        super(1, 0.0f, (pa) -> 0, false, false, TileType.PORT, false, false, TileRenderType.NORMAL);
     }
 
     @Override

+ 6 - 0
src/pathgame/tilemap/TileRenderType.java

@@ -0,0 +1,6 @@
+package pathgame.tilemap;
+
+public enum TileRenderType
+{
+    WATER, SWAMP, NORMAL
+}

+ 1 - 1
src/pathgame/tilemap/TileTown.java

@@ -6,7 +6,7 @@ public class TileTown extends Tile
 {
     public TileTown()
     {
-        super(1, 0.0f, (pa) -> 0, false, false, TileType.LAND);
+        super(1, 0.0f, (pa) -> 0, false, false, TileType.LAND, false, false, TileRenderType.NORMAL);
     }
 
     @Override

+ 45 - 0
src/pathgame/tilemap/Tiles.java

@@ -24,6 +24,7 @@ public class Tiles
             .setEnergyCost(2)
             .setForestReplaceChance(0.0f)
             .setSpeedUp((pa) -> pa.getFasterForest())
+            .pathHost()
             .build();
     public final static Tile SWAMP = buildSwamp();
     public final static Tile SWAMP_DECO = buildSwamp();
@@ -33,6 +34,7 @@ public class Tiles
             .setForestReplaceChance(0.0f)
             .setSpeedUp((pa) -> pa.getFasterShallowWater())
             .noTown()
+            .setRenderType(TileRenderType.WATER)
             .setType(TileType.SHALLOW_WATER)
             .build();
     public final static Tile DEEP_WATER = Tile.TileBuilder.create()
@@ -40,12 +42,14 @@ public class Tiles
             .setForestReplaceChance(0.0f)
             .setSpeedUp((pa) -> pa.getFasterDeepWater())
             .noTown()
+            .setRenderType(TileRenderType.WATER)
             .setType(TileType.DEEP_WATER)
             .build();
     public final static Tile HILL = Tile.TileBuilder.create()
             .setEnergyCost(3)
             .setForestReplaceChance(0.5f)
             .setSpeedUp((pa) -> pa.getFasterHill())
+            .pathHost()
             .build();
     public final static Tile MOUNTAIN = Tile.TileBuilder.create()
             .setEnergyCost(5)
@@ -70,6 +74,7 @@ public class Tiles
     {
         return Tile.TileBuilder.create()
                 .setSpeedUp((pa) -> pa.getFasterGrass())
+                .pathHost()
                 .build();
     }
     
@@ -78,6 +83,7 @@ public class Tiles
         return Tile.TileBuilder.create()
                 .setEnergyCost(3)
                 .setForestReplaceChance(0.0f)
+                .setRenderType(TileRenderType.SWAMP)
                 .build();
     }
     
@@ -88,4 +94,43 @@ public class Tiles
                 .noTown()
                 .build();
     }
+    
+    public final static Tile HOME_TOWN = new TileHomeTown();
+    
+    public final static Tile buildPath()
+    {
+        return Tile.TileBuilder.create()
+                .setForestReplaceChance(0.0f)
+                .path()
+                .build();
+    }
+    
+    public final static Tile PATH_N_E_S_W = buildPath();
+    public final static Tile PATH_N_E_S = buildPath();
+    public final static Tile PATH_N_E_W = buildPath();
+    public final static Tile PATH_N_E = buildPath();
+    public final static Tile PATH_N_S_W = buildPath();
+    public final static Tile PATH_N_S = buildPath();
+    public final static Tile PATH_N_W = buildPath();
+    public final static Tile PATH_N = buildPath();
+    public final static Tile PATH_E_S_W = buildPath();
+    public final static Tile PATH_E_S = buildPath();
+    public final static Tile PATH_E_W = buildPath();
+    public final static Tile PATH_E = buildPath();
+    public final static Tile PATH_S_W = buildPath();
+    public final static Tile PATH_S = buildPath();
+    public final static Tile PATH_W = buildPath();
+    
+    public final static Tile[] PATH = new Tile[]
+    {
+        PATH_N_E_S_W, PATH_N_E_S, PATH_N_E_W, PATH_N_E,
+        PATH_N_S_W, PATH_N_S, PATH_N_W, PATH_N,
+        PATH_E_S_W, PATH_E_S, PATH_E_W, PATH_E,
+        PATH_S_W, PATH_S, PATH_W, GRASS
+    };
+    
+    public static Tile getPath(boolean north, boolean east, boolean south, boolean west)
+    {
+        return PATH[(north ? 0 : 8) + (east ? 0 : 4) + (south ? 0 : 2) + (west ? 0 : 1)];
+    }
 }