From a8e68af6dbb714c50bbe71cae2c5c62f3f601028 Mon Sep 17 00:00:00 2001 From: cognitivegears Date: Mon, 21 Apr 2014 21:44:36 -0500 Subject: [PATCH] Initial version of pathfinding --- src/com/droidquest/avatars/GameCursor.java | 111 ++++------- src/com/droidquest/avatars/PaintBrush.java | 29 ++- src/com/droidquest/avatars/SolderingPen.java | 67 +++---- src/com/droidquest/items/Item.java | 81 ++++++-- src/com/droidquest/materials/Material.java | 5 + src/com/droidquest/pathfinder/Node.java | 180 ++++++++++++++++++ src/com/droidquest/pathfinder/Pathfinder.java | 178 +++++++++++++++++ 7 files changed, 524 insertions(+), 127 deletions(-) create mode 100644 src/com/droidquest/pathfinder/Node.java create mode 100644 src/com/droidquest/pathfinder/Pathfinder.java diff --git a/src/com/droidquest/avatars/GameCursor.java b/src/com/droidquest/avatars/GameCursor.java index 4ee9fa3..54d3568 100644 --- a/src/com/droidquest/avatars/GameCursor.java +++ b/src/com/droidquest/avatars/GameCursor.java @@ -3,6 +3,7 @@ package com.droidquest.avatars; import com.droidquest.Room; import com.droidquest.RoomDisplay; import com.droidquest.items.*; +import com.droidquest.pathfinder.Node; import javax.swing.*; import java.awt.*; @@ -324,81 +325,51 @@ public class GameCursor extends Player { return level.cheatmode; } - public void Animate() { - if (automove == 1 && room == null) { - automove = 0; - } - if (automove == 1) { - int dx = autoX - x; - int dy = autoY - y; - if (dx == 0 && dy == 0) { - automove = 0; - return; - } - if (dx < -28) { - dx = -28; - } - if (dx > 28) { - dx = 28; - } - if (dy < -32) { - dy = -32; - } - if (dy > 32) { - dy = 32; - } - walk = 1 - walk; - if (dx == 0) { - if (dy < 0) { - currentIcon = icons[0 + walk].getImage(); - } - else { - currentIcon = icons[2 + walk].getImage(); - } + + @Override + protected void animateCharacter(int dx, int dy) { + walk = 1 - walk; + if (dx == 0) { + if (dy < 0) { + currentIcon = icons[0 + walk].getImage(); } else { - if (dx < 0) { - currentIcon = icons[4 + walk].getImage(); - } - else { - currentIcon = icons[6 + walk].getImage(); - } - } - if (dx > 0) { - moveRight(dx); - } - if (dx < 0) { - moveLeft(-dx); - } - if (dy > 0) { - moveDown(dy); - } - if (dy < 0) { - moveUp(-dy); - } - } - if (automove == 2) { - walk = 1 - walk; - if (autoX > 0) { - currentIcon = icons[6 + walk].getImage(); - moveRight(autoX); - } - - if (autoX < 0) { - currentIcon = icons[4 + walk].getImage(); - moveLeft(-autoX); - } - - if (autoY > 0) { currentIcon = icons[2 + walk].getImage(); - moveDown(autoY); - } - - if (autoY < 0) { - currentIcon = icons[0 + walk].getImage(); - moveUp(-autoY); } } + else { + if (dx < 0) { + currentIcon = icons[4 + walk].getImage(); + } + else { + currentIcon = icons[6 + walk].getImage(); + } + } + } + + @Override + protected void autoMoveFull() { + walk = 1 - walk; + if (autoX > 0) { + currentIcon = icons[6 + walk].getImage(); + moveRight(autoX); + } + + if (autoX < 0) { + currentIcon = icons[4 + walk].getImage(); + moveLeft(-autoX); + } + + if (autoY > 0) { + currentIcon = icons[2 + walk].getImage(); + moveDown(autoY); + } + + if (autoY < 0) { + currentIcon = icons[0 + walk].getImage(); + moveUp(-autoY); + } + } public GenericRobot PlayerInRobot(GenericRobot robot) { diff --git a/src/com/droidquest/avatars/PaintBrush.java b/src/com/droidquest/avatars/PaintBrush.java index 16b11c6..6dd7322 100644 --- a/src/com/droidquest/avatars/PaintBrush.java +++ b/src/com/droidquest/avatars/PaintBrush.java @@ -93,8 +93,8 @@ public class PaintBrush extends Player { paintMats[1] = Material.FindSimiliar(new Material(Color.green, false, false)); Item robot = null; - for(Item item : level.items) { - if(item instanceof OrangeRobot) { + for (Item item : level.items) { + if (item instanceof OrangeRobot) { robot = item; } } @@ -104,14 +104,14 @@ public class PaintBrush extends Player { paintMats[2] = Material.FindSimiliar(new RobotBlocker(robot, new Color(255, 128, 0))); for (Item item : level.items) { - if(item instanceof WhiteRobot) { + if (item instanceof WhiteRobot) { robot = item; } } paintMats[3] = Material.FindSimiliar(new RobotBlocker(robot, Color.white)); - for(Item item : level.items) { - if(item instanceof BlueRobot) { + for (Item item : level.items) { + if (item instanceof BlueRobot) { robot = item; } } @@ -210,9 +210,10 @@ public class PaintBrush extends Player { @Override protected boolean handleRepeatSpace() { - return false; + return false; } + @Override public void moveUp(boolean nudge) { int dist = 32; if (nudge) { @@ -231,6 +232,7 @@ public class PaintBrush extends Player { } } + @Override public void moveDown(boolean nudge) { int dist = 32; if (nudge) { @@ -249,6 +251,20 @@ public class PaintBrush extends Player { } } +// @Override +// protected void findPath(int startX, int startY, int endX, int endY) { +// // The paintbrush can go anywhere +//// autoPath = new ArrayList(); +//// autoPath.add(new Node(endX * 28, endY * 32)); +// autoX = endX * 28; +// autoY = endY * 28; +// autoX -= autoX % 2; // Even numbered pixel only! +// autoY -= autoY % 2; +// automove = 1; +// +// } + + @Override public void moveLeft(boolean nudge) { int dist = 28; if (nudge) { @@ -267,6 +283,7 @@ public class PaintBrush extends Player { } } + @Override public void moveRight(boolean nudge) { int dist = 28; if (nudge) { diff --git a/src/com/droidquest/avatars/SolderingPen.java b/src/com/droidquest/avatars/SolderingPen.java index a82d041..f8c1ded 100644 --- a/src/com/droidquest/avatars/SolderingPen.java +++ b/src/com/droidquest/avatars/SolderingPen.java @@ -225,6 +225,7 @@ public class SolderingPen extends Device implements Avatar { CheckPort(); } + @Override public void Animate() { Room tempRoom = room; super.Animate(); @@ -297,16 +298,16 @@ public class SolderingPen extends Device implements Avatar { public boolean KeyUp(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_C && handleGameCursor()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_R && handleRadio()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_P && handlePaintbrush()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_SLASH && handleHelp()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { if (carriedBy == null) { @@ -340,13 +341,13 @@ public class SolderingPen extends Device implements Avatar { WirePort(); } else if (e.getKeyCode() == KeyEvent.VK_F && handleFlipDevice()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_E && handleEnterRoom()) { - return false; + return false; } else if (e.getKeyCode() == KeyEvent.VK_X && handleExitRoom()) { - return false; + return false; } return false; } @@ -592,32 +593,32 @@ public class SolderingPen extends Device implements Avatar { @Override public boolean handleFlipDevice() { if (hot) { - if (ports[0].myWire != null) // If SP is wired - { - // Flip wire attached to SP - Port tempPort = ports[0].myWire.fromPort; - ports[0].myWire.fromPort = ports[0].myWire.toPort; - ports[0].myWire.toPort = tempPort; - } - else if (ports[0].myWire == null) // If SP is not wired - { - // Flip wire attached to CurrentPort - if (currentPort.myWire != null) { - Port tempPort = currentPort.myWire.fromPort; - currentPort.myWire.fromPort = currentPort.myWire.toPort; - currentPort.myWire.toPort = tempPort; - } - } - } - else { - if (ports[0].myWire != null) // If SP is wired - { - // Flip wire attached to SP - Port tempPort = ports[0].myWire.fromPort; - ports[0].myWire.fromPort = ports[0].myWire.toPort; - ports[0].myWire.toPort = tempPort; - } - } + if (ports[0].myWire != null) // If SP is wired + { + // Flip wire attached to SP + Port tempPort = ports[0].myWire.fromPort; + ports[0].myWire.fromPort = ports[0].myWire.toPort; + ports[0].myWire.toPort = tempPort; + } + else if (ports[0].myWire == null) // If SP is not wired + { + // Flip wire attached to CurrentPort + if (currentPort.myWire != null) { + Port tempPort = currentPort.myWire.fromPort; + currentPort.myWire.fromPort = currentPort.myWire.toPort; + currentPort.myWire.toPort = tempPort; + } + } + } + else { + if (ports[0].myWire != null) // If SP is wired + { + // Flip wire attached to SP + Port tempPort = ports[0].myWire.fromPort; + ports[0].myWire.fromPort = ports[0].myWire.toPort; + ports[0].myWire.toPort = tempPort; + } + } return true; } } diff --git a/src/com/droidquest/items/Item.java b/src/com/droidquest/items/Item.java index 1eee5af..ce54d4a 100644 --- a/src/com/droidquest/items/Item.java +++ b/src/com/droidquest/items/Item.java @@ -5,6 +5,8 @@ import com.droidquest.Wire; import com.droidquest.devices.Device; import com.droidquest.levels.Level; import com.droidquest.materials.ChipTrash; +import com.droidquest.pathfinder.Node; +import com.droidquest.pathfinder.Pathfinder; import javax.swing.*; import java.awt.*; @@ -15,6 +17,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.util.ArrayList; public class Item implements Serializable, Cloneable { public transient static Level level; @@ -28,6 +31,7 @@ public class Item implements Serializable, Cloneable { public transient int autoX; // Destination of automovement public transient int autoY; // Destination of automovement public Room InternalRoom = null; // Room inside this item, if any. + public transient ArrayList autoPath = new ArrayList(); protected int repeating = 0; // Keyboard repeat. public int charge = 0; // Battery Charge of this item, if any. @@ -225,6 +229,11 @@ public class Item implements Serializable, Cloneable { return false; } + protected void findPath(int startX, int startY, int endX, int endY) { + Pathfinder pf = new Pathfinder(room); + autoPath = pf.search(startX, startY, endX, endY, this); + } + public void MouseClick(MouseEvent e) { int button = 0; if ((e.getModifiers() & InputEvent.BUTTON1_MASK) == InputEvent.BUTTON1_MASK) { @@ -236,11 +245,23 @@ public class Item implements Serializable, Cloneable { if (button == 1) { if (e.getClickCount() == 1) { - autoX = e.getX() - width / 2; - autoY = e.getY() - height / 2; - autoX -= autoX % 2; // Even numbered pixel only! - autoY -= autoY % 2; - automove = 1; + int endX = e.getX() / 28; + int endY = e.getY() / 32; + + int startX = x / 28; + int startY = y / 32; + + findPath(startX, startY, endX, endY); + + if(autoPath != null && autoPath.size() > 0) { + Node next = autoPath.remove(0); + + autoX = next.getX(); + autoY = next.getY(); + autoX -= autoX % 2; // Even numbered pixel only! + autoY -= autoY % 2; + automove = 1; + } } else if (e.getClickCount() == 2) { int dx = e.getX() - width / 2 - x; @@ -436,6 +457,10 @@ public class Item implements Serializable, Cloneable { moveRight(dist); } + protected void animateCharacter(int dx, int dy) { + + } + public void Animate() { if (automove == 1 && room == null) { automove = 0; @@ -443,8 +468,21 @@ public class Item implements Serializable, Cloneable { if (automove == 1) { int dx = autoX - x; int dy = autoY - y; + if (dx == 0 && dy == 0) { - automove = 0; + if(autoPath.size() > 0) { + Node next = autoPath.remove(0); + autoX = next.getX(); + autoY = next.getY(); + autoX -= autoX % 2; // Even numbered pixel only! + autoY -= autoY % 2; + dx = autoX - x; + dy = autoY - y; + } + if(dx == 0 && dy == 0) { + automove = 0; + return; + } } if (dx < -28) { dx = -28; @@ -458,6 +496,9 @@ public class Item implements Serializable, Cloneable { if (dy > 32) { dy = 32; } + + animateCharacter(dx, dy); + if (dx > 0) { moveRight(dx); } @@ -472,18 +513,22 @@ public class Item implements Serializable, Cloneable { } } if (automove == 2) { - if (autoX > 0) { - moveRight(autoX); - } - if (autoX < 0) { - moveLeft(-autoX); - } - if (autoY > 0) { - moveDown(autoY); - } - if (autoY < 0) { - moveUp(-autoY); - } + autoMoveFull(); + } + } + + protected void autoMoveFull() { + if (autoX > 0) { + moveRight(autoX); + } + if (autoX < 0) { + moveLeft(-autoX); + } + if (autoY > 0) { + moveDown(autoY); + } + if (autoY < 0) { + moveUp(-autoY); } } diff --git a/src/com/droidquest/materials/Material.java b/src/com/droidquest/materials/Material.java index 00266ef..3f4a55a 100644 --- a/src/com/droidquest/materials/Material.java +++ b/src/com/droidquest/materials/Material.java @@ -1,6 +1,7 @@ package com.droidquest.materials; import com.droidquest.RoomDisplay; +import com.droidquest.avatars.PaintBrush; import com.droidquest.items.Item; import com.droidquest.levels.Level; @@ -62,6 +63,10 @@ public class Material implements Serializable, Cloneable { } public boolean Passable(Item item) { + // The PaintBrush can pass anything + if(item instanceof PaintBrush) { + return true; + } return passable; } diff --git a/src/com/droidquest/pathfinder/Node.java b/src/com/droidquest/pathfinder/Node.java new file mode 100644 index 0000000..c305ef6 --- /dev/null +++ b/src/com/droidquest/pathfinder/Node.java @@ -0,0 +1,180 @@ +package com.droidquest.pathfinder; + +import com.droidquest.materials.Material; + +/** + * Represents a Node in a path, using the A* algorithm. + */ +public class Node implements Cloneable { + private int x = -1; + private int y = -1; + private Material material = null; + + private int gCost = 0; + private int hCost = 0; + private int fCost = 0; + + Node parent = null; + + /** + * Create a new, empty Node + */ + public Node() { + this(-1, -1, null, null); + } + + /** + * Create a new Node with given coordinates + * @param x Coordinate (of the room array, not screen location) + * @param y Coordinate (of the room array, not screen location) + */ + public Node(int x, int y) { + this(x, y, null, null); + } + + /** + * Create a new room Node with the given coordinates and material + * @param x Coordinate (of the room array, not screen location) + * @param y Coordinate (of the room array, not screen location) + * @param mat Material at the Node location + */ + public Node(int x, int y, Material mat) { + this(x, y, null, mat); + } + + /** + * Create a new room Node with a given parent Node (for pathing) + * @param x Coordinate (of the room array, not screen location) + * @param y Coordinate (of the room array, not screen location) + * @param parent current Parent node + */ + public Node(int x, int y, Node parent) { + this(x, y, parent, null); + } + + /** + * Create a new room node with a given parent Node and Material + * @param x Coordinate (of the room array, not screen location) + * @param y Coordinate (of the room array, not screen location) + * @param parent current Parent node + * @param mat Material at the Node location + */ + public Node(int x, int y, Node parent, Material mat) { + this.x = x; + this.y = y; + this.parent = parent; + this.material = mat; + } + + /** + * Retrieve the current room location X coordinate (room array, not screen location) + * @return X coordinate + */ + public int getX() { + return x; + } + + /** + * Set the current room location X coordinate (room array, not screen location) + * @param x coordinate + */ + public void setX(int x) { + this.x = x; + } + + /** + * Retrieve the current room location Y coordinate (room array, not screen location) + * @return y coordinate + */ + public int getY() { + return y; + } + + /** + * Set the current room Y coordinate (room array, not screen location) + * @param y coordinate + */ + public void setY(int y) { + this.y = y; + } + + /** + * Retrieve the currently set Material for the Node (or null if not set) + * @return Material + * @see com.droidquest.materials.Material + */ + public Material getMaterial() { + return material; + } + + /** + * Set the Material for the Node + * @param material Material + * @see com.droidquest.materials.Material + */ + public void setMaterial(Material material) { + this.material = material; + } + + /** + * Create a clone of this Node + * @return Node clone (as Object) + */ + public Object clone() { + try { + return super.clone(); + } + catch(CloneNotSupportedException cnse) { + System.out.println("Clone not supported!"); + return this; + } + } + + /** + * Set the gCost (cost to the current Node from the origin) + * @return int cost + */ + public int getgCost() { + return gCost; + } + + /** + * Set the gCost (cost to the current Node from the origin) + * @param gCost int cost + */ + public void setgCost(int gCost) { + this.gCost = gCost; + } + + /** + * Retrieves the hCost (heuristic cost) from the current Node to the end Node + * @return int cost + */ + public int gethCost() { + return hCost; + } + + /** + * Sets the hCost (heuristic cost) from the current Node to the end Node + * @param hCost int cost + */ + public void sethCost(int hCost) { + this.hCost = hCost; + } + + /** + * Retrieves the fCost (full cost) for the Node + * @return int cost + */ + public int getfCost() { + return fCost; + } + + /** + * Sets the fCost (full cost) for the Node + * @param fCost int cost + */ + public void setfCost(int fCost) { + this.fCost = fCost; + } +} diff --git a/src/com/droidquest/pathfinder/Pathfinder.java b/src/com/droidquest/pathfinder/Pathfinder.java new file mode 100644 index 0000000..1f621ce --- /dev/null +++ b/src/com/droidquest/pathfinder/Pathfinder.java @@ -0,0 +1,178 @@ +package com.droidquest.pathfinder; + +import com.droidquest.Room; +import com.droidquest.items.Item; + +import java.util.ArrayList; +import java.util.Collections; + +/** + * Finds a path between two places on the map, using the A* algorithm + */ +public class Pathfinder { + private ArrayList> nodeList = new ArrayList>(); + + private ArrayList openList = new ArrayList(); + + private ArrayList closedList = new ArrayList(); + + /** + * Begin a search for a path. Returns an empty ArrayList if no path found. + * @param startX int starting X coordinate (not screen location) + * @param startY int starting Y coordinate (not screen location) + * @param endX int ending X coordinate (not screen location) + * @param endY int ending Y coordinate (not screen location) + * @param player Item player used to verify whether a Node is passable. + * @return ArrayList of Nodes representing the path, or empty list + */ + public ArrayList search(int startX, int startY, int endX, int endY, Item player) { + ArrayList results = new ArrayList(); + if(startY < 0 || startY > nodeList.size() || startX < 0 || startX > nodeList.get(startY).size()) { + System.out.println("Starting node outside of room bounds!"); + return results; + } + + if(endY < 0 || endY > nodeList.size() || endX < 0 || endX > nodeList.get(endY).size()) { + System.out.println("Ending node outside of room bounds!"); + return results; + } + + if(!nodeList.get(startY).get(startX).getMaterial().Passable(player)) { + System.out.println("Starting node isn't passable!"); + return results; + } + + if(!nodeList.get(endY).get(endX).getMaterial().Passable(player)) { + System.out.println("Ending node isn't passable!"); + return results; + } + + Node endNode = nodeList.get(endY).get(endX); + + // Add nodes adjacent to starting node + Node startNode = nodeList.get(startY).get(startX); + startNode.setgCost(0); + startNode.sethCost(manhattanDistance(startNode, endNode)); + startNode.setfCost(startNode.gethCost()); + startNode.parent = null; + openList.add(startNode); + + findLowestCost(results, endNode, player); + + return results; + } + + private void findLowestCost(ArrayList results, Node endNode, Item player) { + + while(openList.size() > 0) { + Node lowestCost = null; + for (Node node : openList) { + if (lowestCost == null || node.getfCost() < lowestCost.getfCost()) { + lowestCost = node; + } + } + + openList.remove(lowestCost); + closedList.add(lowestCost); + + if (lowestCost != null && lowestCost == endNode) { + Node pathNode = lowestCost; + while (pathNode.parent != null) { + results.add(new Node(pathNode.getX() * 28, pathNode.getY() * 32)); + pathNode = pathNode.parent; + } + Collections.reverse(results); + return; + + } + + addSurroundingNodes(lowestCost, endNode, player); + } + + System.out.println("Could not find any nodes to process, no path."); + + } + + + + + private void addSurroundingNodes(Node parent, Node endNode, Item player) { + if(parent == null) { + return; + } + + int x = parent.getX(); + int y = parent.getY(); + for(int deltaY = -1; deltaY <= 1; deltaY++) { + for(int deltaX = -1; deltaX <=1; deltaX++) { + if(deltaX != 0 || deltaY != 0) { + int checkX = x + deltaX; + int checkY = y + deltaY; + if(checkY >= 0 && checkY < nodeList.size() && checkX >= 0 && checkX < nodeList.get(checkY).size() && nodeList.get(checkY).get(checkX).getMaterial().Passable(player)) { + Node currentNode = nodeList.get(checkY).get(checkX); + if(closedList.contains(currentNode)) { + continue; + } + int diagonalCost = 0; + if(deltaX != 0 && deltaY != 0) { + + // Diagonal, check to make sure other squares are passable + if(!nodeList.get(y).get(checkX).getMaterial().Passable(player) || !nodeList.get(checkY).get(x).getMaterial().Passable(player)) { + diagonalCost = 500; // don't make impassible, just really expensive + } + } + if(openList.contains(currentNode)) { + // Set gCost (10 for orthoginal, 14 for diagonal) + int newgCost = parent.getgCost() + ((deltaX == 0 || deltaY == 0) ? 10 : 14) + diagonalCost; + if(newgCost < currentNode.getgCost()) { + currentNode.parent = parent; + currentNode.setgCost(newgCost); + currentNode.setfCost(newgCost + manhattanDistance(currentNode, endNode)); + } + } + else { + openList.add(currentNode); + currentNode.parent = parent; + + // Set gCost (10 for orthoginal, 14 for diagonal) + currentNode.setgCost(parent.getgCost() + ((deltaX == 0 || deltaY == 0) ? 10 : 14) + diagonalCost); + currentNode.sethCost(manhattanDistance(currentNode, endNode)); + currentNode.setfCost(currentNode.getgCost() + currentNode.gethCost()); + } + } + } + } + } + + } + + /** + * Create a new Pathfinder object and initialize with a Room + * @param room Room for path-finding. + * @see com.droidquest.Room + */ + public Pathfinder(Room room) { + // Generate node array for room + generate(room); + + } + + /** + * Generate the Node list for a Room. Generally should use new Pathfinder(room) instead of this method + * @param room Room for path-finding + */ + public void generate(Room room) { + for(int y = 0; y < room.RoomArray.length;y++) { + nodeList.add(new ArrayList()); + for(int x=0;x