From f4a7a77bda0d4c3e298beb7fb034c54556710b06 Mon Sep 17 00:00:00 2001 From: Alexandre Tolstenko Date: Sun, 25 Aug 2024 20:09:59 -0400 Subject: [PATCH] fix: flocking algorithm --- docs/algorithms/README.md | 2 + .../00-introduction/assignments.md | 7 + docs/artificialintelligence/CMakeLists.txt | 2 +- docs/artificialintelligence/README.md | 44 ++++- .../assignments/flocking/CMakeLists.txt | 4 +- .../assignments/flocking/README.md | 14 +- .../assignments/flocking/flocking.cpp | 171 ++++-------------- 7 files changed, 96 insertions(+), 148 deletions(-) create mode 100644 docs/artificialintelligence/00-introduction/assignments.md diff --git a/docs/algorithms/README.md b/docs/algorithms/README.md index 234258e5..220b1318 100644 --- a/docs/algorithms/README.md +++ b/docs/algorithms/README.md @@ -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 diff --git a/docs/artificialintelligence/00-introduction/assignments.md b/docs/artificialintelligence/00-introduction/assignments.md new file mode 100644 index 00000000..0a944655 --- /dev/null +++ b/docs/artificialintelligence/00-introduction/assignments.md @@ -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); \ No newline at end of file diff --git a/docs/artificialintelligence/CMakeLists.txt b/docs/artificialintelligence/CMakeLists.txt index ff32740c..dec32f95 100644 --- a/docs/artificialintelligence/CMakeLists.txt +++ b/docs/artificialintelligence/CMakeLists.txt @@ -35,4 +35,4 @@ add_subdirectory(assignments/flocking) add_subdirectory(assignments/maze) add_subdirectory(assignments/life) add_subdirectory(assignments/rng) -add_subdirectory(assignments/catchthecat) \ No newline at end of file +#add_subdirectory(assignments/catchthecat) \ No newline at end of file diff --git a/docs/artificialintelligence/README.md b/docs/artificialintelligence/README.md index b9a2e361..09e2a753 100644 --- a/docs/artificialintelligence/README.md +++ b/docs/artificialintelligence/README.md @@ -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 | + + +
+ +- ### :beginner:{ .lg .middle } __Introduction__ +
+ - Week 1. 2024/01/15 + - Topic: AI for games, review of basic AI techniques + - [Assignments](01-introduction/assignments.md) + +
+ +--- +Old schedules for reference + +## Schedule for Spring 2024 + College dates for the Spring 2024 semester: | Date | Event | @@ -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: @@ -174,8 +204,7 @@ College dates for the Spring 2024 semester: ---- -Old schedule for reference + ## Schedule for Fall 2023 @@ -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__ @@ -325,4 +354,3 @@ Relevant dates for the Fall 2023 semester: slide test: [test](slides/test.md) -LangChain \ No newline at end of file diff --git a/docs/artificialintelligence/assignments/flocking/CMakeLists.txt b/docs/artificialintelligence/assignments/flocking/CMakeLists.txt index 1fc55969..0409c64e 100644 --- a/docs/artificialintelligence/assignments/flocking/CMakeLists.txt +++ b/docs/artificialintelligence/assignments/flocking/CMakeLists.txt @@ -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}") diff --git a/docs/artificialintelligence/assignments/flocking/README.md b/docs/artificialintelligence/assignments/flocking/README.md index dad0411a..71777e60 100644 --- a/docs/artificialintelligence/assignments/flocking/README.md +++ b/docs/artificialintelligence/assignments/flocking/README.md @@ -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). diff --git a/docs/artificialintelligence/assignments/flocking/flocking.cpp b/docs/artificialintelligence/assignments/flocking/flocking.cpp index c7e585eb..2662783f 100644 --- a/docs/artificialintelligence/assignments/flocking/flocking.cpp +++ b/docs/artificialintelligence/assignments/flocking/flocking.cpp @@ -98,25 +98,10 @@ struct Cohesion { double radius; double k; + Cohesion() = default; + Vector2 ComputeForce(const vector& 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 {}; } }; @@ -124,23 +109,10 @@ struct Alignment { double radius; double k; + Alignment() = default; + Vector2 ComputeForce(const vector& 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 {}; } }; @@ -149,36 +121,24 @@ struct Separation { double k; double maxForce; + Separation() = default; + Vector2 ComputeForce(const vector& boids, int boidAgentIndex) { - auto agent = boids[boidAgentIndex]; - Vector2 avg = {0,0}; - int numberOfNeighbors = 0; - for(int i=0;i 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 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; @@ -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 allForces; // a vector of the sum of forces for each boid. + // a vector of the sum of forces for each boid. + vector allForces = vector(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 nvANi - = get_normalized_vector(allBoids[j].x, allBoids[j].y, allBoids[i].x, allBoids[i].y); - pair 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 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 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; } }