Skip to content

Commit

Permalink
Pheromone drops, test for utils
Browse files Browse the repository at this point in the history
  • Loading branch information
hasnainroopawalla committed Aug 12, 2023
1 parent 045d050 commit f6ecd94
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 12 deletions.
16 changes: 15 additions & 1 deletion src/ant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { FoodItem } from "./food-item";
import { World } from "./world";
import { Colony } from "./colony";
import { config } from "./config";
import { IPheromoneType, Pheromone } from "./pheromone";
import { distance } from "./utils";

export enum IAntState {
ReturningHome,
Expand All @@ -28,6 +30,8 @@ export class Ant {
this.colony.position.x,
this.colony.position.y
);
// TODO: Move IPheromoneType to Pheromone
// this.world.depositPheromone(this.position.copy(), IPheromoneType.Wander);
this.wanderAngle = 0;
this.angle = p5i.random(p5i.TWO_PI);
this.velocity = p5m.Vector.fromAngle(this.angle);
Expand All @@ -50,12 +54,15 @@ export class Ant {
}

private handleEdgeCollision() {
// left / right
if (this.position.x > p5i.windowWidth - 10 || this.position.x < 10) {
this.velocity.x *= -1;
}
// top / bottom
if (this.position.y > p5i.windowHeight - 10 || this.position.y < 10) {
this.velocity.y *= -1;
}
// TODO: ants should not be rendered over colonies
}

private handleWandering() {
Expand Down Expand Up @@ -96,7 +103,6 @@ export class Ant {
}

private handleReturningHome() {
// TODO: ants should not be rendered over colonies
// check if food item is delivered to colony
if (this.colony.collide(this.position)) {
this.targetFoodItem.delivered();
Expand All @@ -109,6 +115,13 @@ export class Ant {
this.applyForce(approachColony);
}

private handlePheromoneDeposit() {
this.world.depositPheromone(
this.position.copy(),
this.isSearchingForFood() ? IPheromoneType.Wander : IPheromoneType.Food
);
}

public searchingForFood() {
this.state = IAntState.SearchingForFood;
}
Expand All @@ -135,6 +148,7 @@ export class Ant {
public update() {
this.handleEdgeCollision();
this.handleWandering();
this.handlePheromoneDeposit();

this.isSearchingForFood() && this.handleSearchingForFood();
this.isReturningHome() && this.handleReturningHome();
Expand Down
15 changes: 14 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const config = {
ant: {
wanderStrength: 1,
maxSpeed: 3,
steeringLimit: 0.4,
steeringLimit: 0.7,
size: 2,
color: "#000000",
strokeWeight: 2,
Expand All @@ -34,4 +34,17 @@ export const config = {
strokeWeight: 1,
},
},
pheromone: {
size: 3,
strokeWeight: 0,
distanceBetween: 10,
show: true,
evaporationRate: 0.007,
wander: {
color: "#1AA7EC",
},
food: {
color: "#D21F3C",
},
},
};
31 changes: 31 additions & 0 deletions src/pheromone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { config } from "./config";
import { p5i } from "./sketch";

export enum IPheromoneType {
Wander,
Food,
}

export class Pheromone {
position: p5.Vector;
type: IPheromoneType;
strength: number;

constructor(position: p5.Vector, type: IPheromoneType) {
this.position = position;
this.type = type;
this.strength = 1;
}

public evaporate() {
this.strength -= config.pheromone.evaporationRate;
}

public render() {
p5i.push();
p5i.strokeWeight(config.pheromone.strokeWeight);
p5i.fill(255, 0, 0, this.strength * 100);
p5i.circle(this.position.x, this.position.y, config.pheromone.size);
p5i.pop();
}
}
2 changes: 1 addition & 1 deletion src/sketch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { World } from "./world";
import { config } from "./config";

let world: World;
const numAnts: number = 700;
const numAnts: number = 10;

const sketch = (p: p5) => {
p.setup = () => {
Expand Down
17 changes: 9 additions & 8 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
export const distance = (position1: p5.Vector, position2: p5.Vector): number =>
// Euclidean distance equivalent to p5.dist(x1, y1, x2, y2)
Math.sqrt(
Math.pow(position1.x - position2.x, 2) +
Math.pow(position1.y - position2.y, 2)
);

export const circleCollision = (
candidatePosition: p5.Vector,
circlePosition: p5.Vector,
circleDiameter: number // diameter
): boolean =>
// Euclidean distance equivalent to p5.dist(x1, y1, x2, y2)
Math.sqrt(
Math.pow(candidatePosition.x - circlePosition.x, 2) +
Math.pow(candidatePosition.y - circlePosition.y, 2)
) <=
circleDiameter / 2;
circleDiameter: number
): boolean => distance(candidatePosition, circlePosition) <= circleDiameter / 2;
31 changes: 31 additions & 0 deletions src/world.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import { Ant } from "./ant";
import { FoodItem } from "./food-item";
import { Colony } from "./colony";
import { config } from "./config";
import { IPheromoneType, Pheromone } from "./pheromone";
import { distance } from "./utils";

export class World {
ants: Ant[];
foodItems: FoodItem[];
colonies: Colony[];
pheromones: Pheromone[];

constructor() {
this.ants = [];
this.foodItems = [];
this.colonies = [new Colony()];
this.pheromones = [];
}

public createAnt() {
Expand All @@ -34,6 +38,21 @@ export class World {
}
}

private shouldPheromoneBeDeposited(position: p5.Vector) {
if (this.pheromones.length === 0) {
return true;
}
return (
distance(position, this.pheromones.at(-1).position) >
config.pheromone.distanceBetween
);
}

public depositPheromone(position: p5.Vector, type: IPheromoneType) {
this.shouldPheromoneBeDeposited(position) &&
this.pheromones.push(new Pheromone(position, type));
}

// TODO: this method should limit the perception to only in FRONT of the ant
public getFoodItemInPerceptionRange(
antPosition: p5m.Vector,
Expand All @@ -55,6 +74,17 @@ export class World {
}
}

private renderPheromones() {
for (let i = 0; i < this.pheromones.length; i++) {
const pheromone = this.pheromones[i];
pheromone.evaporate();
if (pheromone.strength <= 0) {
this.pheromones.splice(i, 1);
}
pheromone.render();
}
}

public render() {
p5i.background(config.world.background);
this.colonies.map((colony) => {
Expand All @@ -67,5 +97,6 @@ export class World {
this.foodItems.map((food) => {
food.render();
});
config.pheromone.show && this.renderPheromones();
}
}
16 changes: 15 additions & 1 deletion tests/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import * as p5m from "p5";
import { circleCollision } from "../src/utils";
import { distance, circleCollision } from "../src/utils";

let circlePosition: p5m.Vector;
let circleDiameter: number;

describe("distance", () => {
test.only("should return the Euclidean distance between 2 points in a 2D space", () => {
expect(distance(new p5m.Vector(2, 3), new p5m.Vector(2, 7))).toBe(4);
});
test.only("should return 0 if both points are the same", () => {
expect(distance(new p5m.Vector(2, 3), new p5m.Vector(2, 3))).toBe(0);
});
test.only("should return the Euclidean distance between 2 points in a 2D space float", () => {
expect(distance(new p5m.Vector(3, 5), new p5m.Vector(-2, 4))).toBeCloseTo(
5.099
);
});
});

describe("circleCollision", () => {
beforeEach(() => {
circlePosition = new p5m.Vector(10, 10);
Expand Down

0 comments on commit f6ecd94

Please sign in to comment.