Hydreigon_Lord

Kahoot! Simulation

Nov 20th, 2020 (edited)
503
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 19.70 KB | None | 0 0
  1. //Hydreigon_Lord
  2. //Kahoot Simulation
  3.  
  4. #include <iostream>
  5. #include <fstream>
  6. #include <random>
  7. #include <cmath>
  8. #include <string>
  9. #include <vector>
  10. #include <iomanip>
  11. using namespace std;
  12.  
  13. /* Global constants */
  14. const double QUESTION_TIME_LIMIT = 20;
  15. const double MEDIAN_KNOWLEDGE = 0.7;
  16. const double MEDIAN_SPEED = 0.7;
  17. const double MEDIAN_ACCURACY = 0.95;
  18. const string PLAYER_FILENAME = "players.txt";
  19.  
  20. mt19937_64* en;  //Pointer to global random engine
  21. exponential_distribution<>* expdist;  //Pointer to an exponential distribution to be used as needed
  22.  
  23. /* Generates a random double in the [0,1) range. Equivalent to randRoot(0.5). */
  24. double randDec()
  25. {
  26.     return generate_canonical<double, 64>(*en);
  27. }
  28.  
  29. /* Generates a random double in the [0,1) range on a distribution with the median specified. */
  30. double randRoot(double median)
  31. {
  32.     double p = -log2(abs(median - 0.5) + 0.5);
  33.     double r = generate_canonical<double, 64>(*en);
  34.     if (median < 0.5)
  35.         return 1 - pow(1 - r, p);
  36.     else
  37.         return pow(r, p);
  38. }
  39.  
  40. /* Class to represent players in the simulation. */
  41. class Player
  42. {
  43. public:
  44.     /* Constructors */
  45.     Player() : score(0), streak(0)
  46.     {
  47.         numPlayers++;
  48.         knowledge = randRoot(MEDIAN_KNOWLEDGE);
  49.         speed = randRoot(MEDIAN_SPEED);
  50.         accuracy = randRoot(MEDIAN_ACCURACY);
  51.         name = "Player " + to_string(numPlayers);
  52.     }
  53.     Player(string n) : Player()
  54.     {
  55.         name = n;
  56.     }
  57.    
  58.     /* Getters */
  59.     unsigned getScore() const
  60.     {
  61.         return score;
  62.     }
  63.     unsigned getStreak() const
  64.     {
  65.         return streak;
  66.     }
  67.     unsigned getCorrect() const
  68.     {
  69.         unsigned c = 0;
  70.         for (bool r: responses)
  71.             c += r;
  72.         return c;
  73.     }
  74.     unsigned getIncorrect() const
  75.     {
  76.         return getNumQuestions() - getCorrect() - getTimeouts();
  77.     }
  78.     unsigned getTimeouts() const
  79.     {
  80.         unsigned timeouts = 0;
  81.         for (double t: responseTimes) {
  82.             if (t < 0) {
  83.                 timeouts++;
  84.             }
  85.         }
  86.         return timeouts;
  87.     }
  88.     unsigned getNumQuestions() const
  89.     {
  90.         return (unsigned)responses.size();
  91.     }
  92.     bool getResponseAt(unsigned questionNum) const
  93.     {
  94.         return responses[questionNum - 1];
  95.     }
  96.     double getResponseTimeAt(unsigned questionNum) const
  97.     {
  98.         return responseTimes[questionNum - 1];
  99.     }
  100.     unsigned getRankAt(unsigned questionNum) const
  101.     {
  102.         return ranks[questionNum - 1];
  103.     }
  104.     double getAvgResponseTime() const
  105.     {
  106.         unsigned count = 0;
  107.         double sum = 0;
  108.         for (double t: responseTimes) {
  109.             if (t > -1) {
  110.                 count++;
  111.                 sum += t;
  112.             }
  113.         }
  114.         if (count == 0)  //The player timed out on every question
  115.             return -1;
  116.         return sum / count;
  117.     }
  118.     int getLastRankChange() const
  119.     {
  120.         unsigned s = (unsigned)ranks.size();
  121.         if (s < 2) {
  122.             return 0;
  123.         }
  124.         return ranks[s - 2] - ranks[s - 1];
  125.     }
  126.     double getKnowledge() const
  127.     {
  128.         return knowledge;
  129.     }
  130.     double getSpeed() const
  131.     {
  132.         return speed;
  133.     }
  134.     double getAccuracy() const
  135.     {
  136.         return accuracy;
  137.     }
  138.     string getName() const
  139.     {
  140.         return name;
  141.     }
  142.    
  143.     /* Setter */
  144.     void setName(string n)
  145.     {
  146.         name = n;
  147.     }
  148.     void addRank(unsigned r)
  149.     {
  150.         ranks.push_back(r);
  151.     }
  152.    
  153.     /* This method updates this player's statistics according to the outcome of a random question with the specified time limit in seconds. */
  154.     void simQuestion(double timeLimit)
  155.     {
  156.         //Figure out how long it took for the player to answer
  157.         expdist = new exponential_distribution<>(speed);
  158.         double responseTime = (*expdist)(*en) * timeLimit / 10;
  159.         delete expdist;
  160.         responseTimes.push_back((responseTime <= timeLimit) ? responseTime : -1);
  161.         //Determine whether or not the player was correct
  162.         bool isCorrect;
  163.         if (responseTime > timeLimit) {  //timeout
  164.             isCorrect = false;
  165.         } else if (randDec() < knowledge && randDec() < accuracy) {
  166.             isCorrect = true;
  167.         } else {  //guessing or misclick
  168.             isCorrect = randDec() < 0.25;
  169.         }
  170.         responses.push_back(isCorrect);
  171.         //Update the player's score and answer streak
  172.         if (isCorrect) {
  173.             score += (int)round(500 + 100 * (streak > 5 ? 5 : streak) + 500 * (1 - responseTime / timeLimit));
  174.             streak++;
  175.         } else {
  176.             streak = 0;
  177.         }
  178.     }
  179.    
  180. private:
  181.     static int numPlayers;
  182.    
  183.     /* Player characteristics */
  184.     double knowledge, speed, accuracy;
  185.     string name;
  186.    
  187.     /* Player statistics */
  188.     unsigned score, streak;
  189.     vector<bool> responses;
  190.     vector<double> responseTimes;
  191.     vector<unsigned> ranks;
  192.    
  193. };
  194.  
  195. int Player::numPlayers = 0;
  196.  
  197. int main()
  198. {
  199.     //Seed random engine with a non-deterministic device if possible
  200.     random_device rd;
  201.     seed_seq seq{rd(), rd(), rd(), rd(), rd(), rd(), rd(), rd()};
  202.     en = new mt19937_64(seq);
  203.    
  204.     int playerCount, questionCount;
  205.     Player* players;
  206.     vector<string> playerNames;
  207.    
  208.     //Try to read the player list from the players.txt file
  209.     //The file should contain one player name per line
  210.     fstream fin(PLAYER_FILENAME, ios::in);
  211.     if (fin) {
  212.         cout << "File " << PLAYER_FILENAME << " found. Reading players from file...\n";
  213.         while (!fin.eof()) {
  214.             string n;
  215.             getline(fin, n);
  216.             playerNames.push_back(n);
  217.         }
  218.         playerCount = (int)playerNames.size();
  219.         cout << "Successfully read file.\n";
  220.         fin.close();
  221.     } else {
  222.         cout << "File " << PLAYER_FILENAME << " not found. How many players? ";
  223.         cin >> playerCount;
  224.     }
  225.     players = new Player[playerCount];
  226.     if (playerNames.size() == playerCount) {
  227.         for (int i = 0; i < playerCount; i++) {
  228.             players[i].setName(playerNames[i]);
  229.         }
  230.     }
  231.    
  232.     //Get the number of questions
  233.     cout << "How many questions? ";
  234.     cin >> questionCount;
  235.    
  236.     //Ask the user for two files to output the game report to
  237.     string filename;
  238.     cout << "Enter the filename for the game report: ";
  239.     cin >> filename;
  240.     fstream report_out(filename, ios::out);
  241.     report_out << "*** Kahoot Game Report ***\n\n";
  242.     if (!report_out) {
  243.         cout << "Error writing to file " << filename << endl;
  244.         exit(1);
  245.     }
  246.     cout << "Enter the filename for the detailed report: ";
  247.     cin >> filename;
  248.     fstream detail_out(filename, ios::out);
  249.     detail_out << "*** Kahoot Detailed Report ***\n\n";
  250.     if (!detail_out) {
  251.         cout << "Error writing to file " << filename << endl;
  252.         exit(2);
  253.     }
  254.     cout << "Successfully opened files for writing.\n";
  255.    
  256.     //Simulate the game
  257.     report_out << left << setprecision(5) << fixed << showpoint;
  258.     detail_out << left << setprecision(5) << fixed << showpoint;
  259.     //Report player stats
  260.     report_out << "\n=== Player Stats ===\n";
  261.     detail_out << "\n=== Player Stats ===\n";
  262.     detail_out << setw(40) << "Name" << setw(10) << "Knowledge" << setw(10) << "Speed" << setw(10) << "Accuracy" << setw(10) << "Stat Total" << endl;
  263.     detail_out << string(80, '-') << endl;
  264.     double topKnowledge = -1, topSpeed = -1, topAccuracy = -1, topStatTotal = -1;
  265.     string topKnowledgePlayer = "", topSpeedPlayer = "", topAccuracyPlayer = "", topStatTotalPlayer = "";
  266.     double lowKnowledge = 2, lowSpeed = 2, lowAccuracy = 2, lowStatTotal = 7;
  267.     string lowKnowledgePlayer = "", lowSpeedPlayer = "", lowAccuracyPlayer = "", lowStatTotalPlayer = "";
  268.     for (int p = 0; p < playerCount; p++) {
  269.         detail_out << setw(40) << players[p].getName();
  270.         detail_out << setw(10) << players[p].getKnowledge();
  271.         if (players[p].getKnowledge() > topKnowledge) {
  272.             topKnowledge = players[p].getKnowledge();
  273.             topKnowledgePlayer = players[p].getName();
  274.         }
  275.         if (players[p].getKnowledge() < lowKnowledge) {
  276.             lowKnowledge = players[p].getKnowledge();
  277.             lowKnowledgePlayer = players[p].getName();
  278.         }
  279.         detail_out << setw(10) << players[p].getSpeed();
  280.         if (players[p].getSpeed() > topSpeed) {
  281.             topSpeed = players[p].getSpeed();
  282.             topSpeedPlayer = players[p].getName();
  283.         }
  284.         if (players[p].getSpeed() < lowSpeed) {
  285.             lowSpeed = players[p].getSpeed();
  286.             lowSpeedPlayer = players[p].getName();
  287.         }
  288.         detail_out << setw(10) << players[p].getAccuracy();
  289.         if (players[p].getAccuracy() > topAccuracy) {
  290.             topAccuracy = players[p].getAccuracy();
  291.             topAccuracyPlayer = players[p].getName();
  292.         }
  293.         if (players[p].getAccuracy() < lowAccuracy) {
  294.             lowAccuracy = players[p].getAccuracy();
  295.             lowAccuracyPlayer = players[p].getName();
  296.         }
  297.         double statTotal = players[p].getKnowledge() + players[p].getSpeed() + players[p].getAccuracy();
  298.         detail_out << setw(10) << statTotal;
  299.         if (statTotal > topStatTotal) {
  300.             topStatTotal = statTotal;
  301.             topStatTotalPlayer = players[p].getName();
  302.         }
  303.         if (statTotal < lowStatTotal) {
  304.             lowStatTotal = statTotal;
  305.             lowStatTotalPlayer = players[p].getName();
  306.         }
  307.         detail_out << endl;
  308.     }
  309.     report_out << topKnowledgePlayer << " has the highest Knowledge stat, at " << topKnowledge << ".\n";
  310.     report_out << topSpeedPlayer << " has the highest Speed stat, at " << topSpeed << ".\n";
  311.     report_out << topAccuracyPlayer << " has the highest Accuracy stat, at " << topAccuracy << ".\n";
  312.     report_out << topStatTotalPlayer << " has the highest stat total, at " << topStatTotal << ".\n";
  313.     report_out << lowKnowledgePlayer << " has the lowest Knowledge stat, at " << lowKnowledge << ".\n";
  314.     report_out << lowSpeedPlayer << " has the lowest Speed stat, at " << lowSpeed << ".\n";
  315.     report_out << lowAccuracyPlayer << " has the lowest Accuracy stat, at " << lowAccuracy << ".\n";
  316.     report_out << lowStatTotalPlayer << " has the lowest stat total, at " << lowStatTotal << ".\n";
  317.     report_out << setprecision(3);
  318.     detail_out << setprecision(3);
  319.     for (int q = 1; q <= questionCount; q++) {
  320.         //Write the question header to the reports
  321.         cout << "Simulating question " << q << " of " << questionCount << ".\n";
  322.         report_out << "\n=== Question " << q << " of " << questionCount << " ===\n";
  323.         detail_out << "\n=== Question " << q << " of " << questionCount << " ===\n";
  324.         detail_out << setw(8) << "Rank" << setw(40) << "Name" << setw(8) << "Time" << setw(8) << "Correct" << setw(8) << "Score" << setw(8) << "Streak" << endl;
  325.         detail_out << string(80, '-') << endl;
  326.         //Simulate the question
  327.         int numCorrect = 0;
  328.         for (int p = 0; p < playerCount; p++) {
  329.             players[p].simQuestion(QUESTION_TIME_LIMIT);
  330.             if (players[p].getResponseAt(q)) {
  331.                 numCorrect++;
  332.             }
  333.         }
  334.         //Sort the player array (selection sort is used here)
  335.         for (int i = 0; i < playerCount - 1; i++) {
  336.             int maxScore = players[i].getScore();
  337.             int maxIndex = i;
  338.             for (int j = i + 1; j < playerCount; j++) {
  339.                 if (players[j].getScore() > maxScore) {
  340.                     maxScore = players[j].getScore();
  341.                     maxIndex = j;
  342.                 }
  343.             }
  344.             Player temp = players[i];
  345.             players[i] = players[maxIndex];
  346.             players[maxIndex] = temp;
  347.         }
  348.         //Update player ranks
  349.         unsigned lastScore = players[0].getScore();
  350.         unsigned lastRank = 1;
  351.         players[0].addRank(1);
  352.         for (int p = 1; p < playerCount; p++) {
  353.             if (players[p].getScore() == lastScore) {
  354.                 players[p].addRank(lastRank);
  355.             } else {
  356.                 lastScore = players[p].getScore();
  357.                 lastRank = p + 1;
  358.                 players[p].addRank(lastRank);
  359.             }
  360.         }
  361.         //Report the question results in detail
  362.         for (int p = 0; p < playerCount; p++) {
  363.             detail_out << setw(8) << players[p].getRankAt(q);
  364.             detail_out << setw(40) << players[p].getName();
  365.             double time = players[p].getResponseTimeAt(q);
  366.             if (time < 0) {
  367.                 detail_out << setw(8) << "Timeout";
  368.             } else {
  369.                 detail_out << setw(8) << time;
  370.             }
  371.             detail_out << setw(8) << (players[p].getResponseAt(q) ? 'Y' : 'N');
  372.             detail_out << setw(8) << players[p].getScore();
  373.             detail_out << setw(8) << players[p].getStreak();
  374.             detail_out << endl;
  375.         }
  376.         detail_out << string(80, '-') << endl;
  377.         //Report the rank changes and point differentials in detail
  378.         int highestClimb = 0, highestFall = 0;
  379.         string highestClimbPlayer = "", highestFallPlayer = "";
  380.         for (int p = 0; p < playerCount; p++) {
  381.             detail_out << players[p].getName() << " ";
  382.             if (q > 1) {
  383.                 int rankChange = players[p].getLastRankChange();
  384.                 if (rankChange > 0) {
  385.                     detail_out << "moved up " << rankChange << " rank" << (rankChange > 1 ? "s" : "");
  386.                 } else if (rankChange < 0) {
  387.                     detail_out << "moved down " << -rankChange << " rank" << (rankChange < -1 ? "s" : "");
  388.                 } else {
  389.                     detail_out << "did not change rank";
  390.                 }
  391.                 if (rankChange > highestClimb) {
  392.                     highestClimb = rankChange;
  393.                     highestClimbPlayer = players[p].getName();
  394.                 }
  395.                 if (-rankChange > highestFall) {
  396.                     highestFall = -rankChange;
  397.                     highestFallPlayer = players[p].getName();
  398.                 }
  399.                 detail_out << " and ";
  400.             }
  401.             if (p == 0) {
  402.                 detail_out << "is in the lead";
  403.             } else {
  404.                 unsigned diff = players[p - 1].getScore() - players[p].getScore();
  405.                 if (diff == 0) {
  406.                     detail_out << "is tied with " << players[p - 1].getName();
  407.                 } else {
  408.                     detail_out << "is " << diff << " point" << (diff > 1 ? "s" : "") << " behind " << players[p - 1].getName();
  409.                 }
  410.             }
  411.             detail_out << ".\n";
  412.         }
  413.         //Report the correct answer rate and the top and bottom 5
  414.         report_out << numCorrect << " out of " << playerCount << " players answered this question correctly.\n";
  415.         if (playerCount <= 10) {
  416.             for (int p = 0; p < playerCount; p++) {
  417.                 report_out << players[p].getRankAt(q) << ". " << players[p].getName() << " " << players[p].getScore() << endl;
  418.             }
  419.         } else {
  420.             for (int p = 0; p < 5; p++) {
  421.                 report_out << players[p].getRankAt(q) << ". " << players[p].getName() << " " << players[p].getScore() << endl;
  422.             }
  423.             report_out << "...\n";
  424.             for (int p = playerCount - 5; p < playerCount; p++) {
  425.                 report_out << players[p].getRankAt(q) << ". " << players[p].getName() << " " << players[p].getScore() << endl;
  426.             }
  427.         }
  428.         //Report the highest climber and highest faller
  429.         if (highestClimb > 0) {
  430.             report_out << highestClimbPlayer << " is the highest climber, having moved up " << highestClimb << " rank" << (highestClimb > 1 ? "s" : "") << ".\n";
  431.         }
  432.         if (highestFall > 0) {
  433.             report_out << highestFallPlayer << " is the highest faller, having moved down " << highestFall << " rank" << (highestFall > 1 ? "s" : "") << ".\n";
  434.         }
  435.         //Report interesting answer streaks
  436.         unsigned highestStreak = 0, perfectStreaks = 0;
  437.         string highestStreakPlayer = "", comebackPlayer = "";
  438.         for (int p = 0; p < playerCount; p++) {
  439.             if (players[p].getStreak() > highestStreak) {
  440.                 highestStreak = players[p].getStreak();
  441.                 highestStreakPlayer = players[p].getName();
  442.             }
  443.             if (players[p].getStreak() == q) {
  444.                 perfectStreaks++;
  445.             }
  446.             if (q > 3 && players[p].getStreak() == 3) {
  447.                 comebackPlayer = players[p].getName();
  448.             }
  449.         }
  450.         if (perfectStreaks > 1) {
  451.             report_out << perfectStreaks << " players are still perfect at answer streak " << q << ".\n";
  452.         } else if (perfectStreaks == 1) {
  453.             report_out << highestStreakPlayer << " is still perfect at answer streak " << q << ".\n";
  454.         } else if (highestStreak > 0) {
  455.             report_out << highestStreakPlayer << " has the highest answer streak, at " << highestStreak << ".\n";
  456.         } else {
  457.             report_out << "No-one has an answer streak.\n";
  458.         }
  459.         if (!comebackPlayer.empty()) {
  460.             report_out << "With an answer streak of 3, " << comebackPlayer << " could be staging a comeback.\n";
  461.         }
  462.     }
  463.    
  464.     //Report the final results
  465.     report_out << "\n=== Final Results ===\n";
  466.     cout << left << setw(8) << "Rank" << setw(40) << "Name" << setw(8) << "Score" << setw(8) << "Correct" << endl;
  467.     report_out << setw(8) << "Rank" << setw(40) << "Name" << setw(8) << "Score" << setw(8) << "Correct" << endl;
  468.     cout << string(64, '-') << endl;
  469.     report_out << string(64, '-') << endl;
  470.     for (int p = 0; p < playerCount; p++) {
  471.         cout << setw(8) << players[p].getRankAt(questionCount);
  472.         cout << setw(40) << players[p].getName();
  473.         cout << setw(8) << players[p].getScore();
  474.         cout << setw(8) << players[p].getCorrect();
  475.         cout << endl;
  476.         report_out << setw(8) << players[p].getRankAt(questionCount);
  477.         report_out << setw(40) << players[p].getName();
  478.         report_out << setw(8) << players[p].getScore();
  479.         report_out << setw(8) << players[p].getCorrect();
  480.         report_out << endl;
  481.     }
  482.    
  483.     //Report detailed statistics
  484.     detail_out << "\n=== Final Statistics ===\n";
  485.     detail_out << setw(8) << "Rank" << setw(40) << "Name" << setw(questionCount > 7 ? questionCount + 1 : 8) << "Answers" << setw(8) << "AvgTime" << setw(8) << "Timeout" << setw(8) << "Corr%" << endl;
  486.     detail_out << string(questionCount > 7 ? questionCount + 73 : 80, '-') << endl;
  487.     for (int p = 0; p < playerCount; p++) {
  488.         detail_out << setw(8) << players[p].getRankAt(questionCount);
  489.         detail_out << setw(40) << players[p].getName();
  490.         string answers = "";
  491.         for (int q = 1; q <= questionCount; q++) {
  492.             if (players[p].getResponseTimeAt(q) < 0) {
  493.                 answers += "T";
  494.             } else if (players[p].getResponseAt(q)) {
  495.                 answers += "Y";
  496.             } else {
  497.                 answers += "N";
  498.             }
  499.         }
  500.         detail_out << setw(questionCount > 7 ? questionCount + 1 : 8) << answers;
  501.         double avgResponseTime = players[p].getAvgResponseTime();
  502.         if (avgResponseTime < 0) {
  503.             detail_out << setw(8) << "N/A";
  504.         } else {
  505.             detail_out << setw(8) << avgResponseTime;
  506.         }
  507.         detail_out << setw(8) << players[p].getTimeouts();
  508.         if (avgResponseTime >= 0) {
  509.             detail_out << setw(8) << 100.0 * players[p].getCorrect() / (questionCount - players[p].getTimeouts());
  510.         }
  511.         detail_out << endl;
  512.     }
  513.    
  514.     //Cleanup
  515.     delete en;
  516.     delete[] players;
  517.     return 0;
  518. }
Add Comment
Please, Sign In to add comment