import java.awt.BasicStroke; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Point; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.text.NumberFormat; import java.util.LinkedList; import java.util.List; import javax.swing.JComponent; import javax.swing.Timer; @SuppressWarnings("serial") public class WedgeWorld extends JComponent { public static final Color BOARD_COLOR = new Color(0, 0, 128); public static final int CELL_SIZE = 30; public static final Stroke BOARD_STROKE = new BasicStroke(12.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); public static final double COLLISION_DISTANCE = 5.0; public static final NumberFormat distanceFormat = NumberFormat.getIntegerInstance(); static { distanceFormat.setMinimumIntegerDigits(2); distanceFormat.setMaximumIntegerDigits(2); } private Timer timer; private int[][] worldInts = new int[][] { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, {1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1}, {1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1}, {1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1}, {1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1}, {1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1}, {1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1}, {1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1}, {1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1}, {1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1}, {1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1}, {1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1}, {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} }; private Space[][] world; private int foodX = -1; private int foodY = -1; private int poisonX = -1; private int poisonY = -1; private WedgeFrame wedgeFrame; private WedgeDude wedgeDude; private Ghost[] ghosts; private Image foodImage; private Image poisonImage; public WedgeWorld(WedgeFrame wedgeFrame, Ghost[] ghosts) throws IOException { this.wedgeFrame = wedgeFrame; this.ghosts = ghosts; this.wedgeDude = new WedgeDude(); this.world = initWorld(); placeItems(); this.foodImage = this.wedgeFrame.loadImage("Food.png"); this.poisonImage = this.wedgeFrame.loadImage("Poison.png"); new GameThread().start(); } private void placeItems() { int[] food = place(Space.FOOD, 10, 10, 15); this.foodX = food[0]; this.foodY = food[1]; int[] poison; do { // Try to get the poison in the path of WedgeDude poison = place(Space.POISON, (this.wedgeDude.getGridX() + this.foodX) / 2, (this.wedgeDude.getGridY() + this.foodY) / 2, 3); } while (poison[0] == this.foodX && poison[1] == this.foodY); this.poisonX = poison[0]; this.poisonY = poison[1]; } private int[] place(Space item, int centerX, int centerY, int maxDistance) { int itemX = -1, itemY = -1; boolean placed = false; while (!placed) { itemX = centerX + (int)((Math.random() * 2 - 1.0) * maxDistance); itemY = centerY + (int)((Math.random() * 2 - 1.0) * maxDistance); if (itemY >= 0 && itemY < this.world.length && itemX >= 0 && itemX < this.world[0].length && this.world[itemY][itemX] == Space.OPEN && Point.distance(this.wedgeDude.getGridX(), itemX, this.wedgeDude.getGridY(), itemY) > 3.0) placed = true; } if (itemX > -1 && itemY > -1) this.world[itemY][itemX] = item; return new int[] { itemX, itemY }; } private Space[][] initWorld() { Space[] values = Space.values(); Space[][] world = new Space[this.worldInts.length][this.worldInts[0].length]; for (int i = 0; i < this.worldInts.length; i++) for (int j = 0; j < this.worldInts[i].length; j++) world[i][j] = values[this.worldInts[i][j]]; if (this.foodX > -1 && this.foodY > -1) world[this.foodY][this.foodX] = Space.FOOD; if (this.poisonX > -1 && this.poisonY > -1) world[this.poisonY][this.poisonX] = Space.POISON; return world; } public Space getContents(int gridX, int gridY) { return this.world[gridY][gridX]; } public void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D)g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Dimension size = getSize(); g2d.setColor(Color.WHITE); g2d.fillRect(0, 0, size.width, size.height); g2d.setColor(BOARD_COLOR); Stroke str = g2d.getStroke(); g2d.setStroke(BOARD_STROKE); int halfCell = WedgeWorld.CELL_SIZE / 2; for (int i = 0; i < this.world.length; i++) { for (int j = 0; j < this.world[i].length; j++) { int x = getPixel(j); int y = getPixel(i); Space s = this.world[i][j]; if (s == Space.WALL) { // Decide which line segments to draw to represent the maze boolean top = i == 0; boolean left = j == 0; boolean bottom = i == this.world.length - 1; boolean right = j == this.world[i].length - 1; boolean upLeftWall = !top && !left && this.world[i - 1][j - 1] == Space.WALL; boolean upWall = !top && this.world[i - 1][j] == Space.WALL; boolean upRightWall = !top && !right && this.world[i - 1][j + 1] == Space.WALL; boolean leftWall = !left && this.world[i][j - 1] == Space.WALL; boolean rightWall = !right && this.world[i][j + 1] == Space.WALL; boolean downLeftWall = !bottom && !left && this.world[i + 1][j - 1] == Space.WALL; boolean downWall = !bottom && this.world[i + 1][j] == Space.WALL; boolean downRightWall = !bottom && !right && this.world[i + 1][j + 1] == Space.WALL; if (!(leftWall && rightWall && upWall && downWall)) { if (leftWall && !(upWall && downWall && upLeftWall && downLeftWall)) g2d.drawLine(x, y, x - halfCell, y); if (rightWall && !(upWall && downWall && upRightWall && downRightWall)) g2d.drawLine(x, y, x + halfCell, y); if (upWall && !(leftWall && rightWall && upLeftWall && upRightWall)) g2d.drawLine(x, y, x, y - halfCell); if (downWall && !(leftWall && rightWall && downLeftWall && downRightWall)) g2d.drawLine(x, y, x, y + halfCell); // g2d.drawOval(x - 10, y - 10, 20, 20); } } } } g2d.setStroke(str); if (this.foodX > -1 && this.foodY > -1) { int foodPixelX = getPixel(this.foodX); int foodPixelY = getPixel(this.foodY); g2d.drawImage(this.foodImage, foodPixelX - 15, foodPixelY - 15, null); } if (this.poisonX > -1 && this.poisonY > -1) { int poisonPixelX = getPixel(this.poisonX); int poisonPixelY = getPixel(this.poisonY); g2d.drawImage(this.poisonImage, poisonPixelX - 15, poisonPixelY - 15, null); } this.wedgeDude.updateModel(); this.wedgeDude.draw(g2d, this); for (Ghost ghost : WedgeWorld.this.ghosts) { ghost.updateModel(); ghost.draw(g2d, this); } if (this.timer == null) { this.timer = new Timer(25, new ActionListener() { public void actionPerformed(ActionEvent event) { repaint(WedgeWorld.this.wedgeDude.getBoundingBox()); for (Ghost ghost : WedgeWorld.this.ghosts) repaint(ghost.getBoundingBox()); } }); this.timer.start(); } } private int getPixel(int grid) { return WedgeWorld.CELL_SIZE / 2 + CELL_SIZE * grid; } public static void printWorld(Move[][] world) { for (int i = 0; i < world.length; i++) { for (int j = 0; j < world[i].length; j++) { Move m = world[i][j]; System.out.print(m == null ? "----" : ((m.getDirection() == null ? "?" : m.getDirection().getAbbreviation()) + " " + distanceFormat.format( m.getDistance()))); System.out.print(" "); } System.out.println(); } } public class GameThread extends Thread { public void run() { int lifeSpan = Integer.MAX_VALUE; while (lifeSpan-- > 0) { try { Thread.sleep(10L); } catch (Exception e) { } WedgeDude wd = WedgeWorld.this.wedgeDude; Space[][] world = initWorld(); Location[] badLocations = new Location[WedgeWorld.this.ghosts == null ? 1 : WedgeWorld.this.ghosts.length + 1]; badLocations[0] = new Location(WedgeWorld.this.poisonX, WedgeWorld.this.poisonY, null); if (WedgeWorld.this.ghosts != null) for (int i = 0; i < WedgeWorld.this.ghosts.length; i++) badLocations[i + 1] = WedgeWorld.this.ghosts[i].getLocation(); Move[][] toFood = markMaze(WedgeWorld.this.foodX, WedgeWorld.this.foodY, world, badLocations); Move[][] toWedge = markMaze(wd.getGridX(), wd.getGridY(), world, null); long t1 = System.nanoTime(); // Move items around Move wedgeMove = null; Move[] ghostMoves = new Move[WedgeWorld.this.ghosts.length]; try { wedgeMove = WedgeBrain.nextMove(wd.getGridX(), wd.getGridY(), world, toFood, WedgeWorld.this.ghosts); } catch (Throwable t) { t.printStackTrace(); } for (int i = 0; i < ghostMoves.length; i++) { try { Ghost g = WedgeWorld.this.ghosts[i]; ghostMoves[i] = g.getBrain().nextMove(g.getGridX(), g.getGridY(), g.getDirection(), world, toWedge, wd.getGridX(), wd.getGridY()); } catch (Throwable t) { t.printStackTrace(); } } long t2 = System.nanoTime(); // System.out.println(t2 - t1); if (wedgeMove != null && wedgeMove.getDirection() != null) wd.move(WedgeWorld.this, wedgeMove.getDirection()); for (int i = 0; i < ghostMoves.length; i++) if (ghostMoves[i] != null && ghostMoves[i].getDirection() != null) WedgeWorld.this.ghosts[i].move(WedgeWorld.this, ghostMoves[i].getDirection()); // Detect proximity to items and react int wdx = wd.getPixelX(); int wdy = wd.getPixelY(); int fx = getPixel(WedgeWorld.this.foodX); int fy = getPixel(WedgeWorld.this.foodY); int px = getPixel(WedgeWorld.this.poisonX); int py = getPixel(WedgeWorld.this.poisonY); for (Ghost ghost : WedgeWorld.this.ghosts) { int gx = getPixel(ghost.getGridX()); int gy = getPixel(ghost.getGridY()); double ghostDistance = Point.distance(wdx, wdy, gx, gy); if (ghostDistance < COLLISION_DISTANCE && lifeSpan > 15) { die(); lifeSpan = 15; } } double foodDistance = Point.distance(wdx, wdy, fx, fy); double poisonDistance = Point.distance(wdx, wdy, px, py); if (poisonDistance < COLLISION_DISTANCE && lifeSpan > 15) { die(); lifeSpan = 15; } if (foodDistance < COLLISION_DISTANCE) { placeItems(); repaint(); } } System.out.println("Exiting"); } private void die() { System.out.println("Die"); WedgeWorld.this.poisonX = -1; WedgeWorld.this.poisonY = -1; WedgeWorld.this.foodX = -1; WedgeWorld.this.foodY = -1; WedgeWorld.this.wedgeDude.die(); } } /** * Trace out the distance of each cell in the world from a given reference * location. * @param refX Ground zero in the X dimension * @param refY Ground zero in the Y dimension * @param world Grid of world geography * @return Grid of moves towards the reference cell */ public static Move[][] markMaze(int refX, int refY, Space[][] world, Location[] blockedSpaces) { Move[][] solution = new Move[world.length][world[0].length]; if (refX > -1 && refY > -1) { List locations = new LinkedList(); locations.add(new Location(refX, refY, new Move(null, 0))); while (locations.size() > 0) { Location l = locations.remove(0); if (solution[l.getY()][l.getX()] == null) { solution[l.getY()][l.getX()] = l.getMove(); int newDistance = l.getMove().getDistance() + 1; if (isValid(l.getX(), l.getY() - 1, world, blockedSpaces)) locations.add(new Location(l.getX(), l.getY() - 1, new Move( Direction.SOUTH, newDistance))); if (isValid(l.getX() + 1, l.getY(), world, blockedSpaces)) locations.add(new Location(l.getX() + 1, l.getY(), new Move( Direction.WEST, newDistance))); if (isValid(l.getX(), l.getY() + 1, world, blockedSpaces)) locations.add(new Location(l.getX(), l.getY() + 1, new Move( Direction.NORTH, newDistance))); if (isValid(l.getX() - 1, l.getY(), world, blockedSpaces)) locations.add(new Location(l.getX() - 1, l.getY(), new Move( Direction.EAST, newDistance))); } } } return solution; } public static boolean isValid(int x, int y, Space[][] world, Location[] blockedSpaces) { if (x < 0 || x > 19 || y < 0 || y > 19) return false; Space s = world[y][x]; if (!s.isPassable()) return false; if (blockedSpaces != null) for (int i = 0; i < blockedSpaces.length; i++) if (blockedSpaces[i].getX() == x && blockedSpaces[i].getY() == y) return false; return true; } }