/** * Written by Josh Lifton 10MAY2000. * Copyright (c) Josh Lifton 2000. **/ import java.awt.*; import java.util.*; public class Boid { /* FIELDS */ private double heading, speed, x, y, maximumSpeed, minimumSpeed, turnAngle, acceleration, sensorRange, collisionRange, deathRange, deathRangeNumber, deathConstant, attackConstant, retreatConstant; private double friendsHeading, friendsSpeed, friendsX, friendsY, friendsNumber; private double enemiesHeading, enemiesSpeed, enemiesX, enemiesY, enemiesNumber; private Color color; private Flock flock; private Boid nearestFriend; // Set only if within the collision range. private Boid nearestEnemy; /* CONSTRUCTORS */ public Boid(double direction, double magnitude, double xPos, double yPos, double maxSpeed, double minSpeed, Color c, double angle, double accel, double sensor, double death, double deathC, double attackC, double retreatC, double collision, Flock gaggle) { setMaximumSpeed(maxSpeed); setMinimumSpeed(minSpeed); setHeading(direction); setSpeed(magnitude); setX(xPos); setY(yPos); setColor(c); setTurnAngle(angle); setAcceleration(accel); setSensorRange(sensor); setDeathRange(death); setDeathConstant(deathC); setAttackConstant(attackC); setRetreatConstant(retreatC); setCollisionRange(collision); setFlock(gaggle); setNearestFriend(null); setNearestEnemy(null); } public Boid(BoidGene b) { setMaximumSpeed(b.getMaximumSpeed()); setMinimumSpeed(b.getMinimumSpeed()); setHeading(b.getHeading()); setSpeed(b.getSpeed()); setX(b.getX()); setY(b.getY()); setColor(b.getColor()); setTurnAngle(b.getTurnAngle()); setAcceleration(b.getAcceleration()); setSensorRange(b.getSensorRange()); setDeathRange(b.getDeathRange()); setDeathConstant(b.getDeathConstant()); setAttackConstant(b.getAttackConstant()); setRetreatConstant(b.getRetreatConstant()); setCollisionRange(b.getCollisionRange()); setFlock(b.getFlock()); setNearestFriend(null); setNearestEnemy(null); } /* METHODS */ public void move() { setX(getX() + getSpeed()*Math.cos(getHeading())); setY(getY() + getSpeed()*Math.sin(getHeading())); } public void slowDown() { setSpeed(getSpeed() - getAcceleration()); } public void speedUp() { setSpeed(getSpeed() + getAcceleration()); } public void turnLeft() { setHeading(getHeading() + getTurnAngle()); } public void turnRight() { setHeading(getHeading() - getTurnAngle()); } public boolean isInRange(Boid b, double range) { return (Math.sqrt((b.getX()-getX())*(b.getX()-getX()) + (b.getY()-getY())*(b.getY()-getY())) < range); } public void senseFlock(Flock f) { Enumeration e = f.elements(); Boid b; double friends = 0.0; double totalFriendsX = 0.0; double totalFriendsY = 0.0; double totalFriendsSpeed = 0.0; double totalFriendsHeading = 0.0; double enemies = 0.0; double totalEnemiesX = 0.0; double totalEnemiesY = 0.0; double totalEnemiesSpeed = 0.0; double totalEnemiesHeading = 0.0; setNearestFriend(null); setNearestEnemy(null); setDeathRangeNumber(0.0); double distanceToFriend = getCollisionRange(); double distanceToEnemy = getSensorRange(); while (e.hasMoreElements()) { b = (Boid) e.nextElement(); if (b != this) { if (isFriend(b)) { if (isInRange(b, distanceToFriend)) { setNearestFriend(b); distanceToFriend = Math.sqrt((b.getX()-getX())*(b.getX()-getX()) + (b.getY()-getY())*(b.getY()-getY())); } if (isInRange(b, getSensorRange())) { friends = friends + 1.0; totalFriendsX = totalFriendsX + b.getX(); totalFriendsY = totalFriendsY + b.getY(); totalFriendsSpeed = totalFriendsSpeed + b.getSpeed(); totalFriendsHeading = totalFriendsHeading + b.getHeading(); } } else { if (isInRange(b, distanceToEnemy)) { setNearestEnemy(b); distanceToEnemy = Math.sqrt((b.getX()-getX())*(b.getX()-getX()) + (b.getY()-getY())*(b.getY()-getY())); } if (isInRange(b, getDeathRange())) { setDeathRangeNumber(getDeathRangeNumber() + 1.0); } if (isInRange(b, getSensorRange())) { enemies = enemies + 1.0; totalEnemiesX = totalEnemiesX + b.getX(); totalEnemiesY = totalEnemiesY + b.getY(); totalEnemiesSpeed = totalEnemiesSpeed + b.getSpeed(); totalEnemiesHeading = totalEnemiesHeading + b.getHeading(); } } } if (friends == 0.0) { setFriendsNumber(0.0); setFriendsHeading(getHeading()); setFriendsSpeed(getSpeed()); setFriendsX(getX()); setFriendsY(getY()); } else { setFriendsNumber(friends); setFriendsHeading(totalFriendsHeading/friends); setFriendsSpeed(totalFriendsSpeed/friends); setFriendsX(totalFriendsX/friends); setFriendsY(totalFriendsY/friends); } if (enemies == 0.0) { setEnemiesNumber(0.0); setEnemiesHeading(getHeading()); setEnemiesSpeed(getSpeed()); setEnemiesX(getX()); setEnemiesY(getY()); } else { setEnemiesNumber(enemies); setEnemiesHeading(totalEnemiesHeading/enemies); setEnemiesSpeed(totalEnemiesSpeed/enemies); setEnemiesX(totalEnemiesX/enemies); setEnemiesY(totalEnemiesY/enemies); } } } /* This is where the boid decides what to do based on what it senses. */ public void decide() { if (isDead()) { getFlock().remove(this); } else { if (getEnemiesNumber() != 0.0) { if ((getFriendsNumber()+1)/getEnemiesNumber() > getAttackConstant()) { attackEnemies(); } else if ((getFriendsNumber()+1)/getEnemiesNumber() < getRetreatConstant()) { retreat(); } } else if (getNearestFriend() != null) { avoidCollision(); } else { hangOutWithFriends(); } } } public void avoidCollision() { Boid n = getNearestFriend(); if (n != null) { turnAway(n.getX(),n.getY()); double angle = relativeAngle(n.getX(),n.getY()); if (Math.cos(n.getHeading() - this.getHeading()) > 0) { // going the same direction if (Math.cos(angle - getHeading()) > 0) { // this boid is behind the other slowDown(); } else { // this boid is in front of the other speedUp(); } } else { // going opposite directions if (Math.cos(angle - getHeading()) > 0) { // head-on collision course slowDown(); } } } } public void hangOutWithFriends() { if (getFriendsNumber() > 0.0) { turnToward(getFriendsX(),getFriendsY()); if (getFriendsSpeed() > getSpeed()) { speedUp(); } else if (getFriendsSpeed() < getSpeed()) { slowDown(); } } else { slowDown(); if (Math.random() < 0.5) { turnLeft(); } else { turnRight(); } } } public void attackEnemies() { turnToward(getEnemiesX(),getEnemiesY()); if (getEnemiesSpeed() > getSpeed()) { speedUp(); } else if (getEnemiesSpeed() < getSpeed()) { slowDown(); } } public void retreat() { turnAway(getEnemiesX(),getEnemiesY()); speedUp(); } public double relativeAngle(double xPos, double yPos) { double dx, dy, r, angle; dx = xPos - getX(); dy = yPos - getY(); r = Math.sqrt(dx*dx + dy*dy); angle = Math.acos(dx/r); if (dy < 0) { angle = 2.0*Math.PI - angle; } return angle; } public void turnAway(double xPos, double yPos) { if ((xPos == getX()) && (yPos == getY())) { return; } double angle = relativeAngle(xPos,yPos); if (Math.abs(angle-getHeading()) < Math.PI) { if (angle-getHeading() > 0) { turnRight(); } else { turnLeft(); } } else { if (angle-getHeading() > 0) { turnLeft(); } else { turnRight(); } } } public void turnToward(double xPos, double yPos) { if ((xPos == getX()) && (yPos == getY())) { return; } double angle = relativeAngle(xPos,yPos); if (Math.abs(angle-getHeading()) < Math.PI) { if (angle-getHeading() > 0) { turnLeft(); } else { turnRight(); } } else { if (angle-getHeading() > 0) { turnRight(); } else { turnLeft(); } } } public boolean isFriend(Boid b) { return getColor().equals(b.getColor()); } public boolean isDead() { return (Math.random() < (1.0 - Math.exp(-getDeathConstant()*getDeathRangeNumber()))); /** * If death constant is 0.231049, then there is a 50% chance * of dying when confronted with three enemies. **/ } /* GET AND SET METHODS */ public double getHeading() {return heading;} public void setHeading(double direction) { heading=direction; if (heading > 2.0*Math.PI) { heading = heading - 2.0*Math.PI; } if (heading < 0) { heading = 2.0*Math.PI + heading; } } public double getSpeed() {return speed;} public void setSpeed(double magnitude) { speed = Math.abs(magnitude); if (speed > getMaximumSpeed()) { speed = getMaximumSpeed(); } if (speed < getMinimumSpeed()) { speed = getMinimumSpeed(); } } public double getX() {return x;} public void setX(double xPos) {x=xPos;} public double getY() {return y;} public void setY(double yPos) {y=yPos;} public double getMaximumSpeed() {return maximumSpeed;} public void setMaximumSpeed(double s) {maximumSpeed = Math.abs(s);} public double getMinimumSpeed() {return minimumSpeed;} public void setMinimumSpeed(double s) {minimumSpeed = Math.abs(s);} public Color getColor() {return color;} public void setColor(Color c) {color = c;} public double getTurnAngle() {return turnAngle;} public void setTurnAngle(double a) { while (a>2.0*Math.PI) { a = a - 2.0*Math.PI; } while (a<0) { a = 2.0*Math.PI + a; } turnAngle = a; } public double getAcceleration() {return acceleration;} public void setAcceleration(double a) {acceleration = Math.abs(a);} public double getSensorRange() {return sensorRange;} public void setSensorRange(double sensor) {sensorRange = Math.abs(sensor);} public double getDeathRange() {return deathRange;} public void setDeathRange(double death) {deathRange = Math.abs(death);} public double getDeathConstant() {return deathConstant;} public void setDeathConstant(double c) {deathConstant = Math.abs(c);} public double getAttackConstant() {return attackConstant;} public void setAttackConstant(double c) {attackConstant = Math.abs(c);} public double getRetreatConstant() {return retreatConstant;} public void setRetreatConstant(double c) {retreatConstant = Math.abs(c);} public double getDeathRangeNumber() {return deathRangeNumber;} public void setDeathRangeNumber(double number) {deathRangeNumber = number;} public double getCollisionRange() {return collisionRange;} public void setCollisionRange(double collision) {collisionRange = Math.abs(collision);} public Flock getFlock() {return flock;} public void setFlock(Flock gaggle) {flock = gaggle;} public double getFriendsHeading() {return friendsHeading;} public void setFriendsHeading(double direction) {friendsHeading = direction;} public double getFriendsSpeed() {return friendsSpeed;} public void setFriendsSpeed(double speed) {friendsSpeed = speed;} public double getFriendsX() {return friendsX;} public void setFriendsX(double xPos) {friendsX = xPos;} public double getFriendsY() {return friendsY;} public void setFriendsY(double yPos) {friendsY = yPos;} public double getFriendsNumber() {return friendsNumber;} public void setFriendsNumber(double number) {friendsNumber = number;} public double getEnemiesHeading() {return enemiesHeading;} public void setEnemiesHeading(double direction) {enemiesHeading = direction;} public double getEnemiesSpeed() {return enemiesSpeed;} public void setEnemiesSpeed(double speed) {enemiesSpeed = speed;} public double getEnemiesX() {return enemiesX;} public void setEnemiesX(double xPos) {enemiesX = xPos;} public double getEnemiesY() {return enemiesY;} public void setEnemiesY(double yPos) {enemiesY = yPos;} public double getEnemiesNumber() {return enemiesNumber;} public void setEnemiesNumber(double number) {enemiesNumber = number;} public Boid getNearestFriend() {return nearestFriend;} public void setNearestFriend(Boid b) {nearestFriend = b;} public Boid getNearestEnemy() {return nearestEnemy;} public void setNearestEnemy(Boid b) {nearestEnemy = b;} }