Skip to content

Commit

Permalink
fix: flocking algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
tolstenko committed Aug 26, 2024
1 parent fc672c3 commit f4a7a77
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 148 deletions.
2 changes: 2 additions & 0 deletions docs/algorithms/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Students compare and contrast a variety of data structures. Students compare alg
### Textbook

- Grokking Algorithms, Aditya Bhargava, Manning Publications, 2016. ISBN 978-1617292231
- [Champ link](https://library.champlain.edu/record=b2471451~S1)
- [Amazon](https://a.co/d/1g39FBY)

## Student-centered Learning Outcomes

Expand Down
7 changes: 7 additions & 0 deletions docs/artificialintelligence/00-introduction/assignments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Week 01 - Introduction to Artificial Intelligence Assignments

1. [Read the Syllabus](README.md);
2. [Read Notes on plagiarism](blog/posts/NotesOnSubmissions/NotesOnSubmissions.md);
3. [FERPA Consent Form](blog/posts/FerpaCompliance/FerpaCompliance.md);
4. [Quiz 01](quiz/quiz01/quiz01.md);
5. [Flocking Simulation](../assignments/flocking/README.md);
2 changes: 1 addition & 1 deletion docs/artificialintelligence/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ add_subdirectory(assignments/flocking)
add_subdirectory(assignments/maze)
add_subdirectory(assignments/life)
add_subdirectory(assignments/rng)
add_subdirectory(assignments/catchthecat)
#add_subdirectory(assignments/catchthecat)
44 changes: 36 additions & 8 deletions docs/artificialintelligence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,43 @@ Upon completion of the Advanced AI for Games, students should be able to:
- **Integrate** advanced AI seamlessly into game systems for cohesive environments;
- **Consider** societal impact and consequences of AI applications in gaming;

## Schedule for Spring 2024

!!! warning

This is a work in progress, and the schedule is subject to change. Every change will be communicated in class. Use the github repo as the source of truth for the schedule and materials. The materials provided in canvas are just a copy for archiving purposes and might be outdated.

## Schedule for Fall 2024

College dates for the Fall 2024 semester:

| Event | Date |
|-------------------------------------------|-------------------|
| Classes Begin | Aug. 26 |
| Add/Drop | Aug. 26 - 30 |
| No Classes - College remains open | Sept. 20 |
| Indigenous Peoples Day Holiday Observance | Oct. 14 |
| Registration for Spring Classes | Oct. 28 - Nov. 8 |
| Last Day to Withdraw | Nov. 8 |
| Thanksgiving Break | Nov. 25 - Nov. 29 |
| Last Day of Classes | Dec. 6 |
| Finals | Dec. 9 - Dec. 13 |
| Winter Break | Dec. 16 - Jan. 10 |


<div class="grid cards" markdown>

- ### :beginner:{ .lg .middle } __Introduction__
<hr>
- Week 1. 2024/01/15
- Topic: AI for games, review of basic AI techniques
- [Assignments](01-introduction/assignments.md)

</div>

---
Old schedules for reference

## Schedule for Spring 2024

College dates for the Spring 2024 semester:

| Date | Event |
Expand All @@ -59,7 +90,6 @@ College dates for the Spring 2024 semester:
- ### :beginner:{ .lg .middle } __Introduction__

---

- Week 1. 2024/01/15
- Topic: AI for games, review of basic AI techniques
- Activities:
Expand Down Expand Up @@ -174,8 +204,7 @@ College dates for the Spring 2024 semester:
</div>


---
Old schedule for reference


## Schedule for Fall 2023

Expand All @@ -193,7 +222,7 @@ Relevant dates for the Fall 2023 semester:

- Week 1. 2023/08/28
- Topic: Introduction
- Formal Assignment: [Flocking at Beecrowd](assignments/flocking/README.md)
- Formal Assignment: [Flocking Formal](assignments/flocking/README.md)
- Interactive Assignment: [Flocking at MoBaGEn](https://github.com/InfiniBrains/mobagen/tree/master/examples/flocking)

- ### :robot:{ .lg .middle } __Behavioral Agents__
Expand Down Expand Up @@ -325,4 +354,3 @@ Relevant dates for the Fall 2023 semester:
slide test: [test](slides/test.md)


LangChain
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
add_executable(flocking flocking.cpp)
add_executable(ai-flocking flocking.cpp)

file(GLOB TEST_INPUT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.in)
file(GLOB TEST_OUTPUT_FILES ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.out)

add_custom_test(flocking-test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/flocking "${TEST_INPUT_FILES}" "${TEST_OUTPUT_FILES}")
add_custom_test(ai-flocking-test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/flocking "${TEST_INPUT_FILES}" "${TEST_OUTPUT_FILES}")

14 changes: 13 additions & 1 deletion docs/artificialintelligence/assignments/flocking/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# Flocking agents behavior assignment
# Flocking agents behavior formal assignment

You are in charge of implementing some functions to make some AI agents flock together in a game. After finishing it, you will be one step further to render it in a game engine, and start making reactive NPCs and enemies. You will learn all the basic concepts needed to code and customize your own AI behaviors.

You can code this assignment in any language and/or game engine you want. But I already crafted some boilerplates to maximize your efficiency.

- (Preferred) Formal and automatically tested: [This current repo](https://github.com/InfiniBrains/Awesome-GameDev-Resources/tree/main/docs/artificialintelligence/assignments/flocking)
- (Funnier) Interactive with SDL2: [MoBaGEn](https://github.com/InfiniBrains/mobagen/tree/master/examples/flocking/behaviours)
- (Hard-core) C++ with CMake: [SDL2-CPM-CMake-Example](https://github.com/InfiniBrains/SDL2-CPM-CMake-Example)
- I don't recommend using Game Engines for this specific assignment. Historically, students fail on the implementation of the double buffering and the math operations. But if you are confident, go ahead.

!!! danger "Notes on imprecision"

The automated tests of the formal assignment might differ somehow because of floating point imprecison, so don't worry much. If you cannot make it pass 100% of the tests, explain how you tried to solve it and what you think is wrong. I will evaluate your code based on your explanation.
If you find an issue on my formal description or on the tests, send a PR and I will give you extra points.

## What is flocking?

Flocking is a behavior that is observed in birds, fish and other animals that move in groups. It is a very simple behavior that can be implemented with a few lines of code. The idea is that each agent will try to move towards the center of mass of the group (cohesion), and will try to align its velocity with the average velocity of the group (AKA alignment). In addition, each agent will try to avoid collisions with other agents (AKA avoidance).
Expand Down
171 changes: 35 additions & 136 deletions docs/artificialintelligence/assignments/flocking/flocking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,49 +98,21 @@ struct Cohesion {
double radius;
double k;

Cohesion() = default;

Vector2 ComputeForce(const vector<Boid>& boids, int boidAgentIndex) {
auto agent = boids[boidAgentIndex];
Vector2 centerOfMass = {0,0};
int numberOfNeighbors = 0;
for(int i = 0; i < boids.size(); i++){
if(i==boidAgentIndex)
continue;
auto vec = boids[i].position - agent.position;
auto magnitude = vec.getMagnitude();
if(magnitude < radius && vec != Vector2::zero) {
centerOfMass += vec;
numberOfNeighbors++;
}
}
if(numberOfNeighbors>0){
return k * ((centerOfMass/numberOfNeighbors)/radius);
}
else
return {};
return {};
}
};

struct Alignment {
double radius;
double k;

Alignment() = default;

Vector2 ComputeForce(const vector<Boid>& boids, int boidAgentIndex) {
auto agent = boids[boidAgentIndex];
Vector2 avgVelocity = {0,0};
int numberOfNeighbors = 0;
for(const auto & boid : boids){
auto displacementVec = boid.position - agent.position;
auto magnitude = displacementVec.getMagnitude();
if(magnitude < radius) {
avgVelocity += boid.velocity;
numberOfNeighbors++;
}
}
if(numberOfNeighbors>0){
return k * (avgVelocity/numberOfNeighbors);
}
else
return {};
return {};
}
};

Expand All @@ -149,36 +121,24 @@ struct Separation {
double k;
double maxForce;

Separation() = default;

Vector2 ComputeForce(const vector<Boid>& boids, int boidAgentIndex) {
auto agent = boids[boidAgentIndex];
Vector2 avg = {0,0};
int numberOfNeighbors = 0;
for(int i=0;i<boids.size();i++){
if(i==boidAgentIndex)
continue;
auto displacementVec = boids[i].position - agent.position;
auto magnitude = displacementVec.getMagnitude();
if(magnitude < radius && displacementVec != Vector2::zero) {
auto displacementVecNorm = displacementVec / magnitude;
avg += displacementVecNorm;
numberOfNeighbors++;
}
}
if(avg.getMagnitude() > maxForce)
return avg.normalized() * maxForce;
else
return avg;
return {};
}
};

// feel free to edit this main function to meet your needs
int main() {
// Variable declaration
double rc, rs, Fsmax, ra, Kc, Ks, Ka;
Separation separation{};
Alignment alignment{};
Cohesion cohesion{};
int numberOfBoids;
string line; // for reading until EOF
vector<Boid> currentState, newState;
// Input Reading
cin >> rc >> rs >> Fsmax >> ra >> Kc >> Ks >> Ka >> numberOfBoids;
cin >> cohesion.radius >> separation.radius >> separation.maxForce >> alignment.radius >> cohesion.k >> separation.k >> alignment.k >> numberOfBoids;
for (int i = 0; i < numberOfBoids; i++)
{
Boid b;
Expand All @@ -188,103 +148,42 @@ int main() {
newState.push_back(b);
}
// Final input reading and processing
// todo: edit this. probably my code will be different than yours.
while (getline(cin, line)) { // game loop
// Use double buffer! you should read from the current and store changes in the new state.
currentState = newState;
double deltaT = stod(line);
// cout << "== Start Line Read. deltaT: " << deltaT << " ==" << endl;
vector<Vector2> allForces; // a vector of the sum of forces for each boid.
// a vector of the sum of forces for each boid.
vector<Vector2> allForces = vector<Vector2>(numberOfBoids, {0, 0});
// Compute Forces
for (int i = 0; i < numberOfBoids; i++) // for every boid
{
// cout << ">-> For Every Boid " << i << endl;
double totalForceX = 0.0, totalForceY = 0.0, cohesionTotalX = 0.0, cohesionTotalY = 0.0,
separationTotalVX = 0.0, separationTotalVY = 0.0, alignmentTotalVX = 0.0,
alignmentTotalVY = 0.0;
int cohesionTotalBoids = 0, alignmentTotalBoids = 0;
for (int j = 0; j < N; j++) // for every boid combination. Pre-processing loop.
for (int j = 0; j < numberOfBoids; j++) // for every boid combination. Pre-processing loop.
{
// cout << ">-> >-> For Every Boid Combination " << j << endl;
// Pre-Process Cohesion Forces
if (i != j
&& get_distance(allBoids[i].x, allBoids[i].y, allBoids[j].x, allBoids[j].y) <= rc) {
// cout << "Cohesion Force Found" << endl;
cohesionTotalX += allBoids[j].x;
cohesionTotalY += allBoids[j].y;
cohesionTotalBoids++;
// Process Cohesion Forces
auto dist = (currentState[i].position-currentState[j].position).getMagnitude();
if (i != j && dist <= cohesion.radius) {
allForces[i] += cohesion.ComputeForce(currentState, i);
}
// Pre-Process Separation Forces
if (i != j
&& get_distance(allBoids[j].x, allBoids[j].y, allBoids[i].x, allBoids[i].y) <= rs) {
// cout << "Separation Force Found" << endl;
pair<double, double> nvANi
= get_normalized_vector(allBoids[j].x, allBoids[j].y, allBoids[i].x, allBoids[i].y);
pair<double, double> vANi
= get_vector(allBoids[j].x, allBoids[j].y, allBoids[i].x, allBoids[i].y);
// cout << "nvANI. x: " << nvANi.first << " y: " << nvANi.second << " vANI. x: " << vANi.first << " y: " << vANi.second << endl;
if (vANi.first != 0) {
separationTotalVX += nvANi.first / abs(vANi.first);
}
if (vANi.second != 0) {
separationTotalVY += nvANi.second / abs(vANi.second);
}
// cout << "nvANi.first: " << nvANi.first << " vANi.first: " << vANi.first << " nvANi.first/vANi.first: " << nvANi.first/vANi.first << endl;
// Process Separation Forces
if (i != j && dist <= separation.radius) {
allForces[i] += separation.ComputeForce(currentState, i);
}
// Pre-Process Alignment Forces
if (get_distance(allBoids[i].x, allBoids[i].y, allBoids[j].x, allBoids[j].y) <= ra) {
// cout << "Alignment Force Found" << endl;
alignmentTotalVX += allBoids[j].vx;
alignmentTotalVY += allBoids[j].vy;
alignmentTotalBoids++;
// cout << "alignmentTotalVX: " << alignmentTotalVX << endl;
// Process Alignment Forces
if (i != j && dist <= alignment.radius) {
allForces[i] += alignment.ComputeForce(currentState, i);
}
}
// Process Cohesion Forces
if (cohesionTotalBoids > 0) { // If a cohesion force was found
pair<double, double> cohesionVector
= get_vector(allBoids[i].x, allBoids[i].y, cohesionTotalX / cohesionTotalBoids,
cohesionTotalY / cohesionTotalBoids);
totalForceX += (cohesionVector.first / rc) * Kc;
totalForceY += (cohesionVector.second / rc) * Kc;
// cout << "* cohesion force Y: " << ((cohesionVector.second/rc) * Kc) << endl;
}
// Process Separation Forces
if (sqrt(pow(separationTotalVX, 2) * pow(separationTotalVY, 2))
<= Fsmax) // if total force is NOT greater than limit, use as is.
{
totalForceX += (separationTotalVX * Ks);
// cout << "S totalForceY: " << totalForceY << endl;
totalForceY += (separationTotalVY * Ks);
// cout << "* (1)separation Force Y: " << totalForceY << endl;
} else { // else normalize and multiply by limit
pair<double, double> normalized = normalize_vector(separationTotalVX, separationTotalVY);
totalForceX += normalized.first * Fsmax * Ks;
totalForceY += normalized.second * Fsmax * Ks;
// cout << "* (2)separation Force Y: " << (normalized.second * Fsmax * Ks) << endl;
}
// Process Alignment Forces
if (alignmentTotalBoids > 0) {
totalForceX += alignmentTotalVX / alignmentTotalBoids * Ka;
// cout << "(A) totalForceX: " << totalForceX << endl;
totalForceY += alignmentTotalVY / alignmentTotalBoids * Ka;
// cout << "* total Alignment Force X: " << (alignmentTotalVX/alignmentTotalBoids * Ka) << " totalForceX " << totalForceX << endl;
}
// cout << "total Force X in the end: " << totalForceX << endl;
// cout << "* total Force Y in the end: " << totalForceY << endl;
// Add total forces of 1 boid to forces vector.
allForcesX.push_back(totalForceX);
allForcesY.push_back(totalForceY);
}
// Tick Time and Output
// todo: edit this. probably my code will be different than yours.
cout << fixed << setprecision(3); // set 3 decimal places precision for output
for (int i = 0; i < N; i++) // for every boid
for (int i = 0; i < numberOfBoids; i++) // for every boid
{
// cout << "FORCES X: " << allForcesX[i] << " Y: " << allForcesY[i] << endl;
allBoids[i].vx += allForcesX[i] * deltaT;
allBoids[i].vy += allForcesY[i] * deltaT;
allBoids[i].x += allBoids[i].vx * deltaT;
allBoids[i].y += allBoids[i].vy * deltaT;
cout << allBoids[i].x << " " << allBoids[i].y << " " << allBoids[i].vx << " "
<< allBoids[i].vy << endl;
newState[i].velocity += allForces[i] * deltaT;
newState[i].position += currentState[i].velocity * deltaT;
cout << newState[i].position.x << " " << newState[i].position.y << " "
<< newState[i].velocity.x << " " << newState[i].velocity.y << endl;
}
}

Expand Down

0 comments on commit f4a7a77

Please sign in to comment.