import java.awt.*; import java.applet.Applet; import java.net.URL; public class MapApplet extends Applet { // The scrollbar objects Scrollbar vertical; Scrollbar horizontal; // Generate a new map when this is clicked Button newMap; // We treat the map canvas as a "window" into a larger // map. These are the coordinates of the upper left // corner of the "window", within the larger map int xoffset = 0; int yoffset = 0; // The component we'll draw the map in MapCanvas mapCanvas; // An array of images for use as tiles in the map Image tiles[]; // Names for the different tile types static final int ocean = 0; static final int plain = 1; static final int desert = 2; static final int swamp = 3; static final int forest = 4; static final int hill = 5; static final int mountain = 6; // Total number of tile types. Be sure to update this if // you add or remove entries above static final int tilesTotal = 7; // The size of a tile in pixels. Tiles are always square static final int tileSize = 32; // The size of the map, in tiles int width = 32; int height = 32; // Each entry in this array is a tile id number indicating // which tile to draw at that location int map[][]; public void init() { // 1. Create the map. makeMap(); // 2. Lay out the controls. if (!makeComponents()) { return; } // 3. Get the images. if (!loadImages()) { return; } } boolean loadImages() { // Find out where the images are String baseurlString = getParameter("baseurl"); if (baseurlString == null) { System.out.println( " tag missing!"); return false; } // Now go get them tiles = new Image[tilesTotal]; int i; // getImage returns right away; the image then loads // progressively. We want to make sure all of the images // load completely before going on, so we use a handy // class known as MediaTracker. MediaTracker mt = new MediaTracker(this); for (i = 0; (i < 7); i++) { URL url; String urlString = "not yet known"; try { urlString = baseurlString + "/tile" + Integer.toString(i) + ".gif"; url = new URL(urlString); } catch (java.net.MalformedURLException e) { System.out.println(urlString + " tile missing!"); return false; } // Fetch the image tile and add it to the // media tracker. tiles[i] = getImage(url); mt.addImage(tiles[i], 0); } // Ask the media tracker to wait until all of the // images have loaded completely. try { mt.waitForAll(); } catch (InterruptedException e) { // This shouldn't happen System.out.println(e); } return true; } boolean makeComponents() { // A typical GridBagLayout as in many other applets. GridBagLayout layout = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setLayout(layout); c.fill = GridBagConstraints.BOTH; c.weightx = 1.0; c.weighty = 1.0; c.gridx = 0; c.gridy = 0; mapCanvas = new MapCanvas(this); layout.setConstraints(mapCanvas, c); add(mapCanvas); c.fill = GridBagConstraints.VERTICAL; c.weightx = 0.0; c.weighty = 1.0; c.gridx = 1; c.gridy = 0; vertical = new Scrollbar(Scrollbar.VERTICAL); layout.setConstraints(vertical, c); add(vertical); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; c.weighty = 0.0; c.gridx = 0; c.gridy = 1; horizontal = new Scrollbar(Scrollbar.HORIZONTAL); layout.setConstraints(horizontal, c); add(horizontal); c.fill = GridBagConstraints.HORIZONTAL; c.weightx = 1.0; c.weighty = 0.0; c.gridwidth = 2; c.gridx = 0; c.gridy = 2; newMap = new Button("New Map"); layout.setConstraints(newMap, c); add(newMap); // Do the layout now. This makes sure the // real controls (such as Windows scrollbars) have // been created. No, this shouldn't be necessary // before proceeding, but inconsistencies between // java implementations make it a Good Idea to do this. layout(); // Tell the scrollbars what their parameters will be. setScrollbars(); return true; } void setScrollbars() { Rectangle r = mapCanvas.bounds(); // Find the size in pixels for the entire map int wpixels = width * tileSize; int hpixels = height * tileSize; // Update the scrollbar settings. horizontal.setValues(xoffset, r.width, 0, wpixels - 1 - r.width); vertical.setValues(yoffset, r.height, 0, hpixels - 1 - r.height); // Look for Netscape for Windows, which has // a bug: setLineIncrement and setPageIncrement // are backwards. The system properties are useful // when you absolutely must find out something // about the real operating system and browser // being used. String vendor = System.getProperty("java.vendor"); // This is the version of java, not of Netscape... String version = System.getProperty("java.version"); String osname = System.getProperty("os.name"); boolean reversed = false; if (osname.startsWith("Windows")) { if (vendor.startsWith("Netscape")) { double d = 0.0; try { d = Double.valueOf(version). doubleValue(); } catch (Exception e) { } if (d < 1.1) { // The bug is present. reversed = true; } else { // Let's hope Netscape fixes this // bug when they go to Java 1.1! } } } if (reversed) { // Cope with a Netscape for Windows bug. vertical.setPageIncrement(tileSize); horizontal.setPageIncrement(tileSize); vertical.setLineIncrement(r.height); horizontal.setLineIncrement(r.width); } else { vertical.setLineIncrement(tileSize); horizontal.setLineIncrement(tileSize); vertical.setPageIncrement(r.height); horizontal.setPageIncrement(r.width); } } public boolean action(Event evt, Object obj) { // Respond to the newMap button. if (evt.target == newMap) { makeMap(); mapCanvas.repaint(); return true; } return false; } public boolean keyDown(Event evt, int key) { // Here we are concerned with scrolling // the display when the user presses // arrow keys. // Rectangle r = mapCanvas.bounds(); // Find the size in pixels for the entire map int wpixels = width * tileSize; int hpixels = height * tileSize; if (key == Event.UP) { // Subtract from the y axis offset, // then make sure we're still within bounds. yoffset -= tileSize; if (yoffset < 0) { yoffset = 0; } vertical.setValue(yoffset); mapCanvas.repaint(); return true; } else if (key == Event.DOWN) { // Add to the y axis offset, // then make sure we're still within bounds. yoffset += tileSize; if (yoffset >= hpixels - r.height) { yoffset = hpixels - r.height; } vertical.setValue(yoffset); mapCanvas.repaint(); return true; } else if (key == Event.LEFT) { // Subtract from the x axis offset, // then make sure we're still within bounds. xoffset -= tileSize; if (xoffset < 0) { xoffset = 0; } horizontal.setValue(xoffset); mapCanvas.repaint(); return true; } else if (key == Event.RIGHT) { // Add to the x axis offset, // then make sure we're still within bounds. xoffset += tileSize; if (xoffset >= wpixels - r.width) { xoffset = wpixels - r.width; } horizontal.setValue(xoffset); mapCanvas.repaint(); return true; } return false; } // handleEvent is replaced here in order to catch // events coming from the scrollbars. public boolean handleEvent(Event event) { int newXOffset; int newYOffset; if (event.target == horizontal) { switch (event.id) { case Event.SCROLL_ABSOLUTE: case Event.SCROLL_LINE_DOWN: case Event.SCROLL_LINE_UP: case Event.SCROLL_PAGE_DOWN: case Event.SCROLL_PAGE_UP: // Netscape for Windows likes to send // an awful lot of SCROLL_ABSOLUTE events // for the same position, so just check // to make sure there's a real change. newXOffset = horizontal.getValue(); // Round it off to the nearest tile. // This helps performance too by ignoring // a lot of small steps. newXOffset -= (newXOffset % tileSize); // Now see if there's a real change if (newXOffset != xoffset) { xoffset = newXOffset; mapCanvas.repaint(); } break; } } else if (event.target == vertical) { switch (event.id) { case Event.SCROLL_ABSOLUTE: case Event.SCROLL_LINE_DOWN: case Event.SCROLL_LINE_UP: case Event.SCROLL_PAGE_DOWN: case Event.SCROLL_PAGE_UP: // Netscape for Windows likes to send // an awful lot of SCROLL_ABSOLUTE events // for the same position, so just check // to make sure there's a real change. newYOffset = vertical.getValue(); // Round it off to the nearest tile. // This helps performance too by ignoring // a lot of steps. newYOffset -= (newYOffset % tileSize); // Now see if there's a real change if (newYOffset != yoffset) { yoffset = newYOffset; mapCanvas.repaint(); } break; } } return super.handleEvent(event); } void paintMap(Graphics g) { // Loop through all of the locations in // the map. Figure out where they are in // pixel terms, subtract the current // upper left corner, and paint the // appropriate image there. int x, y; // How much space is available in the canvas? int yspace = bounds().height; int xspace = bounds().width; // How many rows are visible? int visibleRows = yspace / tileSize + 1; // How many columns are visible? int visibleColumns = xspace / tileSize + 1; // What row and column are at the corner? int cornerRow = yoffset / tileSize; int cornerColumn = xoffset / tileSize; for (y = 0; (y < height); y++) { // If this row is not visible, then skip it. if ((y < (cornerRow - 1)) || (y > cornerRow + visibleRows)) { continue; } for (x = 0; (x < width); x++) { // If this column is not visible, // then skip it. if ((x < (cornerColumn - 1)) || (x > cornerColumn + visibleColumns)) { continue; } g.drawImage( tiles[map[y][x]], x * tileSize - xoffset, y * tileSize - yoffset, mapCanvas); } } } void makeMap() { // Our world is made of volcanic islands which // start out as a single very tall volcano, and // are then eroded for a while. We then decide // what terrain goes where based on the altitude // of each square. // The number of squares that are filled so far int full = 0; map = new int[height][width]; int altitude[][] = new int[height][width]; int newAltitude[][] = new int[height][width]; int x, y; // In the beginning, geek made the ocean. for (y = 0; (y < height); y++) { for (x = 0; (x < width); x++) { map[y][x] = ocean; altitude[y][x] = 0; } } // Then geek made some big volcanoes! int seedsTotal = 4; int seed; for (seed = 0; (seed < seedsTotal); seed++) { // Pick reasonable locations. x = randomBetween(2, width - 2); y = randomBetween(2, height - 2); // Plug them into the map. map[y][x] = mountain; // One hundred somethingummies tall altitude[y][x] = 100; // This square is now full full++; } // Then geek simulated many millenia of erosion, // spreading the wealth out among adjacent squares. // Sort of: we don't really lower the original squares. // // Don't not let the altitude drop to zero; // carefully increment full as each hex is // filled. At 80% full or so, or after 100 passes, // call it quits. int acceptable = (width * height) * 8 / 10; int passes = 0; while ((full < acceptable) && (passes < 100)) { for (y = 0; (y < height); y++) { for (x = 0; (x < width); x++) { // Start determining the new // altitude for this cell. newAltitude[y][x] = altitude[y][x]; if (altitude[y][x] == 0) { // Look at the neighbors // and determine how high // this square ought to be. if (fillNewCell( altitude, newAltitude, y, x)) { full++; } } } } // Set the new altitudes for the next pass. for (y = 0; (y < height); y++) { for (x = 0; (x < width); x++) { altitude[y][x] = newAltitude[y][x]; } } passes++; } } // Figure out what to do with one cell. // Returns true if the cell was given // an altitude above zero on this pass. boolean fillNewCell( int[][] altitude, int[][] newAltitude, int y, int x) { int totalAltitude = 0; int fullNeighbors = 0; int subx, suby; totalAltitude = 0; for (suby = (-1); (suby <= 1); suby++) { for (subx = (-1); (subx <= 1); subx++) { int mapy, mapx; mapy = y + suby; mapx = x + subx; if (legal(mapy, mapx)) { int a = altitude[mapy][mapx]; if (a > 0) { totalAltitude += a; fullNeighbors++; } } } } if (totalAltitude > 0) { newAltitude[y][x] = (totalAltitude / fullNeighbors) - randomBetween(0, 20); // Determine the terrain. // This is done based on the // altitude, with a small random bias. int placement = newAltitude[y][x] + randomBetween(-10, 10); if (placement > 90) { map[y][x] = mountain; } else if (placement > 70) { map[y][x] = hill; } else if (placement > 50) { map[y][x] = desert; } else if (placement > 30) { map[y][x] = forest; } else if (placement > 10) { map[y][x] = plain; } else if (placement > 0) { map[y][x] = swamp; } else { map[y][x] = ocean; } return true; } else { return false; } } // A random integer between these two (inclusive) int randomBetween(int low, int high) { int value = (int) (Math.random() * (high - low + 1) + low); // Just in case "any value between 0.00 and 1.00" actually // turns out to be 1.00, which isn't very likely... if (value == high + 1) { value = high; } return value; } // Is this location within the map? boolean legal(int y, int x) { if ((x < 0) || (x >= width)) { return false; } if ((y < 0) || (y >= height)) { return false; } return true; } } // This class is a simple "slave" canvas which asks // its owner to paint it. class MapCanvas extends Canvas { MapApplet owner; MapCanvas(MapApplet ownerArg) { owner = ownerArg; } public void paint(Graphics g) { owner.paintMap(g); } }