
// MazeGen.java
// Nawapoom Lohajarernvanich, CoE Student ID: 4110181
// Revised by Andrew Davison, April 2005, ad@fivedots.coe.psu.ac.th

/* Vytvoří ASCII soubor uchovávající 2D-bludiště, v němž jsou 
   stěny reprezentovány pomocí 'b' (bloků) a 'c' (válců).
   Hráč začíná na pozici 's'. 
   Uprostřed horní části bludišti je jeden vchod/východ.

   Strany bludiště mají lichou délku (např. 15, 17), protože
   podél horního, dolního, levého a pravého okraje je prázdná hranice.

   Cesta skrz bludiště začíná na sudé lokaci (např. (2,2)) a algorimus
   se pro rozšíření cesty dívá jen po sudých lokacích.
   Cesta se nemůže rozšířit mimo 2 až size-3, takže bludiště si
   ponechá vnější stěny.

   Bludiště se uloží do souboru MAZE_FNM nebo do souboru zadaného 
   uživatelem.
 
   Narozdíl od aplikace MazeMaker nejsou místnosti podporovány.
*/

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


public class MazeGen
{
  // výchozí vstupní parametry
  private static final int WIDTH = 41;   // musí být liché
  private static final int HEIGHT = 41;
  private static final String MAZE_FNM = "maze.txt";

  // směry pro chodby bludiště
  private static final int UP=0;
  private static final int DOWN=1;
  private static final int LEFT=2;
  private static final int RIGHT=3;

  private static final int NUM_DIRS=4;

  // procentuální podíl bloků, které se změní na válce
  private final static double CYLINDER_PERCENT = 0.2;   // 20%

  private char maze[][];
  private int width, height;
  private String mazeFNm;
  private Point startCell;   // kde začíná hráč
  

  public MazeGen(String[] args)
  {
    processArgs(args);
    maze = new char[height][width];
    fillGrid();
    cutPath();
    makeCylinders();
    makeEntrance();
  } // konec metody: MazeGen()


  private void processArgs(String[] args)
  {
    if (args.length == 0) {
      width = WIDTH;
      height = HEIGHT;
      mazeFNm = MAZE_FNM;
    }
    else if (args.length == 2) {
      setMazeSize( args[0], args[1]);
      mazeFNm = MAZE_FNM;
    }
    else if (args.length == 3) {
      setMazeSize( args[0], args[1]);
      mazeFNm = args[2];
    }
    else {
      System.out.println("Použití: java MazeGen <width> <height> <fnm>");
      System.exit(0);
    }
    System.out.println("Šířka: " + width + "; Výška: " + height +
							" (2 přičtená pro okraje)" );
    System.out.println("Ukládám do souboru bludiště: " + mazeFNm);
  }  // konec metody: processArgs()


  private void setMazeSize(String wStr, String hStr)
  {
    try {
      width = Integer.parseInt(wStr) + 2;  // pro hranice
      height = Integer.parseInt(hStr) + 2;
      if (width%2 != 1) {
        System.out.println("Šířka musí být licha; přidávám 1");
        width++;
      }
      if (height%2 != 1) {
        System.out.println("Výška musí být licha; přidávám 1");
        height++;
      }
    }
    catch (NumberFormatException ex)
    { System.out.println("Nesprávný formát velikostních dat");  
      width = WIDTH;
      height = HEIGHT;
    }
  } // konec metody: setMazeSize()


  private void fillGrid()
  /* Počáteční mřížka má prázdnou hranici, ostatní
     je vyplněno znaky 'b'. Algoritmus tyto b-čka postupně
     odstraňuje.
  */
  { // fill the maze with 'b's
    for(int i=0; i<height; i++)
      for(int j=0; j<width; j++)
        maze[i][j] = 'b';
  
    // fill top and bottom borders with blanks
    for(int j=0; j<width; j++){
      maze[0][j] = ' ';
      maze[height-1][j] = ' ';
    }

    // fill left and right borders with blanks
    for(int i=0; i<height; i++){
      maze[i][0] = ' ';
      maze[i][width-1] = ' ';
    }
  } // konec metody: fillGrid()



  private void cutPath()
  /* Zvolí počáteční buňku na sudé pozici. Každý pohyb po tomto
     je po dvou krocích, což zajišťuje, že se vytvoří cesta, a ne
     velký blok prostoru.
  */
  { // inicializuj počáteční buňku sudou souřadnicí (x,y)
    int x = randomCoord(height);   
    int y = randomCoord(width);
    System.out.println("Počáteční pozice: (" + x + ", " + y + ")");

    startCell = new Point(x,y);
    maze[x][y] = ' ';
    createPath(startCell); // začni budovat cestu
    maze[x][y] = 's';      // označ (x,y) jako počáteční pozici
  }  // konec metody: cutPath()


  private int randomCoord(int max)
  // Vrátí sudé číslo mezi 2 a max-3, čímž vypadnou pozice
     prázdné hranice a stěn. max je liché.
  { 
    int range = max-4;   // což je liché číslo
    int randRange = (int) Math.floor((Math.random()*range)+2);
    if (randRange%2 == 1)  // liché číslo
      return randRange+1;
    return randRange;
  }  // send of randomCoord()


  private void createPath(Point cell)
  /* Od aktuální buňky zkouší náhodně navštěvovat všechny další
     dva kroky vzdálené buňky. Používá prohledávání do hloubky.

     Buňku navštíví nejvýše jednou a tak nebudou pro tvorbu cesty
     k otevřeny všechny směry.
  */
  { RandomInRange rr = new RandomInRange(NUM_DIRS);
    int dir;
    while((dir = rr.getNumber()) != -1){
      Point nextCell = nextPos(cell, dir);
      // System.out.println("nextCell: " + nextCell.toString());
      if( !beenVisited(nextCell) ){
        visit(nextCell, dir);
        createPath(nextCell);  // rekurzivní tvorba
      }
    }
  } // konec metody: createPath()



  private Point nextPos(Point cell, int dir)
  /* Vrátí pozici následující buňky. 
     Nekontroluje hranice bludiště.
     Následující pozice je vždy dva kroky vzdálená.
  */
  { int newX = cell.x;
    int newY = cell.y;
    if(dir == UP)
      newY = newY-2;
    else if(dir == DOWN)
      newY = newY+2;
    else if(dir == LEFT)
      newX = newX-2;
    else if(dir == RIGHT)
      newX = newX+2;
    else
      System.out.println("Tento směr neznám");

    if (inBounds(newX, newY))
      return new Point(newX, newY);
    else 
      return cell;   // beze změmy
  } // konec metody: nextPos()


  private boolean inBounds(int x, int y)
  { if ((x >= 2) && (x <= height-2) &&    // neuvažuj hranice
        (y >= 2) && (y <= width-2))
      return true;
    return false;
  }  // konec metody: inBounds()



  private boolean beenVisited(Point cell)
  // byla již tato buňka navštívena?
  { if(maze[cell.x][cell.y] == ' ')
      return true;
    else
      return false;
  }   //end of beenVisited()
    


  private void visit(Point cell, int dir)
  /* Vymaže aktuální buňku a tu, která leží mezí ní a předchozí.
     Tím mezi nimi dojde k vytvoření cesty.
  */
  { maze[cell.x][cell.y] = ' ';
    if(dir == UP)   // nastavena předchozí, takže vymaž
      maze[cell.x][cell.y+1] = ' ';
    else if(dir == DOWN)
      maze[cell.x][cell.y-1] = ' ';
    else if(dir == LEFT)
      maze[cell.x+1][cell.y] = ' ';
    else if(dir == RIGHT)
      maze[cell.x-1][cell.y] = ' ';
    else
      System.out.println("Neznámý směr");
  } // konec metody: visit()
   

  public void printMaze()
  { for(int i=0;i<height;i++)
      System.out.println(maze[i]);
  }  // konec metody: PrintMaze()


  public void printMazeToFile()
  {
    File f = new File(mazeFNm);
    PrintWriter pw = null;
    try {
      pw = new PrintWriter(new FileWriter(f),true);
      for(int i=0; i<height; i++)
        pw.println( new String(maze[i]) );
      System.out.println("Bludiště zapsáno do souboru " + mazeFNm);
    }
    catch(Exception e)
    {  System.out.println("Nemohu zapsat bludiště do souboru " + mazeFNm);  }

  } // konec metody: printMazeToFile()


  private void makeCylinders()
  // převeď asi tak CYLINDER_PERCENT část bloků na válce
  {
    for(int x=0; x<height; x++)
      for(int y=0; y<width; y++) {
        if ((maze[x][y] == 'b') && 
            (Math.random() <= CYLINDER_PERCENT))
          maze[x][y] = 'c';
      }
  } // konec metody: makeCylinders


  private void makeEntrance()
  /* Pokus se vytvořit vhod podél horní řádky bludiště,
     poblíž jeho středu. To se nemusí povést, je-li bludiště
     příliš malé.
  */
  { for(int y=(int)width/2; y<width-3; y++) {    // výška je lichá
      if (maze[2][y] == ' ') {     // prázdné místo uvnitř bludiště
        maze[1][y] = ' ';    // vytvoř chod
        return;
      }
    }
    System.out.println("A jé! Nebyl vytvořen žádný vchod");
  }  // konec metody: makeEntrance()


  // ---------------------- main() -----------------------

  public static void main(String args[])
  {
    MazeGen mg = new MazeGen(args);
    mg.printMaze();
    mg.printMazeToFile();
  } // konec metody: main()



} // konec třídy: MazeGen