// // // Cavern // // // This takes on the command line the name of a properties file // to define the arguments. // import java.util.*; import java.io.*; import java.awt.*; public class Cavern extends Frame { // This says how many reserved non-negative values we have. // 0 = empty, 1 = door, 2 = hallway public static final int RESROOM = 3; public static final int DOOR = 1; public static final int HALL = 2; // This holds the configuration properties public Properties caveProps; // This holds various extracted properties, with defaults. int width = 100; // Width of cavern int height = 100; // Heigth of cavern int seed_percent = 50; // percentage of cave to seed int seed_min_live = 4; // This number will keep alive int seed_new_life = 5; // This number will spawn int seed_number = 0; // RNG seed, if not zero. int x_chunks = 0; // Where to put in firebreaks int y_chunks = 0; // Where to put in firebreaks boolean connect_smallest = false; // Try to connect smallest two caves. int smallest_room = 0; // Smallest room to allow boolean nc_refresh = true; // Display during numberCave? boolean cr_refresh = true; // Display during calc rooms? boolean ct_refresh = true; // Display during connect tunnels? boolean needs_paint = true; // Do we need to paint at the next stable point? java.util.Random random;// The random number generator int disp_gen_delay = 1; // Deciseconds between display updates int disp_dot_size = 4; // size of one dot in display Graphics g; // The graphics context to draw in String PPMoutfile; // The name of the PPM output file String POVoutfile; // The name of the POV output file int [][] theCave; // The actual data of the cave rooms int [][] theCave100; // Generation 200 of the cave, for loop checking int [][] caveRooms; // The cave with numbered rooms int [][] theHalls; // The hallways int [] roomSize; // The size of each room in theCave. public static void main(String [] argv) { Cavern current; current = new Cavern(argv); current.showFrame(argv); current.makeCave(); current.output(); System.out.println("CAVES COMPLETE!"); // while (true) { try { Thread.sleep(30000); } catch (Exception e) { // Do nothing } // } exit(0); } public static void exit(int i) { try { Thread.sleep(5000); } catch (Exception e) { // Do nothing } System.exit(i); } public Cavern(String [] argv) { super ("Cavern"); loadProps(argv); if (seed_number != 0) { random = new java.util.Random(seed_number); } else { random = new java.util.Random(); } } public void loadProps(String [] argv) { String propfilename; FileInputStream is; if (null == argv || argv.length < 1) { propfilename = "caveprop.ini"; } else { propfilename = argv[0]; } try { is = new FileInputStream(propfilename); caveProps = new Properties(); caveProps.load(is); is.close(); } catch (Exception e) { // Because Java's exception handling is screwed. System.out.println("Loading properties: " + e); exit(1); } width = (new Integer(caveProps.getProperty("width", (new Integer(width)).toString()))).intValue(); height = (new Integer(caveProps.getProperty("height", (new Integer(height)).toString()))).intValue(); x_chunks = (new Integer(caveProps.getProperty("x_chunks", (new Integer(x_chunks)).toString()))).intValue(); y_chunks = (new Integer(caveProps.getProperty("y_chunks", (new Integer(y_chunks)).toString()))).intValue(); disp_gen_delay = (new Integer(caveProps.getProperty("disp_gen_delay", (new Integer(disp_gen_delay)).toString()))).intValue(); disp_dot_size = (new Integer(caveProps.getProperty("disp_dot_size", (new Integer(disp_dot_size)).toString()))).intValue(); seed_percent = (new Integer(caveProps.getProperty("seed_percent", (new Integer(seed_percent)).toString()))).intValue(); seed_number = (new Integer(caveProps.getProperty("seed_number", (new Integer(seed_number)).toString()))).intValue(); smallest_room = (new Integer(caveProps.getProperty("smallest_room", (new Integer(smallest_room)).toString()))).intValue(); nc_refresh = (new Boolean(caveProps.getProperty("nc_refresh", (new Boolean(nc_refresh)).toString()))).booleanValue(); cr_refresh = (new Boolean(caveProps.getProperty("cr_refresh", (new Boolean(cr_refresh)).toString()))).booleanValue(); ct_refresh = (new Boolean(caveProps.getProperty("ct_refresh", (new Boolean(ct_refresh)).toString()))).booleanValue(); connect_smallest = (new Boolean(caveProps.getProperty("connect_smallest", (new Boolean(connect_smallest)).toString()))).booleanValue(); PPMoutfile = caveProps.getProperty("PPMoutfile", null); POVoutfile = caveProps.getProperty("POVoutfile", null); } // This initializes the frame public void showFrame(String [] argv) { if (argv.length > 0) { setTitle("Cavern " + argv[0]); } show(); resize(insets().left + insets().right + (width * disp_dot_size), insets().top + insets().bottom + (height * disp_dot_size)); g = getGraphics(); } /////////////////////////////////////////////////////////// // This builds the actual cave public void makeCave() { initCave(); paint(g); seedCave(); paint(g); calcRooms(); paint(g); calcTunnels(); paint(g); fixCaveRooms(); paint(g); //calcShapes(); } // This initializes theCave to all zeros public void initCave() { int x, y; theCave = new int[width][height]; theHalls = new int[width][height]; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { theCave[x][y] = 0; theHalls[x][y] = 0; } } roomSize = null; } // This puts in some ones public void seedCave() { int x, y, dink; for (x = 1; x < width-1; x++) { for (y = 1; y < height-1; y++) { dink = Math.abs(random.nextInt()) % 100; if (dink < seed_percent) { theCave[x][y] = 1; } } } if (0 < x_chunks) { for (x = x_chunks; x < width - 2; x += x_chunks) { for (y = 0; y < height; y++) { theCave[x][y] = 0; theCave[x+1][y] = 0; } } } if (0 < y_chunks) { for (y = y_chunks; y < height - 2; y += y_chunks) { for (x = 0; x < width; x++) { theCave[x][y] = 0; theCave[x][y+1] = 0; } } } } // This calculates the bumpy rooms. public void calcRooms() { int [][] prevCave; int gen = 0; int numRooms, x, y; do { prevCave = dupCave(theCave); calcGeneration(); if (gen % 100 == 0) { theCave100 = dupCave(prevCave); } if (cr_refresh || needs_paint) paint(g); System.out.println("Generation " + gen++); } while (!eqCave(prevCave, theCave) && !(theCave100 != null && eqCave(theCave100, theCave))); for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (0 != theCave[x][y]) theCave[x][y] = RESROOM; } } System.out.println("Caves stable!"); paint(g); theCave100 = null; prevCave = null; // save some memory // Now fill in the other globals, like the room sizes and such numRooms = numberCave(); paint(g); roomSize = new int[numRooms + RESROOM]; if (connect_smallest || smallest_room != 0) numRooms = numberCave(); // to fill in the roomSize array if (smallest_room != 0) { int r; for (r = RESROOM; r < numRooms + RESROOM; r++) { if (roomSize[r] < smallest_room) { for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (theCave[x][y] == r) theCave[x][y] = 0; } } } } numRooms = numberCave(); } caveRooms = dupCave(theCave); // Save before we blow it away } // This calculates one generation of modified Conway's life. public void calcGeneration() { int [][] prevGen = dupCave(theCave); int x, y, c, i, j; for (x = 1; x < width-1; x++) { for (y = 1; y < height-1; y++) { c = 0; for (i = x-1; i <= x+1; i++) { for (j = y-1; j <= y+1; j++) { c += prevGen[i][j]; // System.out.println("x="+x+" y="+y+" i="+i+" j="+j+" c="+c); } } c -= prevGen[x][y]; // subtract out yourself if (c < seed_min_live) { theCave[x][y] = 0; } else if (seed_new_life <= c) { theCave[x][y] = 1; } } } } // This calculates straight tunnels to connect caves together. // It also, as a side effect, saves the first numbering of the rooms. public void calcTunnels() { int numRooms; int room1 = 0, room2 = 0; numRooms = numberCave(); while (1 < numRooms) { Point p1, p2; System.out.println("Picking points"); if (connect_smallest) { int i; room1 = RESROOM; for (i = RESROOM; i < numRooms + RESROOM; i++) { // System.out.println("i="+i+" roomSize[i]="+roomSize[i]); if (roomSize[i] <= roomSize[room1]) { room1 = i; } } room2 = RESROOM; if (room1 == RESROOM) room2 = RESROOM+1; for (i = RESROOM; i < numRooms + RESROOM; i++) { if (roomSize[i] <= roomSize[room2] && room1 != i) { room2 = i; } } System.out.println("Picked " + room1 + " (" + roomSize[room1] +") and " + room2 + " (" + roomSize[room2] + ")"); } p1 = pickCavePoint(room1); do { p2 = pickCavePoint(room2); } while (theCave[p1.x][p1.y] == theCave[p2.x][p2.y]); connectCaves(p1, p2); if (nc_refresh || needs_paint) paint(g); numRooms = numberCave(); if (ct_refresh || needs_paint) paint(g); } } // This numbers all the rooms. public int numberCave() { int x, y; int nextroom; int cellCount; // OK, first we change everything to -1. for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (0 != theCave[x][y]) { theCave[x][y] = -1; } else { theCave[x][y] = theHalls[x][y]; } } } // Then we find a -1 and floodfill the next room number nextroom = RESROOM; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (theCave[x][y] == -1) { System.out.println("Flooding " + nextroom + " ("+x+","+y+")"); cellCount = floodRoom(x, y, nextroom); if (roomSize != null) roomSize[nextroom] = cellCount; if (nc_refresh || needs_paint) paint(g); nextroom += 1; } } } return nextroom - RESROOM; } // This flood-fills all -1 squares with the given room number, // as long as they're connected to "room" by other -1 squares, // or doors into hallways, but not along diagonals. public int floodRoom(int x, int y, int room) { Vector queue = new Vector(20, 8); Point p = new Point(x, y); int cellCount = 0; queue.addElement(p); do { int i, j; p = (Point) queue.firstElement(); queue.removeElementAt(0); theCave[x = p.x][y = p.y] = room; cellCount += 1; for (i = x-1; i <= x+1; i+=1) { for (j = y-1; j <= y+1; j+=1) { if (theCave[i][j] == -1 && (i == x || j == y)) { p = new Point(i,j); if (!queue.contains(p)) queue.addElement(p); } } } } while (!queue.isEmpty()); return cellCount; } // This picks a point in a cave public Point pickCavePoint(int room) { int x, y; do { x = Math.abs(random.nextInt()) % width; y = Math.abs(random.nextInt()) % height; } while (theCave[x][y] < 1 || (room != 0 && theCave[x][y] != room)); return new Point(x,y); } // Connects the rooms containing p1 and p1, which must be in // different caves. public void connectCaves(Point p1, Point p2) { boolean xfirst = (random.nextInt()) < 0; int r1 = theCave[p1.x][p1.y]; // OK, here we're going to walk from p1 to p2, looking for the // last point we pass thru that's in r1 and the first we get to // in r2. Then we'll draw a line of 1's between them. // This is not quite right, because a hall could be tangent to // one of the two rooms and this wouldn't notice. Point cur = new Point(p1.x, p1.y); Point last = new Point(p2.x, p2.y); Point first = new Point(p1.x, p1.y); Point next = new Point(p1.x, p1.y); Point prev = new Point(p1.x, p1.y); System.out.println("Connecting "+first+" and "+last+" by "+xfirst); while (theCave[cur.x][cur.y] == 0 || theCave[cur.x][cur.y] == r1) { last.x = cur.x ; last.y = cur.y; stepPoint(cur, p2, xfirst); if (theCave[first.x][first.y] == r1 && theCave[cur.x][cur.y] != r1) { prev.x = first.x; prev.y = first.y; first.x = cur.x; first.y = cur.y; } if (theCave[cur.x][cur.y] == r1) { prev.x = first.x; prev.y = first.y; first.x = cur.x ; first.y = cur.y; } } next.x = cur.x; next.y = cur.y; // OK. Now first and last are where we start and where we finish. cur.x = first.x; cur.y = first.y; do { theCave[cur.x][cur.y] = HALL; theHalls[cur.x][cur.y] = HALL; stepPoint(cur, last, xfirst); } while (!cur.equals(last)); if (0 == theHalls[prev.x][prev.y]) { theCave[first.x][first.y] = DOOR; theHalls[first.x][first.y] = DOOR; } else { theCave[first.x][first.y] = HALL; theHalls[first.x][first.y] = HALL; } if (0 == theHalls[next.x][next.y]) { theCave[last.x][last.y] = DOOR; theHalls[last.x][last.y] = DOOR; } else { theCave[last.x][last.y] = HALL; theHalls[last.x][last.y] = HALL; } } // Moves "cur" one cell closer to "dest", with xfirst // determining which way it moves. public void stepPoint(Point cur, Point dest, boolean xfirst) { if (xfirst) { if (cur.x != dest.x) { if (cur.x < dest.x) cur.x += 1; else cur.x -= 1; } else { // x's already equal if (cur.y < dest.y) cur.y += 1; else cur.y -= 1; } } else { // Step y first if (cur.y != dest.y) { if (cur.y < dest.y) cur.y += 1; else cur.y -= 1; } else { // y's already equal if (cur.x < dest.x) cur.x += 1; else cur.x -= 1; } } } // This puts the rooms back, and numbers the hallways with "1". public void fixCaveRooms() { int x, y; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (theCave[x][y] != 0) { theCave[x][y] = 1; } if (caveRooms[x][y] != 0) { theCave[x][y] = caveRooms[x][y]; } if (theHalls[x][y] != 0) { theCave[x][y] = theHalls[x][y]; } } } } /////////////////////////////////////////////////////////// public void output() { if (POVoutfile != null) POVoutput(POVoutfile); if (PPMoutfile != null) PPMoutput(PPMoutfile); } public void POVoutput(String fname) { } public void PPMoutput(String fname) { PrintStream os; int x, y; int curcolor; Color c; System.out.println("Outputting PPM to " + fname); try { os = new PrintStream(new FileOutputStream(fname)); os.println("P3"); os.println("# Generated by Caverns - DNew"); os.println("# Seed_number = " + seed_number + " Seed_percent = " + seed_percent); os.println(width + " " + height + " " + 255); for (y = 0; y < height; y++) { for (x = 0; x < width ; x++) { curcolor = theCave[x][y]; // The following adjustment is in "paint" too. if (color.length <= curcolor) curcolor = curcolor % (color.length-RESROOM) + RESROOM; c = color[curcolor]; os.println(c.getRed() + " " + c.getGreen() + " " + c.getBlue()); } } os.close(); } catch (Exception e) { System.out.println("Whoops - output caught " + e.toString()); } } static Color color[]; // The colors to use in drawing public void paint(Graphics g) { int x, y, curcolor; int il = insets().left; int it = insets().top; needs_paint = false; if (color == null) { color = new Color[21]; color[0] = new Color(255, 255, 255); color[1] = new Color(0, 0, 0); color[2] = new Color(0, 255, 0); color[3] = new Color(0, 0, 255); color[4] = new Color(255, 0, 0); color[5] = new Color(127,127,127); color[6] = new Color(0, 127, 0); color[7] = new Color(127, 0, 0); color[8] = new Color(0, 0, 127); color[9] = new Color(255,255,0); color[10] = new Color(0,255,255); color[11] = new Color(255,0,255); color[12] = new Color(127,127,0); color[13] = new Color(127,0,127); color[14] = new Color(0,127,127); color[15] = new Color(0,127,255); color[16] = new Color(0,255,127); color[17] = new Color(127,255,0); color[18] = new Color(255,127,0); color[19] = new Color(255,0,127); color[20] = new Color(127,0,255); } curcolor = -1; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (curcolor != theCave[x][y]) { curcolor = Math.abs(theCave[x][y]); // Needs work. Also fix PPMoutput if (color.length <= curcolor) curcolor = curcolor % (color.length-RESROOM) + RESROOM; g.setColor(color[curcolor]); } g.fillRect( il + x * disp_dot_size, it + y * disp_dot_size, disp_dot_size, disp_dot_size); } } try { Thread.sleep(disp_gen_delay * 100); } catch (Exception e) { // And, as usual, do nothing } } public void update(Graphics g) { paint(g); } public int[][] dupCave(int [][] cave) { int [][] newCave; int x, y; newCave = new int[width][height]; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { newCave[x][y] = cave[x][y]; } } return newCave; } public boolean eqCave(int [][] cave1, int [][] cave2) { int x, y; for (x = 0; x < width; x++) { for (y = 0; y < height; y++) { if (cave1[x][y] != cave2[x][y]) { return false; } } } return true; } public boolean handleEvent(Event evt) { switch (evt.id) { case Event.WINDOW_DESTROY: // TODO: Place additional clean up code here dispose(); System.exit(0); return true; case Event.MOUSE_DOWN: needs_paint = true; default: return super.handleEvent(evt); } } }