
// MazeMaker.java
// Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Vygeneruje bludiště sestávající z chodeb, místností
   a jednoho vchodu.

   Bludiště je reprezntováno mřížkou z písmen 'b',
   které představují jeho stěny.

   Bludiště se ukládá do souboru MAZE_FN nebo do souboru 
   zadaného na příkazové řádkce.

   Změny oproti souboru MazeMaker.java v adresáři /Maze:
     * šířka a výška zvýšená na 40*40 (byla 20*16)
     * náhodná záměna některých bloků za válce 'b' --> 'c'
     * označení otevřeného místa znakem 's'

   Rozdíl oproti programu MazeGen spočívá v tom, že bludiště 
   zde mají místnosti, velké oblasti nepruchodných bloků a 
   prostory, do kterých se nelze dostat.
*/

import java.awt.*;
import java.io.*;


public class MazeMaker
{
  private final static int WIDTH = 21;   // rozměry mřížky
  private final static int HEIGHT = 21;
    // mělo by být násobkem 4 + 1, aby bylo dostatek místa pro místnosti
    // jak napříč, tak dolů

  private final static int MAX_POINTS = 100;
  private final static int ROOM_LEN = 3;  
  private final static int POINT_SEP = 4;

  private final static double CYLINDER_PERCENT = 0.2;
     // podíl bloků, které se mají změnit na válce

  private final static String MAZE_FN = "maze.txt";


  private Point points[];
  private int numPoints;
  private boolean connected[];

  private char grid[][];
    // použije 'x' pro výplň chodeb či místností
    // použije 'b' pro stěny chodeb/místností

  private Point entrance;   // na horním okraji mřížky


  public MazeMaker()
  {
    points = new Point[MAX_POINTS];
    connected = new boolean[MAX_POINTS];
    grid = new char[HEIGHT][WIDTH];

    initPoints();
    connectPoints();
    makeRooms();
    buildWalls();
    makeCylinders();
    placeStart();
  }  // konec konstruktoru: of MazeMaker()


  private void initPoints()
  // vytvoří vnitří body a vstupní bod mřížky
  {
    numPoints = 0;

    for (int i=0; i < MAX_POINTS; i++)
       connected[i] = false;

    // set grid to be empty
    for(int j=0; j < HEIGHT; j++)
      for(int i=0; i < WIDTH; i++)
        grid[j][i] = ' ';


    // pravidelný vzor bodů
    for(int y=2; y < HEIGHT; y=y+POINT_SEP)
      for(int x=2; x < WIDTH; x=x+POINT_SEP)
        if ((numPoints < MAX_POINTS) && !onAnEdge(x,y)) {
          points[numPoints] = new Point(x, y);
          grid[y][x] = 'x';
          numPoints++;
        }

    entrance = new Point( (int)(WIDTH/2), 0);   // střed horního okraje
    grid[0][(int)(WIDTH/2)] = ' ';
  }  // konec metody: initPoints()


  private boolean onAnEdge(int x, int y)
  {
    if ((x == 0) || (x == WIDTH-1) ||
        (y == 0) || (y == HEIGHT-1))
      return true;
    return false;
  }


  private void connectPoints()
  // mezi body vykreslí tvary písmene L
  {
    Point pt1, pt2;
    int pointsLeft = (int)(0.7 * numPoints);   // propoj méně bodů
    while (pointsLeft >= 2) {
       pt1 = selectPoint();
       pt2 = selectPoint();
       // System.out.print("" + pt1 + ", " + pt2);
       connectPair(pt1.x, pt1.y, pt2.x, pt2.y);
       pointsLeft = pointsLeft - 2;
    }

    // propoj vchod s (nejvýše) dvěma vnitřními body
    pt1 = points[ randomPoint() ];
    connectEntrance( pt1.x, pt1.y);

    pt2 = points[ randomPoint() ];
    connectEntrance( pt2.x, pt2.y);

  }  // konec metody: connectPoints()



  private Point selectPoint()
  // Zvolí bod, který dosud není s ničím propojen
  {
    int posn = randomPoint();
    while (connected[posn])    // neefektivní cyklus
       posn = randomPoint();
    connected[posn] = true;
    return points[ posn ];
  }


  private void connectPair(int x1, int y1, int x2, int y2)
  // propojí body (x1,y1) a (x2,y2) přes tvar L
  {
    if (Math.random() < 0.5) {  // náhodně zvol tvar L
      makeYLine( x1, y1, y2);
      makeXLine( y2, x1, x2);
    }
    else {
      makeXLine( y1, x1, x2);
      makeYLine( x2, y1, y2);
    }
  }  // konec metody: connectPair()


  private void connectEntrance(int x, int y)
  // Propojí vchod (x,y) pomocí tvaru L.    
  // Vchod by měl být na horním okraji.
  {
    makeYLine( entrance.x, entrance.y, y);
    makeXLine( y, entrance.x, x);
  }


  private void makeYLine(int x, int y1, int y2)
  // v mřížce vytvoří úsečku od (x,y1) do (x,y2) 
  {
    // System.out.println("makeYLine " + x + ", " + y1 + ", " + y2);
    int start = (y1 < y2) ? y1 : y2;
    int end = (y1 < y2) ? y2 : y1;
    for(int i = start; i <= end; i++)
      grid[i][x] = 'x';
  }  // konec metody: makeYLine()


  private void makeXLine(int y, int x1, int x2)
  // // v mřížce vytvoří úsečku od (x1,y) do (x2,y)
  {
    // System.out.println("makeXLine " + y + ", " + x1 + ", " + x2);
    int start = (x1 < x2) ? x1 : x2;
    int end = (x1 < x2) ? x2 : x1;
    for(int i = start; i <= end; i++)
      grid[y][i] = 'x';
  }  // konec metody: makeXLine()



  private void makeRooms()
  /* Náhodně zvolí numPoints/2 interních bodů a
     převede je na místnosti (čtverce vyplněné písmeny 'x').
  */
  { int numRooms = 0;
    Point pt;
    while (numRooms < numPoints/2) {
      pt = points[ randomPoint() ];    // nekontroluj duplicitu
      if (!nearEdge(pt.x, pt.y)) {
        makeRoom(pt.x, pt.y);
        numRooms++;
      }
    }
  }  // konec metody: makeRooms()


  private boolean nearEdge(int x, int y)
  // je bod (x,y) + nebo - ROOM_LEN/2 na/za okrajem?
  {
    int len = (int)(ROOM_LEN/2);
    if ((x-len <= 0) || (x+len >= WIDTH-1)) 
      return true;
    if ((y-len <= 0) || (y+len >= HEIGHT-1)) 
      return true;
    return false;
  }  // konec metody: nearEdge()


  private void makeRoom(int x, int y)
  /* Vytvoří box vyplnění znaky 'x' kolem středu (x,y)
     se stěnami velikosti ROOM_LEN */
  {
    int len = (int)(ROOM_LEN/2);
    for(int i = x-len; i <= x+len; i++)
      for(int j = y-len; j <= y+len; j++)
        grid[j][i] = 'x';
  } // konec metody: makeRoom()


  private void buildWalls()
  {
    for(int y=0; y<HEIGHT; y++)
      for(int x=0; x < WIDTH; x++) {
        if (grid[y][x] == ' ')   // odbnouchni 'b'-čka
          grid[y][x] = 'b';
        else if (grid[y][x] == 'x')   // odeber 'x'-ka
          grid[y][x] = ' ';
      }
  }


  private void makeCylinders()
  // převede asi CYLINDER_PERCENT část bloků na válce
  {
    for(int y=0; y<HEIGHT; y++)
      for(int x=0; x < WIDTH; x++) {
        if ((grid[y][x] == 'b') && 
            (Math.random() <= CYLINDER_PERCENT))
          grid[y][x] = 'c';
      }
  }


  private void placeStart()
  // umístí 's' do rozlehlého otevřeného prostoru ve spodní polovině bludiště
  {
    boolean placed = false;
  search:
    for(int y=HEIGHT/2; y<HEIGHT; y++) {
      for(int x=0; x < WIDTH; x++) {
        if ((grid[y][x] == ' ' ) && openSpace(y,x)) {
          grid[y][x] = 's';
          placed = true;
          break search;
        }
      }
    }
    if (!placed)
      grid[entrance.y][entrance.x] = 's';
  } // konec metody: placeStart()
          
  
 private boolean openSpace(int y, int x)
 {
   if ((y-1 < 0) || (y+1 >= HEIGHT) || (x-1 < 0) || (x+1 >= WIDTH))
     return false;
   if ((grid[y-1][x-1] == ' ') && (grid[y-1][x] == ' ') && (grid[y-1][x+1] == ' ') &&
       (grid[y][x-1] == ' ') && (grid[y][x] == ' ') && (grid[y][x+1] == ' ') &&
       (grid[y+1][x-1] == ' ') && (grid[y+1][x] == ' ') && (grid[y+1][x+1] == ' '))
     return true;
   return false;
 }
   

  public void print()
  {
    System.out.println("-----");
    for(int y=0; y<HEIGHT; y++) {
      for(int x=0; x<WIDTH; x++)
        System.out.print( grid[y][x] );
      System.out.println("|");
    }
    System.out.println("-----");
  }


  private int randomX()
  {  return (int)(WIDTH * Math.random()); }

  private int randomY()
  {  return (int)(HEIGHT * Math.random()); }

  private int randomPoint()
  {  return (int)(numPoints * Math.random()); }


  public void save(String fn)
  {
    try {
      PrintWriter out = new PrintWriter(new FileWriter(fn));
      for(int i=0; i < HEIGHT; i++)
        out.println(grid[i]);
      out.close();
      System.out.println("Uloženo do " + fn);
    }
    catch(IOException e)
    { System.out.println("Chyba při zápisu do " + fn); }
  }  // konec metody: save()


  // ---------------------------------

  public static void main(String args[])
  {
    MazeMaker mm = new MazeMaker();
    mm.print();

    if (args.length == 0)
      mm.save( MAZE_FN );
    else 
      mm.save( args[0] );
  }  // konec metody: main()


} // konec třídy: MazeMaker class
