/** * getBestEvadeDirection returns the largest approximate gap to escape. This gap is approximated * by finding the gaps between all potential collideable objects around the Bucketbot, and * weighting them positively by size and weighting them inversely by both distance and gapsize. * The direction bisecting the largest of these weighted gaps is returned. * * @param visible_distance how far the Bucketbot can see * @return best direction to evade */ public float getBestEvadeDirection(float visible_distance) { alphabetsoup.framework.Map map = SimulationWorldSimpleExample.getSimulationWorld().getMap(); // get visible objects within the distance of the next planned update Collection<Circle> visible_objects = map.getBucketbotsWithinDistance(getX(), getY(), visible_distance); if (getBucket() != null) visible_objects.addAll(map.getBucketsWithinDistance(getX(), getY(), visible_distance)); // if nothing other than bucketbot (and bucket if applicable), keep going in same direction if ((getBucket() == null && visible_objects.size() <= 1) || (getBucket() != null && visible_objects.size() <= 2)) return getDirection(); List<CollideableObject> objects = new ArrayList<CollideableObject>(); // get object distances and directions for (Circle c : visible_objects) { if (c == this || c == getBucket()) continue; float object_direction = (float) Math.atan2(c.getY() - getY(), c.getX() - getX()); objects.add( new CollideableObject( 2 * c.getRadius(), getDistance(c.getX(), c.getY()), object_direction)); } // add 4 walls if (getX() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt(visible_distance * visible_distance - getX() * getX())), getX(), (float) Math.PI)); if (map.getWidth() - getX() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt( visible_distance * visible_distance - (map.getWidth() - getX()) * (map.getWidth() - getX()))), map.getWidth() - getX(), 0.0f)); if (getY() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt(visible_distance * visible_distance - getY() * getY())), getY(), (float) (3 * Math.PI / 2))); if (map.getHeight() - getY() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt( visible_distance * visible_distance - (map.getHeight() - getY()) * (map.getHeight() - getY()))), map.getHeight() - getY(), (float) Math.PI / 2)); // if no objects, then nothing to collide with if (objects.size() == 0) return getDirection(); // sort objects by direction, so between each two objects is the smallest real gaps Collections.sort(objects, objects.get(0)); // add beginning one again objects.add( new CollideableObject( objects.get(0).size, objects.get(0).distance, (float) (objects.get(0).direction + 2 * Math.PI))); // find maximal gap // start with first gap float evade_direction = (objects.get(0).direction + objects.get(1).direction) / 2; double weight = ((objects.get(0).size + objects.get(1).size) / 2) / (((objects.get(0).distance + objects.get(1).distance) / 2) * (objects.get(1).direction - objects.get(0).direction)); // for all after the first gap for (int i = 1; i < objects.size() - 1; i++) { // get weight (a heuristic for speed) float w = ((objects.get(i).size + objects.get(i + 1).size) / 2) / (((objects.get(i).distance + objects.get(i + 1).distance) / 2) * (objects.get(i + 1).direction - objects.get(i).direction)); // if better direction, then choose direction bisecting the two objects if (w < weight) { weight = w; evade_direction = (objects.get(i).direction + objects.get(i + 1).direction) / 2; } } return evade_direction; }
public void act(BucketbotBase self) { alphabetsoup.framework.Map map = SimulationWorldSimpleExample.getSimulationWorld().getMap(); float cur_speed = getSpeed(); // called once for code efficiency (since getSpeed does a sqrt) // find distance to be covered before next planned update getNextEventTime(curTime); double time_interval = Math.max(getMinUntil() - curTime, 0.001); float distance_to_be_covered = (float) (Math.max(cur_speed, getTargetSpeed()) * time_interval); distance_to_be_covered += 2 * getRadius(); // count its radius and the radius of another object // make sure see at least a minimal distance regardless of speed float min_visible_distance = 3 * getRadius(); distance_to_be_covered = Math.max(distance_to_be_covered, min_visible_distance); // get visible objects within the distance of the next planned update Collection<Circle> visible_objects = map.getBucketbotsWithinDistance(getX(), getY(), distance_to_be_covered); if (self.getBucket() != null) visible_objects.addAll( map.getBucketsWithinDistance(getX(), getY(), distance_to_be_covered)); // don't want to do anything yet, // unless something is now visible that wasn't before, // a collision occured (speed == 0) // timer is ready to do something else if (((getBucket() == null && visible_objects.size() == 1) || (getBucket() != null && visible_objects.size() == 2)) && cur_speed > 0.0f && curTime < cruiseUntil) return; // if something other than bucketbot (and bucket if applicable) is near, evade if ((getBucket() == null && visible_objects.size() > 1) || (getBucket() != null && visible_objects.size() > 2)) { // find closest object float min_dist2 = Float.POSITIVE_INFINITY; // minimum distance squared for (Circle c : visible_objects) { if (c == self || c == self.getBucket()) continue; // see if infront of bucketbot float object_direction = (float) Math.atan2(c.getY() - getY(), c.getX() - getX()); float relative_direction = angleDifference(object_direction, getDirection()); if (Math.abs(relative_direction) > Math.PI / 4 + 0.5) // just beyond 1/4 circle, to make sure they don't get stuck continue; // see if it's closer than any other float dist2 = (getX() - c.getX()) * (getX() - c.getX()) + (getY() - c.getY()) * (getY() - c.getY()); if (dist2 < min_dist2) min_dist2 = dist2; } // if anything is closer than this constant value, then evade... if (min_dist2 < min_visible_distance * min_visible_distance) { float new_direction = getBestEvadeDirection(min_visible_distance); if (getDirection() != new_direction) setDirection(new_direction); setTargetSpeed(getMaxVelocity()); cruiseUntil = curTime + 0.25f; frustration = 0.0f; stateQueue.add(0, bucketbotEvade); return; } } // if trying to move, but can't try evading if (cur_speed > 0.0f) { stuckCount = 0; } else { // not moving... stuckCount++; if (stuckCount > 3) { frustration = 0.0f; stateQueue.add(0, bucketbotEvade); return; } } // find distance to goal float goal_distance = getDistance(moveToX, moveToY); // make sure tolerance is less than the map's tolerance/3 to make sure that if it sets a // bucket down, // the next one bucketbot will be able to pick it up (and could also be tolerance/3 away) // better fudge factor than tolerance / 2 (which is the minimum that will work) float tolerance = map.getTolerance() / 3; // if close enough to goal and stopped moving, then do next action if (goal_distance < tolerance && cur_speed == 0.0f) { frustration = 0.0f; stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } // if not facing the right way (within tolerance), or heading outside of the map, turn so it // is a good direction double projected_x = getX() + goal_distance * Math.cos(getDirection()); double projected_y = getY() + goal_distance * Math.sin(getDirection()); if ((projected_x - moveToX) * (projected_x - moveToX) + (projected_y - moveToY) * (projected_y - moveToY) >= tolerance * tolerance || projected_x + getRadius() > map.getWidth() || projected_x - getRadius() < 0.0 || projected_y + getRadius() > map.getHeight() || projected_y - getRadius() < 0.0) { setDirection((float) Math.atan2(moveToY - getY(), moveToX - getX())); if (cur_speed > 0) setTargetSpeed(0.0f); cruiseUntil = getAccelerateUntil(); return; } // going the right direction -now figure out speed // if too close to stop, then stop as fast as possible float decel_time = cur_speed / getMaxAcceleration(); float decel_distance = getMaxAcceleration() / 2 * decel_time * decel_time; if (cur_speed > 0.0f && decel_distance > goal_distance) { setTargetSpeed(0.0f); cruiseUntil = getAccelerateUntil(); frustration = (frustration + 1.0f) / 2; return; } // get new velocity based on frustration float cur_vel = getMaxVelocity() * Math.max(1.0f - frustration, 0.0078125f); setTargetSpeed(cur_vel); // see if have room to accelerate to full speed and still decelerate float accel_time = getTargetSpeedDifference() / getMaxAcceleration(); float accel_distance = cur_speed * accel_time + getMaxAcceleration() / 2 * accel_time * accel_time; decel_time = cur_vel / getMaxAcceleration(); decel_distance = getMaxAcceleration() / 2 * accel_time * accel_time; // if enough room to fully accelerate, do so if (accel_distance + decel_distance <= goal_distance) cruiseUntil = getAccelerateUntil(); else { // don't have time to fully accelerate // having this code spread out with sub-steps dramatically helps the java compiler have // better performance double cos_dir = Math.cos(getDirection()); double sin_dir = Math.sin(getDirection()); double x_accel = getMaxAcceleration() * cos_dir; double y_accel = getMaxAcceleration() * sin_dir; if (Math.abs(x_accel) > Math.abs(y_accel)) { double x_goal = goal_distance * cos_dir; cruiseUntil = curTime + sqrt2 * (Math.sqrt(2 * x_accel * x_goal + getXVelocity() * getXVelocity()) - sqrt2 * getXVelocity()) / (2 * x_accel); } else { double y_goal = goal_distance * sin_dir; cruiseUntil = curTime + sqrt2 * (Math.sqrt(2 * y_accel * y_goal + getYVelocity() * getYVelocity()) - sqrt2 * getYVelocity()) / (2 * y_accel); } } } // act()
/** * BucketbotDriver implements a functioning Bucketbot that can complete tasks, but only evades other * buckets and bucketbots by trying to go directly around. * * @author Chris Hazard */ public class BucketbotExample extends BucketbotBase { float frustration = 0.0f; // 0.0->1.0 for maximal frustration int stuckCount = 0; // number of updates not able to move at all static final double sqrt2 = Math.sqrt(2.0); public BucketbotExample( float bucketbot_radius, float bucket_pickup_setdown_time, float bucketbot_max_acceleration, float bucketbot_max_velocity, float collision_penalty_time) { super( bucketbot_radius, bucket_pickup_setdown_time, bucketbot_max_acceleration, bucketbot_max_velocity, collision_penalty_time); } /* (non-Javadoc) * @see alphabetsoup.framework.Bucketbot#assignTask(alphabetsoup.base.BucketbotTask) */ // public void assignTask(Object tt) { public <T> void assignTask(T tt) { BucketbotTask t = (BucketbotTask) tt; setCurrentTask(t); stateQueue.clear(); if (t == null) return; switch (t.getTaskType()) { case NONE: break; case CANCEL: break; case MOVE: stateQueue.add(new BucketbotMove(t.getDestinationX(), t.getDestinationY())); break; case STORE_BUCKET: if (t.getBucket() != getBucket()) { SimulationWorldSimpleExample.getSimulationWorld().bucketbotManager.taskAborted(this, t); return; } // if don't have bucket requested to store, then go get it if (getBucket() == null) { stateQueue.add(new BucketbotMove(t.getBucket().getX(), t.getBucket().getY())); stateQueue.add(new BucketbotPickupBucket(t.getBucket())); } stateQueue.add(new BucketbotMove(t.getDestinationX(), t.getDestinationY())); stateQueue.add(new BucketbotSetdownBucket()); break; case TAKE_BUCKET_TO_LETTER_STATION: if (t.getBucket() != getBucket()) { stateQueue.add(new BucketbotMove(t.getBucket().getX(), t.getBucket().getY())); stateQueue.add(new BucketbotPickupBucket(t.getBucket())); } stateQueue.add( new BucketbotMove( t.getLetterStation().getX(), t.getLetterStation().getY() + t.getLetterStation().getRadius() + getRadius())); stateQueue.add(new BucketbotMove(t.getLetterStation().getX(), t.getLetterStation().getY())); stateQueue.add(new BucketbotGetLetter(t.getLetter(), t.getLetterStation())); stateQueue.add( new BucketbotMove( t.getLetterStation().getX(), t.getLetterStation().getY() - t.getLetterStation().getRadius() - getRadius())); /* stateQueue.add(new BucketbotMove(t.getLetterStation().getX() + t.getLetterStation().getRadius() + 2*getRadius(), t.getLetterStation().getY() + t.getLetterStation().getRadius() + 2*getRadius() )); stateQueue.add(new BucketbotMove(t.getLetterStation().getX(), t.getLetterStation().getY() + t.getLetterStation().getRadius() + 2*getRadius() )); stateQueue.add(new BucketbotMove(t.getLetterStation().getX(), t.getLetterStation().getY())); stateQueue.add(new BucketbotGetLetter(t.getLetter(), t.getLetterStation())); */ break; case TAKE_BUCKET_TO_WORD_STATION: if (t.getBucket() != getBucket()) { stateQueue.add(new BucketbotMove(t.getBucket().getX(), t.getBucket().getY())); stateQueue.add(new BucketbotPickupBucket(t.getBucket())); } stateQueue.add( new BucketbotMove( t.getWordStation().getX(), t.getWordStation().getY() + t.getWordStation().getRadius() + getRadius())); stateQueue.add(new BucketbotMove(t.getWordStation().getX(), t.getWordStation().getY())); stateQueue.add(new BucketbotPutLetter(t.getLetter(), t.getWordStation())); stateQueue.add( new BucketbotMove( t.getWordStation().getX(), t.getWordStation().getY() - t.getWordStation().getRadius() - getRadius())); /* stateQueue.add(new BucketbotMove(t.getWordStation().getX() - t.getWordStation().getRadius() - 2*getRadius(), t.getWordStation().getY() + t.getWordStation().getRadius() + 2*getRadius() )); stateQueue.add(new BucketbotMove(t.getWordStation().getX(), t.getWordStation().getY() + t.getWordStation().getRadius() + 2*getRadius() )); stateQueue.add(new BucketbotMove(t.getWordStation().getX(), t.getWordStation().getY())); stateQueue.add(new BucketbotPutLetter(t.getLetter(), t.getWordStation())); */ break; } } /* (non-Javadoc) * @see alphabetsoup.framework.Bucketbot#idle() */ public void idle() { SimulationWorldSimpleExample.getSimulationWorld() .bucketbotManager .taskComplete(this, getCurrentTask()); SimulationWorldSimpleExample.getSimulationWorld().bucketbotManager.requestNewTask(this); if (stateQueue.size() > 0) stateQueue.get(0).act(this); } public class BucketbotPickupBucket implements BucketbotState { public String getStateName() { return "PickupBucket"; } Bucket bucket; public BucketbotPickupBucket(Bucket b) { bucket = b; } public void act(BucketbotBase self) { // act based on whether bucket was picked up if (pickupBucket(bucket)) { stateQueue.remove(0); } else { // failed to pick up bucket SimulationWorldSimpleExample.getSimulationWorld() .bucketbotManager .taskAborted(self, getCurrentTask()); stateQueue.clear(); } } } public class BucketbotSetdownBucket implements BucketbotState { public String getStateName() { return "SetdownBucket"; } public BucketbotSetdownBucket() {} public void act(BucketbotBase self) { if (setdownBucket()) { stateQueue.remove(0); } else { // failed to set down bucket SimulationWorldSimpleExample.getSimulationWorld() .bucketbotManager .taskAborted(self, getCurrentTask()); stateQueue.clear(); } } } public class BucketbotGetLetter implements BucketbotState { public String getStateName() { return "GetLetter"; } Letter letter; LetterStation station; boolean requested_letters = false; public BucketbotGetLetter(Letter l, LetterStation s) { letter = l; station = s; } public void act(BucketbotBase self) { // make sure bucketbot is in vacinity of station, otherwise go there if (self.getDistance(station.getX(), station.getY()) > SimulationWorldSimpleExample.getSimulationWorld().getMap().getTolerance()) { stateQueue.add(0, new BucketbotMove(station.getX(), station.getY())); return; } // if it's the first time, request the letters if (!requested_letters) { station.requestLetter(self, letter); requested_letters = true; } if (self.getBucket() == null) { // something wrong happened... don't have a bucket! SimulationWorldSimpleExample.getSimulationWorld() .bucketbotManager .taskAborted(self, getCurrentTask()); stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } // see if all letter has been deposited in the bucket if (self.getBucket().containsLetter(letter)) { stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } } } public class BucketbotPutLetter implements BucketbotState { public String getStateName() { return "PutLetter"; } Letter letter; WordStation station; boolean requested_letters_take = false; public BucketbotPutLetter(Letter l, WordStation s) { letter = l; station = s; } public void act(BucketbotBase self) { // make sure bucketbot is in vacinity of station, otherwise go there if (self.getDistance(station.getX(), station.getY()) > SimulationWorldSimpleExample.getSimulationWorld().getMap().getTolerance()) { stateQueue.add(0, new BucketbotMove(station.getX(), station.getY())); return; } // if it's the first time, request the letters be taken if (!requested_letters_take) { station.requestLetterTake(self, letter, null); requested_letters_take = true; } if (self.getBucket() == null) { // something wrong happened... don't have a bucket! SimulationWorldSimpleExample.getSimulationWorld() .bucketbotManager .taskAborted(self, getCurrentTask()); stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } // see if letter has been taken from the bucket if (!self.getBucket().containsLetter(letter)) { stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } } } /** * CollideableObject is used for sorting collideable objects in order of direction (so they are * put around in a circle) * * @author Chris Hazard */ private static class CollideableObject implements Comparator<CollideableObject> { public CollideableObject(float s, float dist, float dir) { size = s; distance = dist; direction = dir; } float size, distance, direction; public int compare(CollideableObject o1, CollideableObject o2) { float dir1 = ((CollideableObject) o1).direction; float dir2 = ((CollideableObject) o2).direction; if (dir1 > dir2) return 1; if (dir2 > dir1) return -1; return 0; } } /** * getBestEvadeDirection returns the largest approximate gap to escape. This gap is approximated * by finding the gaps between all potential collideable objects around the Bucketbot, and * weighting them positively by size and weighting them inversely by both distance and gapsize. * The direction bisecting the largest of these weighted gaps is returned. * * @param visible_distance how far the Bucketbot can see * @return best direction to evade */ public float getBestEvadeDirection(float visible_distance) { alphabetsoup.framework.Map map = SimulationWorldSimpleExample.getSimulationWorld().getMap(); // get visible objects within the distance of the next planned update Collection<Circle> visible_objects = map.getBucketbotsWithinDistance(getX(), getY(), visible_distance); if (getBucket() != null) visible_objects.addAll(map.getBucketsWithinDistance(getX(), getY(), visible_distance)); // if nothing other than bucketbot (and bucket if applicable), keep going in same direction if ((getBucket() == null && visible_objects.size() <= 1) || (getBucket() != null && visible_objects.size() <= 2)) return getDirection(); List<CollideableObject> objects = new ArrayList<CollideableObject>(); // get object distances and directions for (Circle c : visible_objects) { if (c == this || c == getBucket()) continue; float object_direction = (float) Math.atan2(c.getY() - getY(), c.getX() - getX()); objects.add( new CollideableObject( 2 * c.getRadius(), getDistance(c.getX(), c.getY()), object_direction)); } // add 4 walls if (getX() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt(visible_distance * visible_distance - getX() * getX())), getX(), (float) Math.PI)); if (map.getWidth() - getX() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt( visible_distance * visible_distance - (map.getWidth() - getX()) * (map.getWidth() - getX()))), map.getWidth() - getX(), 0.0f)); if (getY() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt(visible_distance * visible_distance - getY() * getY())), getY(), (float) (3 * Math.PI / 2))); if (map.getHeight() - getY() < visible_distance) objects.add( new CollideableObject( (float) (2 * Math.sqrt( visible_distance * visible_distance - (map.getHeight() - getY()) * (map.getHeight() - getY()))), map.getHeight() - getY(), (float) Math.PI / 2)); // if no objects, then nothing to collide with if (objects.size() == 0) return getDirection(); // sort objects by direction, so between each two objects is the smallest real gaps Collections.sort(objects, objects.get(0)); // add beginning one again objects.add( new CollideableObject( objects.get(0).size, objects.get(0).distance, (float) (objects.get(0).direction + 2 * Math.PI))); // find maximal gap // start with first gap float evade_direction = (objects.get(0).direction + objects.get(1).direction) / 2; double weight = ((objects.get(0).size + objects.get(1).size) / 2) / (((objects.get(0).distance + objects.get(1).distance) / 2) * (objects.get(1).direction - objects.get(0).direction)); // for all after the first gap for (int i = 1; i < objects.size() - 1; i++) { // get weight (a heuristic for speed) float w = ((objects.get(i).size + objects.get(i + 1).size) / 2) / (((objects.get(i).distance + objects.get(i + 1).distance) / 2) * (objects.get(i + 1).direction - objects.get(i).direction)); // if better direction, then choose direction bisecting the two objects if (w < weight) { weight = w; evade_direction = (objects.get(i).direction + objects.get(i + 1).direction) / 2; } } return evade_direction; } public class BucketbotEvade implements BucketbotState { public String getStateName() { return "Evade"; } public void act(BucketbotBase self) { setDrawBolded(true); if (curTime < cruiseUntil) return; MersenneTwisterFast rand = SimulationWorld.rand; // if doing something else (stateQueue isn't empty), are trying to move to a new location, // but there's another bucketbot at that location, then sit and wait most of the time if (stateQueue.size() > 1 && stateQueue.get(1).getClass() == BucketbotMove.class && !SimulationWorldSimpleExample.getSimulationWorld() .map .isBucketbotMoveValid( self, ((BucketbotMove) stateQueue.get(1)).moveToX, ((BucketbotMove) stateQueue.get(1)).moveToY)) { // usually sit and wait if (rand.nextFloat() < .7f) return; } setTargetSpeed(getMaxVelocity()); float min_visible_distance = 3 * getRadius(); float new_direction = getBestEvadeDirection(min_visible_distance); if (getDirection() != new_direction) setDirection(new_direction); if (rand.nextFloat() < .5f) { stateQueue.remove(0); setDrawBolded(false); if (stateQueue.size() > 0) stateQueue.get(0).act(self); } } } BucketbotEvade bucketbotEvade = this.new BucketbotEvade(); public class BucketbotMove implements BucketbotState { public String getStateName() { return "Move"; } public float moveToX, moveToY; public BucketbotMove(float x, float y) { moveToX = x; moveToY = y; } public void act(BucketbotBase self) { alphabetsoup.framework.Map map = SimulationWorldSimpleExample.getSimulationWorld().getMap(); float cur_speed = getSpeed(); // called once for code efficiency (since getSpeed does a sqrt) // find distance to be covered before next planned update getNextEventTime(curTime); double time_interval = Math.max(getMinUntil() - curTime, 0.001); float distance_to_be_covered = (float) (Math.max(cur_speed, getTargetSpeed()) * time_interval); distance_to_be_covered += 2 * getRadius(); // count its radius and the radius of another object // make sure see at least a minimal distance regardless of speed float min_visible_distance = 3 * getRadius(); distance_to_be_covered = Math.max(distance_to_be_covered, min_visible_distance); // get visible objects within the distance of the next planned update Collection<Circle> visible_objects = map.getBucketbotsWithinDistance(getX(), getY(), distance_to_be_covered); if (self.getBucket() != null) visible_objects.addAll( map.getBucketsWithinDistance(getX(), getY(), distance_to_be_covered)); // don't want to do anything yet, // unless something is now visible that wasn't before, // a collision occured (speed == 0) // timer is ready to do something else if (((getBucket() == null && visible_objects.size() == 1) || (getBucket() != null && visible_objects.size() == 2)) && cur_speed > 0.0f && curTime < cruiseUntil) return; // if something other than bucketbot (and bucket if applicable) is near, evade if ((getBucket() == null && visible_objects.size() > 1) || (getBucket() != null && visible_objects.size() > 2)) { // find closest object float min_dist2 = Float.POSITIVE_INFINITY; // minimum distance squared for (Circle c : visible_objects) { if (c == self || c == self.getBucket()) continue; // see if infront of bucketbot float object_direction = (float) Math.atan2(c.getY() - getY(), c.getX() - getX()); float relative_direction = angleDifference(object_direction, getDirection()); if (Math.abs(relative_direction) > Math.PI / 4 + 0.5) // just beyond 1/4 circle, to make sure they don't get stuck continue; // see if it's closer than any other float dist2 = (getX() - c.getX()) * (getX() - c.getX()) + (getY() - c.getY()) * (getY() - c.getY()); if (dist2 < min_dist2) min_dist2 = dist2; } // if anything is closer than this constant value, then evade... if (min_dist2 < min_visible_distance * min_visible_distance) { float new_direction = getBestEvadeDirection(min_visible_distance); if (getDirection() != new_direction) setDirection(new_direction); setTargetSpeed(getMaxVelocity()); cruiseUntil = curTime + 0.25f; frustration = 0.0f; stateQueue.add(0, bucketbotEvade); return; } } // if trying to move, but can't try evading if (cur_speed > 0.0f) { stuckCount = 0; } else { // not moving... stuckCount++; if (stuckCount > 3) { frustration = 0.0f; stateQueue.add(0, bucketbotEvade); return; } } // find distance to goal float goal_distance = getDistance(moveToX, moveToY); // make sure tolerance is less than the map's tolerance/3 to make sure that if it sets a // bucket down, // the next one bucketbot will be able to pick it up (and could also be tolerance/3 away) // better fudge factor than tolerance / 2 (which is the minimum that will work) float tolerance = map.getTolerance() / 3; // if close enough to goal and stopped moving, then do next action if (goal_distance < tolerance && cur_speed == 0.0f) { frustration = 0.0f; stateQueue.remove(0); if (stateQueue.size() > 0) stateQueue.get(0).act(self); return; } // if not facing the right way (within tolerance), or heading outside of the map, turn so it // is a good direction double projected_x = getX() + goal_distance * Math.cos(getDirection()); double projected_y = getY() + goal_distance * Math.sin(getDirection()); if ((projected_x - moveToX) * (projected_x - moveToX) + (projected_y - moveToY) * (projected_y - moveToY) >= tolerance * tolerance || projected_x + getRadius() > map.getWidth() || projected_x - getRadius() < 0.0 || projected_y + getRadius() > map.getHeight() || projected_y - getRadius() < 0.0) { setDirection((float) Math.atan2(moveToY - getY(), moveToX - getX())); if (cur_speed > 0) setTargetSpeed(0.0f); cruiseUntil = getAccelerateUntil(); return; } // going the right direction -now figure out speed // if too close to stop, then stop as fast as possible float decel_time = cur_speed / getMaxAcceleration(); float decel_distance = getMaxAcceleration() / 2 * decel_time * decel_time; if (cur_speed > 0.0f && decel_distance > goal_distance) { setTargetSpeed(0.0f); cruiseUntil = getAccelerateUntil(); frustration = (frustration + 1.0f) / 2; return; } // get new velocity based on frustration float cur_vel = getMaxVelocity() * Math.max(1.0f - frustration, 0.0078125f); setTargetSpeed(cur_vel); // see if have room to accelerate to full speed and still decelerate float accel_time = getTargetSpeedDifference() / getMaxAcceleration(); float accel_distance = cur_speed * accel_time + getMaxAcceleration() / 2 * accel_time * accel_time; decel_time = cur_vel / getMaxAcceleration(); decel_distance = getMaxAcceleration() / 2 * accel_time * accel_time; // if enough room to fully accelerate, do so if (accel_distance + decel_distance <= goal_distance) cruiseUntil = getAccelerateUntil(); else { // don't have time to fully accelerate // having this code spread out with sub-steps dramatically helps the java compiler have // better performance double cos_dir = Math.cos(getDirection()); double sin_dir = Math.sin(getDirection()); double x_accel = getMaxAcceleration() * cos_dir; double y_accel = getMaxAcceleration() * sin_dir; if (Math.abs(x_accel) > Math.abs(y_accel)) { double x_goal = goal_distance * cos_dir; cruiseUntil = curTime + sqrt2 * (Math.sqrt(2 * x_accel * x_goal + getXVelocity() * getXVelocity()) - sqrt2 * getXVelocity()) / (2 * x_accel); } else { double y_goal = goal_distance * sin_dir; cruiseUntil = curTime + sqrt2 * (Math.sqrt(2 * y_accel * y_goal + getYVelocity() * getYVelocity()) - sqrt2 * getYVelocity()) / (2 * y_accel); } } } // act() } // class BucketbotMove }