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()
/** * 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; }