Advertisement
MrMusAddict

COVID-19 Simulation v2

Mar 25th, 2020
747
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 14.10 KB | None | 0 0
  1. import com.hamoid.*;
  2. VideoExport videoExport;
  3.  
  4. ArrayList<Person> people;
  5. Boundary world;
  6. Boundary hospital;
  7.  
  8. int population = 2000;
  9. PopStats pStats;
  10.  
  11. int daysInfectedWithoutSymptoms = 3;
  12. int daysInfectedWithSymptoms = 7;
  13. int daysRecoveringButInfectious = 4;
  14.  
  15. int simTicksPerDay = 60;
  16.  
  17. int walkSpeed = 1;
  18.  
  19. float mortalityRate = 0.02;
  20.  
  21. //Whether or not people will stay in during certain circumstances
  22. boolean qStayIn = false;  //Will stay in forever, after infected
  23. boolean qWithoutSymptoms = false; //Will stay in while they're initially infected but not showing symptoms
  24. boolean qWithSymptoms = true; //will stay in while they're showing symptoms
  25. boolean qInRecovery = false; //Will stay in while they're recovering without symptoms, but still infectious
  26.  
  27. //Initializing bools for determining if the recommended or mandated stay-ins are in effect
  28. boolean govReccomend = false;
  29. boolean govRecFinished = false;
  30. boolean govMandate = false;
  31. boolean govManFinished = false;
  32.  
  33. //How many days (in ticks) the reccomended stay-in will last.
  34. int recDayTicks = 28 * simTicksPerDay;
  35. int recTicks = 0;
  36.  
  37. //How many days (in ticks) the mandated stay-in will last. Set to the full lifespan of infectiousness.
  38. int manDayTicks = (daysInfectedWithoutSymptoms + daysInfectedWithSymptoms + daysRecoveringButInfectious) * simTicksPerDay;
  39. int manTicks = 0;
  40.  
  41. ArrayList<PopStats> graph = new ArrayList<PopStats>();
  42.  
  43. //Counts the ticks after everyone's free of infections (healthy, recovered, or dead)
  44. int FinishedTick = 0;
  45.  
  46. //Used to calculate R0 (growth rate)
  47. float oldInf = 1;
  48. float newInf = 1;
  49. float r0;
  50.  
  51.  
  52. void setup() {
  53.   //fullScreen();
  54.   size(1400, 1600);
  55.   videoExport = new VideoExport(this);
  56.   videoExport.startMovie();
  57.  
  58.   //Set the bounds for people to bounce in
  59.   world = new Boundary(0, 280, 1400, 1120);
  60.  
  61.   //Create an array of people the size of the entered population
  62.   people = new ArrayList<Person>();
  63.   for (int i = 0; i < population; i++) {
  64.     people.add(new Person(world));
  65.   }
  66.  
  67.   //Set one person to 'Infected Without Symptoms'
  68.   people.get(0).setInfState(1);
  69.  
  70.   //Initialize simulation stats
  71.   pStats = new PopStats();
  72.  
  73.   //Initialize Graph
  74.   for (int i = 0; i < 1400; i++) {
  75.     graph.add(new PopStats());
  76.   }
  77. }
  78.  
  79. void draw() {
  80.   background(51);
  81.   world.show();
  82.  
  83.   //Move each person and progress their infection
  84.   //Bounce them back into the bounds, if they travel outside
  85.   for (Person p : people) {    
  86.     p.update();
  87.     p.bounds();
  88.   }
  89.  
  90.   //Calculate collisions with other people, spreading infection and recalculating velocity
  91.   //Display each person
  92.   //These are separated into their own for() loop, because collisions need to be calculated after EVERYONE moves
  93.   for (Person p : people) {
  94.     p.collide();
  95.     p.show();
  96.   }
  97.  
  98.   // Update Stats
  99.   pStats.update();
  100.  
  101.   //Close app if there's no more infection, after 300 ticks
  102.   if (pStats.healthy + pStats.recd + pStats.dead == population) {
  103.     FinishedTick++;
  104.     if (FinishedTick >= 300) {
  105.       videoExport.endMovie();
  106.       exit();
  107.     }
  108.   }
  109.  
  110.   //Add new stats data to the graph at the bottom of the simulation
  111.   graph.add(new PopStats());
  112.   graph.get(graph.size()-1).healthy = pStats.healthy;
  113.   graph.get(graph.size()-1).infWOSym = pStats.infWOSym;
  114.   graph.get(graph.size()-1).infWSym = pStats.infWSym;
  115.   graph.get(graph.size()-1).recWOSym = pStats.recWOSym;
  116.   graph.get(graph.size()-1).recd = pStats.recd;
  117.   graph.get(graph.size()-1).dead = pStats.dead;
  118.  
  119.   if (graph.size() > width) {
  120.     graph.remove(0);
  121.   }
  122.  
  123.   //Plot the graph at the bottom of the simulation
  124.   for (int i = 0; i < graph.size()-1; i++) {
  125.     strokeWeight(1);    
  126.  
  127.     float s1 = (graph.get(i).healthy * 1.0) / (population*1.0) * 200.0;
  128.     float s2 = (graph.get(i).infWOSym * 1.0) / (population*1.0) * 200.0;
  129.     float s3 = (graph.get(i).infWSym * 1.0) / (population*1.0) * 200.0;
  130.     float s4 = (graph.get(i).recWOSym * 1.0) / (population*1.0) * 200.0;
  131.     float s5 = (graph.get(i).recd * 1.0) / (population*1.0) * 200.0;
  132.     float s6 = (graph.get(i).dead * 1.0) / (population*1.0) * 200.0;
  133.  
  134.     stroke(127, 127, 127);
  135.     line(i, 1400, i, 1400 + s1);
  136.     stroke(255, 255, 0);
  137.     line(i, 1400 + s1, i, 1400 + s1 + s2);
  138.     stroke(255, 0, 0);
  139.     line(i, 1400 + s1 + s2, i, 1400 + s1 + s2 + s3);
  140.     stroke(255, 0, 255);
  141.     line(i, 1400 + s1 + s2 + s3, i, 1400 + s1 + s2 + s3 + s4);
  142.     stroke(0, 0, 255);
  143.     line(i, 1400 + s1 + s2 + s3 + s4, i, 1400 + s1 + s2 + s3 + s4 + s5);
  144.     stroke(0, 0, 0);
  145.     line(i, 1400 + s1 + s2 + s3 + s4 + s5, i, 1400 + s1 + s2 + s3 + s4 + s5 + s6);
  146.   }
  147.  
  148.   //Reccomend Stay-In when >0.1% of the population is symptomatic
  149.   if ((pStats.infWSym / 1.0) / (population / 1.0) > 0.001) {
  150.     if (!govRecFinished) {
  151.       govReccomend = true;
  152.     }
  153.   }
  154.  
  155.   //Make the stay in last for 28 days, unless >0.5% of the population is still symptomatic. In which case, extend the stay-in.
  156.   if (govReccomend == true && govRecFinished == false) {
  157.     recTicks++;
  158.     if (recTicks >= recDayTicks && (pStats.infWSym / 1.0) / (population / 1.0) <= 0.005) {
  159.       govReccomend = false;
  160.       govRecFinished = true;
  161.     }
  162.   }
  163.  
  164.  
  165.   //Stay-in required by law for all people if >5.0% of the population is symptomatic, but only if the stay-in fails.
  166.   if (govRecFinished) {
  167.     if ((pStats.infWSym / 1.0) / (population / 1.0) > 0.05) {
  168.       if (!govManFinished) {
  169.         govMandate = true;
  170.       }
  171.     }
  172.    
  173.     //Mandate lasts the length of incubation + symptom + recovery. In this case 14 days, but can be changed at the top.
  174.     if (govMandate == true && govManFinished == false && manTicks < manDayTicks) {
  175.       manTicks++;
  176.       if (manTicks >= manDayTicks) {
  177.         govMandate = false;
  178.         govManFinished = true;
  179.       }
  180.     }
  181.   }
  182.  
  183.   //Show stats at top of simulation
  184.   fill(255);
  185.   textSize(20);
  186.   text("Healthy: " + pStats.healthy, 10, 20);
  187.   text("Infected: " + (pStats.infWOSym + pStats.infWSym + pStats.recWOSym), 10, 45);
  188.   text("Showing Symptoms: " + pStats.infWSym + " (" + (round(pStats.infWSym / (population/1.0)*1000.0)/10.0) + "% of pop.)", 10, 70);
  189.   text("People showing symptoms stay home sick.", 450, 70);
  190.   text("Recovered: " + (pStats.recd), 10, 95);
  191.   text("Dead: " + (pStats.dead), 10, 120);
  192.  
  193.   //Show message that the recomended stay-in has been issued.
  194.   textSize(30);
  195.   if (govReccomend == true && govRecFinished == false) {
  196.     fill(255, 255, 0);
  197.     text("Stay-In reccomended by government. 80% of the population is adhering to the Stay-In.", 10, 180);
  198.  
  199.     if (recDayTicks-recTicks > 0) {
  200.       text(round((recDayTicks-recTicks)/(recDayTicks*1.0)*28.0) + " days of Stay-In remaining.", 10, 220);
  201.     } else {
  202.       text("Stay-In extended until <0.5% pop is symptomatic.", 10, 220);
  203.     }
  204.   }
  205.  
  206.   //Show message that mandated stay-in has been issued
  207.   if (govMandate == true && govManFinished == false) {
  208.     fill(255, 0, 0);
  209.     text("Government Mandate issued. All of the population is adhering.", 10, 180);
  210.     text(round((manDayTicks-manTicks)/(manDayTicks*1.0)*14.0) + " days of mandate remaining.", 10, 220);
  211.   }
  212.  
  213.   //Save video frame
  214.   if (frameCount%5 == 0) {
  215.     //videoExport.saveFrame();
  216.   }
  217.  
  218.   //Calculate R0 (growth rate), printed in console.
  219.   if(frameCount%60 == 30){
  220.     oldInf = newInf;
  221.     newInf = (population - pStats.healthy) - oldInf;
  222.     r0 = newInf / oldInf;
  223.     println(r0);
  224.   }
  225. }
  226.  
  227.  
  228.  
  229. //Quit and export if 'Q' is pressed
  230. void keyPressed() {
  231.   if (key == 'q') {
  232.     videoExport.endMovie();
  233.     exit();
  234.   }
  235. }
  236.  
  237.  
  238.  
  239.  
  240. class Person {
  241.   int infState;
  242.    /*
  243.    0 = Healthy
  244.    1 = Infected Without Symptoms
  245.    2 = Infected
  246.    3 = Recovering But Infectious
  247.    4 = Recovered
  248.    5 = Dead
  249.    */
  250.  
  251.   //infTick is used to count the ticks for each phase of infection
  252.   int[] infTick = new int[6];
  253.  
  254.   //Position and Velocity vectors
  255.   PVector pos;
  256.   PVector vel;
  257.  
  258.   //The bounds to bounce in
  259.   Boundary bounds;
  260.  
  261.   //the color, based on their state of infection
  262.   color infCol;
  263.  
  264.   //Whether or not they're isolating
  265.   boolean socDist = false;
  266.  
  267.   //To determine if they will follow the reccomended stay-in
  268.   boolean followRec;
  269.  
  270.   Person(Boundary b) {
  271.     infState = 0; //Everyone starts healthy. One person is manually infected at the start of the simulation.
  272.    
  273.     //Random start position within bounds
  274.     bounds = b;
  275.     pos = new PVector(random(bounds.right) + bounds.left, random(bounds.bottom) + bounds.top);
  276.    
  277.     //Random initial velocity (any direction on a unit circle * walking speed)
  278.     vel = new PVector(walkSpeed, 0).rotate(random(TWO_PI));
  279.    
  280.     //Start out looking healthy
  281.     infCol = color(127, 127, 127);
  282.    
  283.     //Determines if they will follow the reccomended stay-in. Currently 80% chance.
  284.     followRec = random(1)>0.2;
  285.   }
  286.  
  287.  
  288.  
  289.   void update() {
  290.     //Count how many ticks they've been in their current state
  291.     infTick[infState] += 1;
  292.    
  293.     //If they're infected, advance them to the next stage if it's been long enough
  294.     if (infState == 1 && infTick[1] >= daysInfectedWithoutSymptoms * simTicksPerDay) {
  295.       setInfState(2);
  296.     }
  297.     if (infState == 2 && infTick[2] >= daysInfectedWithSymptoms * simTicksPerDay) {
  298.       setInfState(3);
  299.     }
  300.     if (infState == 3 && infTick[3] >= daysRecoveringButInfectious * simTicksPerDay) {
  301.       setInfState(4);
  302.     }
  303.    
  304.     //If they're infected, check to see if they happen to die
  305.     //Mortality rate is currently 2%, but infection currently lasts 840 ticks (14 days * 60 ticks per day)
  306.     //So instead of a 1/50 (2%) chance of death each tick, they have a (1/50 * 1/840) chance of death each tick, or 1/42,000
  307.     //Since they have a 1/42,000 chance 840 times, their true mortality rate is still 1/50.
  308.     if (infState == 1 || infState == 2 || infState == 3) {
  309.       float mortalityCheck = random(1);
  310.       if (mortalityCheck <= 1.0 / ((daysInfectedWithoutSymptoms+daysInfectedWithSymptoms+daysRecoveringButInfectious) * simTicksPerDay / mortalityRate)) {
  311.         setInfState(5);
  312.       }
  313.     }
  314.    
  315.     //Move the character as long as they aren't opting to isolate themselves, and as long as they aren't mandated to stay-in
  316.     if (!(socDist || govMandate || (followRec && govReccomend))) {
  317.       pos.x += vel.x;
  318.       pos.y += vel.y;
  319.     }
  320.   }
  321.  
  322.  
  323.  
  324.   //Changes the state of infection. Also decides whther or not they choose to isolate themselves
  325.   void setInfState(int i) {
  326.     //Infected without symptoms
  327.     if (i == 1) {
  328.       infState = 1;
  329.       infCol = color(255, 255, 0);
  330.       socDist = qWithoutSymptoms || qStayIn;
  331.     }
  332.    
  333.     //Infected with symptoms
  334.     if (i == 2) {
  335.       infState = 2;
  336.       infCol = color(255, 0, 0);
  337.       socDist = qWithSymptoms || qStayIn;
  338.     }
  339.    
  340.     //Recovering but infectious, no symptoms
  341.     if (i == 3) {
  342.       infState = 3;
  343.       infCol = color(250, 0, 250);
  344.       socDist = qInRecovery || qStayIn;
  345.     }
  346.    
  347.     //Recovered
  348.     if (i == 4) {
  349.       infState = 4;
  350.       infCol = color(0, 0, 255);
  351.       socDist = qStayIn;
  352.     }
  353.    
  354.     //Dead
  355.     if (i == 5) {
  356.       infState = 5;
  357.       infCol = color(0, 0, 0);
  358.       socDist = true;
  359.     }
  360.   }
  361.  
  362.  
  363.  
  364.   //Checks collision with boundary
  365.   void bounds() {
  366.     if (pos.x > bounds.left + bounds.right) {
  367.       pos.x = 2 * bounds.left + 2 * bounds.right - pos.x;
  368.       vel.x *= -1;
  369.     }
  370.     if (pos.y > bounds.top + bounds.bottom) {
  371.       pos.y = 2 * bounds.top + 2 * bounds.bottom - pos.y;
  372.       vel.y *= -1;
  373.     }
  374.     if (pos.x < bounds.left) {
  375.       pos.x = 2 * bounds.left - pos.x;
  376.       vel.x *= -1;
  377.     }
  378.     if (pos.y < bounds.top) {
  379.       pos.y = 2 * bounds.top - pos.y;
  380.       vel.y *= -1;
  381.     }
  382.   }
  383.  
  384.  
  385.  
  386.   //Checks collision with every other person
  387.   void collide() {
  388.     boolean hit = false;
  389.     PVector newVel = new PVector(0, 0);
  390.     for (Person p : people) {
  391.      
  392.       //As long as both this person and the collided person are not isolating
  393.       if (p != this && !(p.socDist || govMandate || (p.followRec && govReccomend))) {
  394.         if (!(socDist || govMandate || (followRec && govReccomend))) {
  395.          
  396.           //As long as they touched eachother
  397.           if (pos.dist(p.pos) < 10) {
  398.            
  399.             hit = true; //Confirm the hit
  400.            
  401.             //Calculate the temporary bounce vector, and add it to the new veloctiy vector
  402.             //(in case there are more than 1 bounce per tick, we add all bounce vectors first, to determine the true new velocity)
  403.             PVector tempVel = new PVector(pos.x - p.pos.x, pos.y - p.pos.y).normalize();
  404.             newVel.x += tempVel.x;
  405.             newVel.y += tempVel.y;
  406.            
  407.             //Infect this person, if they aren't already, and collide with an infected
  408.             if (p.infState == 1 || p.infState == 2 || p.infState == 3) {
  409.               if (infState == 0) {
  410.                 setInfState(1);
  411.               }
  412.             }
  413.           }
  414.         }
  415.       }
  416.     }
  417.    
  418.     //Recalculate velocity if they collided
  419.     if (hit) {
  420.       newVel.normalize().setMag(walkSpeed);
  421.       vel.x = newVel.x;
  422.       vel.y = newVel.y;
  423.     }
  424.   }
  425.  
  426.  
  427.  
  428.   //Draw the person
  429.   void show() {
  430.     noStroke();
  431.     fill(infCol);
  432.     circle(pos.x, pos.y, 10);
  433.   }
  434. }
  435.  
  436.  
  437.  
  438.  
  439. class Boundary {
  440.   float top;
  441.   float bottom;
  442.   float left;
  443.   float right;
  444.  
  445.   Boundary(float a, float b, float c, float d) {
  446.     left = a;
  447.     top = b;
  448.     right = c;
  449.     bottom = d;
  450.   }
  451.  
  452.   void show() {
  453.     stroke(255);
  454.     strokeWeight(5);
  455.     noFill();
  456.     rect(left, top, right, bottom);
  457.   }
  458. }
  459.  
  460.  
  461.  
  462.  
  463. class PopStats {
  464.   int healthy = population;
  465.   int infWOSym = 0;
  466.   int infWSym = 0;
  467.   int recWOSym = 0;
  468.   int recd = 0;
  469.   int dead = 0;
  470.  
  471.   void update() {
  472.     healthy = 0;
  473.     infWOSym = 0;
  474.     infWSym = 0;
  475.     recWOSym = 0;
  476.     recd = 0;
  477.     dead = 0;
  478.  
  479.     for (Person p : people) {
  480.       switch(p.infState) {
  481.       case 0:
  482.         healthy++;
  483.         break;
  484.       case 1:
  485.         infWOSym++;
  486.         break;
  487.       case 2:
  488.         infWSym++;
  489.         break;
  490.       case 3:
  491.         recWOSym++;
  492.         break;
  493.       case 4:
  494.         recd++;
  495.         break;
  496.       case 5:
  497.         dead++;
  498.         break;
  499.       }
  500.     }
  501.   }
  502. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement