Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include <thread>
- #include <chrono>
- #include <iostream>
- #include <vector>
- #include <set>
- #include <string>
- #include <random>
- #include <iomanip>
- enum State {
- Uninfected,
- Infected,
- Immune,
- Dead
- };
- struct Person {
- bool vulnerable;
- State state;
- unsigned x;
- unsigned y;
- // Precomputed neighboring people
- std::vector<Person*> neighbors;
- Person()
- : vulnerable()
- , state(Uninfected)
- , x()
- , y()
- {
- }
- };
- // Escape codes, the lazy way
- void CLR() { std::cout << "\x1b[H\x1b[J"; }
- void CUP(int x, int y) { std::cout << "\x1b[" << (y+1) << ";" << (x+1) << "H"; }
- void EL() { std::cout << "\x1b[K"; };
- void RGB(unsigned r, unsigned g, unsigned b) {
- r%=6; g%=6; b%=6;
- std::cout << "\x1b[38;5;" << ((36*r+6*g+b)+16) << "m";
- }
- struct Environment {
- unsigned width;
- unsigned height;
- // Don't need to seed this; getting the same results
- // for the same run is actually better for us.
- std::mt19937 rng;
- // The actual people
- std::vector<Person> people;
- // Number of iterations
- unsigned int rounds;
- // Total deaths
- unsigned int deaths;
- // Infected people this round
- unsigned int infections;
- // Total immune
- unsigned int immunizations;
- // Deaths since last round
- unsigned int death_delta;
- // Immunized since last round
- unsigned int immunization_delta;
- Environment(unsigned w, unsigned h)
- : width(w)
- , height(h)
- , rng()
- , rounds()
- , deaths()
- , infections()
- , immunizations()
- , death_delta()
- , immunization_delta()
- {
- }
- // Convert x,y to the 1D index into our land vector
- unsigned xy_ndx(int x, int y)
- {
- // ...allows small negative deltas with correct wraparound
- // (makes grid a torus)
- if (x<0) x+=width;
- if (y<0) y+=height;
- return (x%width)+(y%height)*width;
- }
- // Add vulnerable
- void add_vulnerable(unsigned int range, unsigned to_add)
- {
- // Build the land array back up
- std::vector<Person*> land(width*height);
- std::uniform_int_distribution<unsigned> xgen(0, width-1);
- std::uniform_int_distribution<unsigned> ygen(0, height-1);
- std::uniform_int_distribution<unsigned> ngen(0, population-1);
- std::vector<Person*> land(width*height);
- for (auto &p : person)
- {
- land[xy_ndx(p.x,p.y)]=&p;
- }
- unsigned int start = people.size();
- unsigned int end = start + to_add;
- people.resize(end);
- for (auto i=start; i<end; ++i)
- {
- for(;;)
- {
- auto x = xgen(rng), y = ygen(rng);
- if (land[xy_ndx(x,y)]) continue;
- people[i].x = x;
- people[i].y = y;
- people[i].state = Uninfected;
- people[i].vulnerable = true;
- }
- }
- unsigned sq = range*range;
- std::vector<std::pair<int,int>> deltas;
- for (int x=0, xe=range; x<xe; ++x)
- for (int y=0, ye=range; y<ye; ++y)
- {
- // skip origin
- if ((x==0)&&(y==0)) continue;
- if (x*x+y*y>sq) continue;
- // Add all deltas
- deltas.push_back(std::make_pair( x, y));
- deltas.push_back(std::make_pair(-x, y));
- deltas.push_back(std::make_pair( x, -y));
- deltas.push_back(std::make_pair(-x, -y));
- }
- // For each new vulnerable added...
- for (auto i=start; i<end; ++i)
- {
- auto &tp = people[i];
- auto x = tp.x;
- auto y = tp.y;
- // For each delta
- for (auto &d : deltas)
- {
- auto& p2 = land[xy_ndx(x+d.first, y+d.second)]
- // Now it's possible they're already linked, so check first
- if (std::find(tp.neighbors.begin(), tp.neighbors.end(), p2)!=tp.neighbors.end())
- continue;
- tp.neighbors.push_back(&p2);
- p2.neighbors.push_back(&p1);
- }
- }
- }
- // Initial array population
- void populate(unsigned population, unsigned range, unsigned vulnerable, unsigned infected, unsigned immune)
- {
- infections = infected;
- immunizations = immune;
- // Temporarily allocate pointers for all land
- std::vector<Person*> land(width*height);
- // for x coordinate (in 2d environment)
- std::uniform_int_distribution<unsigned> xgen(0, width-1);
- // for y coordinate (in 2d environment)
- std::uniform_int_distribution<unsigned> ygen(0, height-1);
- // for n coordinate (index into array of size population)
- std::uniform_int_distribution<unsigned> ngen(0, population-1);
- people.resize(population);
- for (auto i=0; i<population; ++i)
- {
- // For looping if x,y is already populated
- for(;;)
- {
- auto x = xgen(rng), y = ygen(rng);
- if (land[xy_ndx(x,y)]) continue;
- // Set up person i
- Person* tp = &people[i];
- land[xy_ndx(x,y)] = tp;
- tp->x = x;
- tp->y = y;
- // people list already random; infect the first n people
- // where n=infected, then mark the next o immune
- // where o=immune.
- if (i<infected) tp->state = Infected;
- else if (i<infected+immune) tp->state = Immune;
- // and here we exit to outer loop
- break;
- }
- }
- // Now mark people vulnerable; do this in another loop
- // to mixing vulnerable people with infected
- for (auto i=0; i<vulnerable; ++i)
- {
- for(;;) {
- auto n = ngen(rng);
- if (people[n].vulnerable) continue;
- // comment this out to "vaccinate" the vulnerable
- if (people[n].state==Immune) continue;
- people[n].vulnerable = true;
- break;
- }
- }
- // Pre-calculate the integral delta squares one semicircle down;
- // this way we have a smaller set to iterate through.
- unsigned sq = range*range;
- std::vector<std::pair<int,int>> deltas;
- // Loop through just one quadrant and flip while looping
- for (int x=0, xe=range; x<xe; ++x)
- for (int y=0, ye=range; y<ye; ++y)
- {
- // skip origin
- if ((x==0)&&(y==0)) continue;
- if (x*x+y*y>sq) continue;
- // Add this and the x-reflected point
- deltas.push_back(std::make_pair(x, y));
- deltas.push_back(std::make_pair(-x, y));
- }
- // For each person on the grid...
- for (int x=0; x<width; ++x)
- for (int y=0; y<height; ++y)
- if (land[xy_ndx(x,y)])
- // ...find all people "below" that are our neighbors...
- for (auto &d : deltas)
- {
- if (land[xy_ndx(x+d.first, y+d.second)])
- {
- auto p1=land[xy_ndx(x,y)];
- auto p2=land[xy_ndx(x+d.first, y+d.second)];
- // ...and link in both directions
- p1->neighbors.push_back(p2);
- p2->neighbors.push_back(p1);
- }
- }
- }
- // Draw the background as dots
- void bg()
- {
- std::string row(width, '.');
- RGB(2,2,2);
- for (unsigned y=0; y<height; ++y) {
- CUP(0, y+1);
- std::cout << row;
- }
- }
- // Show current state
- void show()
- {
- CUP(0,0);
- EL();
- RGB(5,5,5);
- // Header stats
- std::cout
- << "Round " << std::setw(4) << rounds
- << "|Deaths " << std::setw(4) << deaths
- << " +" << death_delta
- << "|Infected " << std::setw(4) << infections
- << "|Immunized " << std::setw(4) << immunizations
- << " +" << immunization_delta
- ;
- // Render each person
- for (auto &p : people)
- {
- CUP(p.x, p.y+1);
- switch (p.state)
- {
- case Uninfected: RGB(1, 1, 5); std::cout << "U"; break;
- case Infected: RGB(5, 5, 1); std::cout << "S"; break;
- case Immune: RGB(5, 3, 0); std::cout << "I"; break;
- case Dead: RGB(3, 3, 4); std::cout << "X"; break;
- }
- }
- // Move cursor to end and flush;
- // we use "endl" here so we can see lines
- // in captured data with tee (for diagnostics)
- CUP(width,height+1); std::cout << std::endl;
- }
- // Evolution step
- void evolve()
- {
- ++rounds;
- death_delta = 0;
- // To avoid "smearing", we collect persons
- // who need to transition, then transition
- // only after the entire loop
- std::set<Person*> died;
- std::set<Person*> immunized;
- std::set<Person*> infected;
- for (auto& p : people)
- {
- // If a person is initially infected,
- if (p.state == Infected)
- {
- // If they are vulnerable,
- if (p.vulnerable)
- {
- // ...then they die
- died.insert(&p);
- }
- // And if they are not,
- else
- {
- // ...then they become immune
- immunized.insert(&p);
- }
- // And for each neighbor in the infection range...
- for (auto &neighbor : p.neighbors)
- // If that neighbor is uninfected,
- if (neighbor->state==Uninfected)
- {
- // ...then they get sick
- infected.insert(neighbor);
- }
- // Else if they are infected, they changed above
- // Else if they are immune, they don't get sick
- // Else (they are dead) nothing happens.
- }
- }
- // Stats
- death_delta = died.size();
- immunization_delta = immunized.size();
- infections = infected.size();
- deaths += death_delta;
- immunizations += immunization_delta;
- for (auto &p : died) p->state = Dead;
- for (auto &p : immunized) p->state = Immune;
- for (auto &p : infected) p->state = Infected;
- }
- };
- int main(int argc, const char* argv[])
- {
- // Parse arguments; since this is a one-time demo, we don't need nice,
- // just a way to input the stuff.
- // Start by sucking char*'s into vector of strings (and dropping argv[0])
- std::vector<std::string> args(argv+1, argv+argc);
- if (args.size()<7)
- return 1;
- // Arguments, in order:
- unsigned width = std::stoi(args[0]);
- unsigned height = std::stoi(args[1]);
- unsigned population = std::stoi(args[2]);
- unsigned range = std::stoi(args[3]);
- unsigned num_vulnerable = std::stoi(args[4]);
- unsigned num_infected = std::stoi(args[5]);
- unsigned num_immune = std::stoi(args[6]);
- // Create a width x height world
- Environment world(width, height);
- // Populate per above parameters
- world.populate(population, range, num_vulnerable, num_infected, num_immune);
- // Clear the screen...
- CLR();
- // Prime the bg and show initial state
- world.bg();
- world.show();
- for (;;) {
- std::this_thread::sleep_for(std::chrono::milliseconds(500));
- world.evolve();
- world.show();
- // End at eradication
- if (world.infections==0) break;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement