/*================================ BOULDERDASH ------------- Dave Koelle August 2000 http://www.ultranet.com/~dkoelle Sound has been disabled for this version to make the applet load faster. To bring back sound, uncomment parts of redrawAndSound(), redrawCoords(), init(), and three instances of moveSound[].play() =================================*/ import java.applet.*; import java.awt.*; import java.awt.image.*; import java.awt.event.*; import java.net.*; import java.util.*; import java.lang.Thread; import java.io.*; import java.util.StringTokenizer; import java.util.Vector; class Coord { int x,y; public Coord() { x = 0; y = 0; } public Coord(int x, int y) { this.x = x; this.y = y; } } class BotPosition { int x,y; int heading,direction; public BotPosition() { x = 0; y = 0; heading = 1; direction = 0; } public BotPosition(int x, int y, int heading, int direction) { this.x = x; this.y = y; this.heading = heading; this.direction = direction; } } public class Boulderdash extends Applet implements Runnable { private static final String VERSION = "1.0"; private static final String ID = "200009131200"; Vector redraw = new Vector(); Vector sounds = new Vector(); Graphics g; Thread botThread; boolean paused; private static final int SLEEP_DELAY = 2750; int maxtimer=1,timer,level; int score,accumulatedScore,scorePerDiamond=5,scorePerExtraDiamond=10; public static final int UP = 0; public static final int RIGHT = 1; public static final int DOWN = 2; public static final int LEFT = 3; // Control buttons private Button buttonPrev = new Button("< Prev"); private Button buttonRestart = new Button("Restart"); private Button buttonNext = new Button("Next >"); // Game variables Coord coordPlayer,coordExit; int numNeededDiamonds,numFulfilledDiamonds; boolean levelDone,playerAlmostDead,playerDead; // Arrays to handle input from APPLET tag int[] arrNumNeededDiamonds,arrScorePerDiamond,arrScorePerExtraDiamond,arrMaxtimer; String[] arrLevelTitle,arrLevelAuthor,arrImageFilename; // Image and icon data private Image[] img; private static final int NUM_IMAGES = 15; private static final String imgRep = ".:#WMPO+*BFA][R"; private String imageFilename = "images.gif"; private static int[][] imgCoord = {{ 0, 0,20,20}, // Space { 20, 0,20,20}, // Dirt { 40, 0,20,20}, // Titanium Wall { 0,20,20,20}, // Brick Wall { 20,20,20,20}, // Magic Wall { 40,20,20,20}, // Player { 0,40,20,20}, // Rock { 20,40,20,20}, // Diamond { 40,40,20,20}, // Explosion { 0,60,20,20}, // Butterfly { 20,60,20,20}, // Firefly { 40,60,20,20}, // Amoeba { 0,80,20,20}, // Closed Exit { 20,80,20,20}, // Open Exit { 40,80,20,20}}; // Robot (not programmed) // Sound data private static final int NUM_SOUNDS = 13; private static final int MOVE_MAGICWALL_SOUND = 0; private static final int MOVE_ROCK_SOUND = 1; private static final int MOVE_DIAMOND_SOUND = 2; private static final int MOVE_OPENEXIT_SOUND = 3; private static final int MOVE_AMOEBA_SOUND = 4; private static final int MOVE_SMASH_SOUND = 5; private static final int MOVE_NULL_SOUND = 6; private static final int MOVE_DIRT_SOUND = 7; private static final int MOVE_PLAYERSMASH_SOUND = 8; private static final int MOVE_DIED_SOUND = 9; private static final int MOVE_CONGRATULATIONS_SOUND = 10; private static final int MOVE_COMPLETE_SOUND = 11; private static final int MOVE_ROCK2_SOUND = 12; private static String[] soundFilename = {"magicwall.wav", "rock.wav", "diamond.au", "openexit.wav", "amoeba.au", "smash.au", "null.au", "dirt.au", "playersmash.au", "died.wav", "congrats.wav", "complete.wav", "rock2.wav"}; private AudioClip[] moveSound; // Map and level data private static final int X_DIMEN = 20; private static final int Y_DIMEN = 20; private static final int SQUARE_DIMEN = 20; private static final int CON_DIMEN = 40; private static final int VER_DIMEN = 12; private static final int SIG_DIMEN = 24; private char[][] levelMap; private int NUM_LEVELS; private String levelTitle,levelAuthor; private String[][] levelData; Vector bots = new Vector(); public void init() { if (g == null) { g = getGraphics(); } this.requestFocus(); // Ask for keyboard focus so we get key events // Get parameters from the HTML file String numlevels = getParameter("levels"); if (numlevels == null) return; NUM_LEVELS = Integer.parseInt(numlevels); levelData = new String[NUM_LEVELS][Y_DIMEN]; arrNumNeededDiamonds = new int[NUM_LEVELS]; arrScorePerDiamond = new int[NUM_LEVELS]; arrScorePerExtraDiamond = new int[NUM_LEVELS]; arrMaxtimer = new int[NUM_LEVELS]; arrLevelTitle = new String[NUM_LEVELS]; arrLevelAuthor = new String[NUM_LEVELS]; arrImageFilename = new String[NUM_LEVELS]; for (int i=0; i=0) && (explosionsQuenched); y--) { for (int x=0; (x=0; y--) { for (int x=0; x 0) && (levelMap[x][y-1] == 'A')) || ((x+1 < X_DIMEN) && (levelMap[x+1][y] == 'A')) || ((y+1 < Y_DIMEN) && (levelMap[x][y+1] == 'A')) || ((x-1 > 0) && (levelMap[x-1][y] == 'A')) && ((levelMap[x][y] == 'B') || (levelMap[x][y] == 'F'))) { amoebaSmash = true; smashCoord = new Coord(x,y); } // Quench all explosions if (levelMap[x][y] == '*') { levelMap[x][y] = '.'; redrawAndSound(new Coord(x,y),MOVE_NULL_SOUND); explosionsQuenched = true; } else // If, for any reason, the player is dead, restart the level // Do this AFTER the explosions take place if (playerDead) { // paused = true; // All done with the level! Give a little pause... niceBanner("Oh no, you've died!"); /* moveSound[MOVE_DIED_SOUND].play();*/ try { Thread.sleep(SLEEP_DELAY); } catch (Exception e) { e.printStackTrace(); } score = accumulatedScore; setupLevel(level); paused = false; } // Is the player standing next to a bot? if (levelMap[x][y] == 'P') { if (((y-1 > 0) && ((levelMap[x][y-1] == 'B') || (levelMap[x][y-1] == 'F'))) || ((x+1 < X_DIMEN) && ((levelMap[x+1][y] == 'B') || (levelMap[x+1][y] == 'F'))) || ((y+1 < Y_DIMEN) && ((levelMap[x][y+1] == 'B') || (levelMap[x][y+1] == 'F'))) || ((x-1 > 0) && ((levelMap[x-1][y] == 'B') || (levelMap[x-1][y] == 'F')))) { botSmash = true; smashCoord = new Coord(x,y); playerAlmostDead = true; } } else // Work with rocks and diamonds if ((levelMap[x][y] == 'O') || (levelMap[x][y] == '+') || (amoebaSmash || botSmash)) { if (levelMap[x][y] == 'O') { me = 'O'; } else if (levelMap[x][y] == '+') { me = '+'; } else { me = '?'; } // Gravity first... if ((me != '?') && (levelMap[x][y+1] == '.')) { levelMap[x][y+1] = me; levelMap[x][y] = '.'; redrawAndSound(new Coord(x,y),MOVE_ROCK_SOUND); redrawAndSound(new Coord(x,y+1),MOVE_ROCK2_SOUND); if ((y+2 < Y_DIMEN) && ((levelMap[x][y+2] == 'B') || (levelMap[x][y+2] == 'F') || (levelMap[x][y+2] == 'P'))) { rockSmash = true; smashCoord = new Coord(x,y+2); } } else // ... then handle stack collapsing if ((me != '?') && ((levelMap[x][y+1] == 'O') || (levelMap[x][y+1] == '+'))) { if ((levelMap[x+1][y+1] == '.') && (levelMap[x+1][y] == '.')) { levelMap[x+1][y+1] = me; levelMap[x][y] = '.'; redrawAndSound(new Coord(x,y),MOVE_ROCK2_SOUND); redrawAndSound(new Coord(x+1,y+1),MOVE_ROCK_SOUND); if ((y+2 < Y_DIMEN) && ((levelMap[x+1][y+2] == 'B') && (levelMap[x+1][y+2] == 'F') || (levelMap[x+1][y+2] == 'P'))) { rockSmash = true; smashCoord = new Coord(x+1,y+2); } } else if ((levelMap[x-1][y+1] == '.') && (levelMap[x-1][y] == '.')) { levelMap[x-1][y+1] = me; levelMap[x][y] = '.'; redrawAndSound(new Coord(x,y),MOVE_ROCK2_SOUND); redrawAndSound(new Coord(x-1,y+1),MOVE_ROCK2_SOUND); if ((y+2 < Y_DIMEN) && ((levelMap[x-1][y+2] == 'B') && (levelMap[x-1][y+2] == 'F') || (levelMap[x-1][y+2] == 'P'))) { rockSmash = true; smashCoord = new Coord(x-1,y+2); } } } } // Smash a butterfly, fly, or player label1: if ((botSmash || amoebaSmash || rockSmash) && ((levelMap[smashCoord.x][smashCoord.y] == 'B') || (levelMap[smashCoord.x][smashCoord.y] == 'F') || (levelMap[smashCoord.x][smashCoord.y] == 'P'))) { boolean diamond = (levelMap[smashCoord.x][smashCoord.y] == 'B')?true:false; if (levelMap[smashCoord.x][smashCoord.y] == 'P') { if (botSmash || rockSmash) { playerAlmostDead = true; } else break label1; } for (int i=smashCoord.x-1;i<=smashCoord.x+1;i++) { for (int u=smashCoord.y-1;u<=smashCoord.y+1;u++) { if ((levelMap[i][u] == '.') || (levelMap[i][u] == ':') || (levelMap[i][u] == 'W') || (levelMap[i][u] == 'M') || (levelMap[i][u] == '+') || (levelMap[i][u] == 'A') || ((i==smashCoord.x) && (u==smashCoord.y-1)) || ((i==smashCoord.x) && (u==smashCoord.y)) && (i > 0) && (i < X_DIMEN) && (u > 0) && (u < Y_DIMEN)) { if (diamond) { levelMap[i][u]='+'; } else { levelMap[i][u]='*'; } redrawAndSound(new Coord(i,u),MOVE_SMASH_SOUND); for (int b=0; b 1) && ((levelMap[x][y-1] == 'O') || (levelMap[x][y-1] == '+'))) { if (levelMap[x][y+1] == '.') { if (levelMap[x][y-1] == 'O') { levelMap[x][y+1] = '+'; redrawAndSound(new Coord(x,y+1),MOVE_MAGICWALL_SOUND); } else { levelMap[x][y+1] = 'O'; redrawAndSound(new Coord(x,y+1),MOVE_MAGICWALL_SOUND); } } levelMap[x][y-1] = '.'; redrawAndSound(new Coord(x,y-1),MOVE_NULL_SOUND); } // Grow the amoeba if (levelMap[x][y] == 'A') { if (timer >= maxtimer) { if (Math.random() < 0.10) { int dx=0,dy=0; if (Math.random() < 0.5) { dx = (int)((Math.round(Math.random()*2))-1); dy = 0; } else { dy = (int)((Math.round(Math.random()*2))-1); dx = 0; } // Spread it only to places that are empty or have dirt if ((x+dx < X_DIMEN) && (x+dx > 0) && (y+dy < Y_DIMEN) && (y+dy > 0)) { if ((levelMap[x+dx][y+dy] == '.') || (levelMap[x+dx][y+dy] == ':')) { levelMap[x+dx][y+dy] = 'A'; redrawAndSound(new Coord(x+dx,y+dy),MOVE_AMOEBA_SOUND); } } } } } } // for } // for // Move any Fireflies clockwise, and Butterflies counterclockwise if (timer >= maxtimer) { for (int b = 0; b < bots.size(); b++) { BotPosition bot = (BotPosition)bots.elementAt(b); Coord c = new Coord(bot.x,bot.y); if (bot.heading == UP) { if ((bot.direction == UP) && (levelMap[bot.x+1][bot.y] == '.')) { bot.x++; bot.heading = RIGHT; } else if ((bot.direction == RIGHT) && (levelMap[bot.x-1][bot.y] == '.')) { bot.x--; bot.heading = LEFT; } else if (levelMap[bot.x][bot.y-1] == '.') { bot.y--; } else { bot.heading = DOWN; } } if (bot.heading == RIGHT) { if ((bot.direction == UP) && (levelMap[bot.x][bot.y+1] == '.')) { bot.y++; bot.heading = DOWN; } else if ((bot.direction == RIGHT) && (levelMap[bot.x][bot.y-1] == '.')) { bot.y--; bot.heading = UP; } else if (levelMap[bot.x+1][bot.y] == '.') { bot.x++; } else { bot.heading = LEFT; } } if (bot.heading == DOWN) { if ((bot.direction == UP) && (levelMap[bot.x-1][bot.y] == '.')) { bot.x--; bot.heading = LEFT; } else if ((bot.direction == RIGHT) && (levelMap[bot.x+1][bot.y] == '.')) { bot.x++; bot.heading = RIGHT; } else if (levelMap[bot.x][bot.y+1] == '.') { bot.y++; } else { bot.heading = UP; } } if (bot.heading == LEFT) { if ((bot.direction == UP) && (levelMap[bot.x][bot.y-1] == '.')) { bot.y--; bot.heading = UP; } else if ((bot.direction == RIGHT) && (levelMap[bot.x][bot.y+1] == '.')) { bot.y++; bot.heading = DOWN; } else if (levelMap[bot.x-1][bot.y] == '.') { bot.x--; } else { bot.heading = RIGHT; } } bots.setElementAt(bot,b); if ((bot.x != c.x) || (bot.y != c.y)) { levelMap[c.x][c.y] = '.'; levelMap[bot.x][bot.y] = (bot.direction==0)?'F':'B'; redrawAndSound(new Coord(c.x,c.y),MOVE_NULL_SOUND); redrawAndSound(new Coord(bot.x,bot.y),MOVE_NULL_SOUND); } } } // timer // Check if amoeba is suffocated boolean amoebaSuffocated = true; for (int i=1; i= maxtimer) { timer = 0; } timer++; try { Thread.sleep(150); } catch (InterruptedException e) { System.out.println("Interrupted"); } } // while } // run public boolean action(Event evt, Object o) { if ((evt.target == buttonPrev) && (level-1 > 0)) { score = accumulatedScore; setupLevel(--level); } else if (evt.target == buttonRestart) { score = accumulatedScore; setupLevel(level); } else if ((evt.target == buttonNext) && (level+1 < NUM_LEVELS+1)) { score = accumulatedScore; setupLevel(++level); } return true; } public boolean mouseDown(Event evt, int x, int y) { if ((x > 0) && (x < X_DIMEN*SQUARE_DIMEN) && (y > Y_DIMEN*SQUARE_DIMEN+CON_DIMEN) && (y < Y_DIMEN*SQUARE_DIMEN+CON_DIMEN+SIG_DIMEN)) { g.setFont(new Font("Helvetica",Font.BOLD|Font.ITALIC,14)); for (int i=0; i<3; i++) { g.setColor(Color.magenta); g.fillRect(0,Y_DIMEN*SQUARE_DIMEN+CON_DIMEN,X_DIMEN*SQUARE_DIMEN,SIG_DIMEN); try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } g.setColor(Color.blue); g.fillRect(0,Y_DIMEN*SQUARE_DIMEN+CON_DIMEN,X_DIMEN*SQUARE_DIMEN,SIG_DIMEN); g.setColor(Color.yellow); g.drawString("Click here to visit this applet's homepage!",45,(Y_DIMEN*SQUARE_DIMEN)+CON_DIMEN+16); try { Thread.sleep(150); } catch (InterruptedException e) { e.printStackTrace(); } } try { AppletContext ac = getAppletContext(); // PLEASE DO NOT CHANGE WHERE THIS URL POINTS! ac.showDocument(new URL("http://www.ultranet.com/~dkoelle/Boulderdash")); } catch (Exception e) { e.printStackTrace(); } } return true; } public boolean keyDown(Event evt, int keyCode) { if (paused) { return false; } Coord delta = new Coord(); Coord delta2 = new Coord(); Coord move = new Coord(); Coord move2 = new Coord(); boolean validMove = false; // Interpret the keycode if (keyCode == Event.UP) { delta.x = 0; delta.y = -1; } else if (keyCode == Event.RIGHT) { delta.x = 1; delta.y = 0; } else if (keyCode == Event.DOWN) { delta.x = 0; delta.y = 1; } else if (keyCode == Event.LEFT) { delta.x = -1; delta.y = 0; } else if (keyCode == Event.ESCAPE) { playerAlmostDead = true; levelMap[coordPlayer.x][coordPlayer.y] = '*'; redrawAndSound(coordPlayer,MOVE_PLAYERSMASH_SOUND); } // Make move relative to player move.x = coordPlayer.x + delta.x; move.y = coordPlayer.y + delta.y; // Range checking on the move if (move.x < 0) { move.x = 0; } else if (move.x > X_DIMEN) { move.x = X_DIMEN; } if (move.y < 0) { move.y = 0; } else if (move.y > Y_DIMEN) { move.y = Y_DIMEN; } // Create a "look-ahead" position delta2.x = delta.x*2; delta2.y = delta.y*2; move2.x = coordPlayer.x + delta2.x; move2.y = coordPlayer.y + delta2.y; // Moving into an empty space? "I'll allow it!" -- Mills Lane if ((levelMap[move.x][move.y] == '.') || (levelMap[move.x][move.y] == ':')) { if (levelMap[move.x][move.y] == '.') { redrawAndSound(move,MOVE_NULL_SOUND); // SPACE sound } else { redrawAndSound(move,MOVE_NULL_SOUND); // DIRT sound } validMove = true; } else // Pushing a rock? if ((levelMap[move.x][move.y] == 'O') && (move2.y != move.y-1)) { if (((move2.x >= 0) && (move2.x <= X_DIMEN)) && ((move2.y >= 0) && (move2.y <= Y_DIMEN))) { if (levelMap[move2.x][move2.y] == '.') { // "look-ahead" is an empty space. Move the rock. levelMap[move2.x][move2.y] = levelMap[move.x][move.y]; redrawAndSound(move2,MOVE_ROCK_SOUND); redrawAndSound(move,MOVE_NULL_SOUND); validMove = true; } } } else // Grabbing a Diamond? // Add one to the Number of Fulfilled Diamonds, // and see if the exit should be opened if (levelMap[move.x][move.y] == '+') { numFulfilledDiamonds++; score += (numFulfilledDiamonds