Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /******************************************************************
  2. *   21 days: a game for programmers
  3. *   Copyright (C) 2014 Maxim Grishin
  4. *
  5. *   This program is free software; you can redistribute it and/or
  6. *   modify it under the terms of the GNU General Public License
  7. *   as published by the Free Software Foundation; either version 2
  8. *   of the License, or (at your option) any later version.
  9. *
  10. *   This program is distributed in the hope that it will be useful,
  11. *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. *   GNU General Public License for more details.
  14. *
  15. *   You should have received a copy of the GNU General Public License
  16. *   along with this program; if not, write to the Free Software
  17. *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  18. *   MA  02110-1301, USA.
  19. *******************************************************************/
  20.  
  21. #define DEFAULT_PATH  0
  22. #define WORK_PATH     1
  23. #define STARTUP_PATH  2
  24.  
  25. #include "sys.h"
  26. #include "strings.h"
  27. #include "interface.h"
  28. #include "pq.h"
  29.  
  30. #ifdef _KOS32
  31.     #include <menuet/os.h>
  32.     #define vector vector21
  33. extern "C" {
  34.     void *__dso_handle = 0;
  35.     void *__cxa_atexit = 0;
  36.     void *__stack_chk_fail = 0;
  37.     }
  38. #else
  39.     #define printf2 printf
  40.     #include <stdlib.h>  //srand
  41.     #include <vector>
  42.     using std::vector;
  43.     #include <unistd.h>  // usleep
  44. #endif
  45.  
  46. #include <time.h>
  47. #include <math.h>
  48. using std::string;
  49.  
  50. Undo2 history;
  51.  
  52. /**************************************
  53. *   Player
  54. *****************************************/
  55. long double playerMoney = 100;
  56. int playerSalary = 0;
  57. int playerSalaryFirstDay = 0;
  58. int playerPath = DEFAULT_PATH;
  59. int playerKarma = 20;
  60. double dt = 1.0; // days per secons
  61. long double gameTime = 0;   // Time in days
  62. double unreadMessages = 0;
  63. unsigned int timeHops = 0;
  64. bool allAchievesShowed = false;
  65. bool charityUnlocked = false;
  66.  
  67. int  playerFreelanceAttempts = 0;
  68. bool controlFreelanceAttempts = false;
  69.  
  70. bool sentBotsmannLetter = false;
  71. bool astraLetterSent = false;
  72. bool shitCodeDetected = false;
  73. bool shitLettersSent[3] = {0,0,0};
  74. bool shitLettersFinished[3] = {0,0,0};
  75. bool showCoursesTab = false;
  76. PQ3 pq;  // events priority queue
  77. bool  noPopularity = false;
  78. short finalCardsUnlocked = 0;
  79. bool  finalCardUnlocked[5] = {0,0,0,0,0};
  80. bool  returnTo21HintShowed = false;
  81. // Player Stats
  82. time_t playerStartedPlaying;
  83. unsigned long playerTimeHops = 0;
  84. unsigned long playerMessagesRead = 0;
  85. unsigned long playerHelped = 0;
  86. unsigned long playerDidntHelped = 0;
  87. long double playerSpentRealDays = 0;
  88. long double playerPrevUndo = 0;
  89. long double playerMoneyEarned = 100;
  90. long double playerMoneySpent = 0;
  91. double playerMoneySpentForCharity = 0;
  92.  
  93.  
  94. void mainGameCycle();
  95. int  showSettingsScreen();
  96. int  chooseCourse();
  97. void showAchievesScreen();
  98.  
  99. void startNewGame();
  100. void initSendingMessage();
  101. void undo(long double toTime);
  102.  
  103. string getStatusLine();
  104.  
  105. void addTimer(event e) {
  106.     if (e.type == SPECIAL_LETTER &&
  107.         (e.idata == LETTER_SHITCODE_1
  108.         || e.idata == LETTER_SHITCODE_2
  109.         || e.idata == LETTER_SHITCODE_3)
  110.     && pq.containsType(e.type, e.idata))
  111.         return;
  112.     if (e.type == MESSAGE && pq.containsType((int)e.type))
  113.         return;
  114.  
  115.     pq.insert(e);
  116.     history.insert(event(e.time + gameTime, e.type, e.idata));
  117.     }
  118.  
  119. void unlockFinalCards(short p) {
  120.     if (!finalCardUnlocked[p])
  121.         finalCardsUnlocked++;
  122.  
  123.     finalCardUnlocked[p] = true;
  124.     }
  125.  
  126. void increaseMoney(long double m) {
  127.     playerMoney += m;
  128.     if (m > 0)
  129.         playerMoneyEarned += m;
  130.     if (m < 0)
  131.         playerMoneySpent -= m;
  132.     }
  133.  
  134. void increaseKarma(int k, bool addTimerMsg = true) {
  135.     if (playerKarma <= 0 && playerKarma+k>0)
  136.         initSendingMessage();
  137.     int oldKarma = playerKarma;
  138.     playerKarma += k;
  139.     if (playerKarma < -100)
  140.         playerKarma = -100;
  141.  
  142.     if (playerKarma > 100)
  143.         playerKarma = 100;
  144.  
  145.     if (addTimerMsg && playerKarma - oldKarma != 0)
  146.         history.insert(event(2*dt, INCREASEPOPULARITY, playerKarma - oldKarma));
  147.     }
  148.  
  149. void changePath(int to) {
  150.     history.insert(event(gameTime, CHANGEPATH, playerPath));
  151.     playerPath = to;
  152.     }
  153.  
  154. void changeSalary(int to) {
  155.     history.insert(event(gameTime, CHANGESALARY, playerSalary));
  156.     playerSalary = to;
  157.     }
  158.  
  159. void changeSalaryFirstDay(int to) {
  160.     history.insert(event(gameTime, CHANGESALARYFIRSTDAY, playerSalaryFirstDay));
  161.     playerSalaryFirstDay = to;
  162.     }
  163.  
  164. int karma() {
  165.     return playerKarma;
  166.     }
  167.  
  168. void initSendingMessage() {
  169.     double newTime = (karma() <= 0)?0.0:0.5 ;//* karma() / 100.0;
  170.     if (newTime > 0) {
  171.         addTimer(event(newTime, MESSAGE));
  172.         }
  173.     else {
  174.         noPopularity = true;
  175.         addTimer(event(0, NOPOPULARITY));
  176.         }
  177.     }
  178.  
  179. void checkAchieves() {
  180.     /// No popularity
  181.     if (!achievesPicked[1] && noPopularity)
  182.         achievesPicked[1] = true;
  183.     /// Millionaire
  184.     if (!achievesPicked[3] && playerMoney >= 1000000.0) {
  185.         achievesPicked[3] = true;
  186.         charityUnlocked = true;
  187.         }
  188.     /// Time jumper
  189.     if (!achievesPicked[8] && timeHops >= 3)
  190.         achievesPicked[8] = true;
  191.  
  192.     allAchievesShowed = true;
  193.     /// Show achieve notification
  194.     for (unsigned int i = 0; i < achievesNumber; i++) {
  195.         if (achievesPicked[i] && !achievesShowed[i]) {
  196.             drawModalWindow(achieves[i], newAchievement);
  197.             wait();
  198.             if (i == 3) {
  199.                 drawModalWindow(pressRForCharity, 0, "[R]");
  200.                 wait('r','R');
  201.                 }
  202.             achievesShowed[i] = true;
  203.             }
  204.         else if (!achievesShowed[i])
  205.             allAchievesShowed = false;
  206.         }
  207.     }
  208.  
  209. void showMainMenu() {
  210.     string s = days21;
  211.     s+=mainMenuText;
  212.     drawWindow(s, gameName, "", txt("%s    %s", one_or_two, developer));
  213.     char menu = getAnswer('1', '2');
  214.  
  215.     switch(menu) {
  216.         case '1':
  217.             startNewGame();
  218.         break;
  219.         case '2': // TODO: Save?
  220.  
  221.         break;
  222.         }
  223.     }
  224.  
  225. void showAchievesScreen() {
  226.     string s;
  227.     for (unsigned short i = 0; i < achievesNumber; i++)
  228.         s += txt("[%c] %s \n", achievesPicked[i]?'X':' ', achieves[i]);
  229.  
  230.     s += txt(finalCardsUnlockedText, finalCardsUnlocked, 5);
  231.     char totalCoursesCount = 12;
  232.     s += txt(coursesFinishedText, (int)finishedCoursesCount, (int)totalCoursesCount);
  233.     drawWindow(s, achievementsTitle, getStatusLine(), enter);
  234.     }
  235.  
  236. string printExactTask(int course, int task, int word) {
  237.     switch (course) {
  238.         case 1:
  239.         case 2:
  240.             return string(programmingTasks[task]);
  241.             break;
  242.         case 3:
  243.         case 4:
  244.             return string(mobileTasks[task]);
  245.             break;
  246.         case 5:
  247.             return string(webProgrammingTasks[task]);
  248.             break;
  249.         case 6:
  250.             return string(systemProgrammingTask[task]);
  251.             break;
  252.         case 7:
  253.             return string(adminTasks[task]);
  254.             break;
  255.         case 0:
  256.         default:
  257.             return txt(level1Task[task], level1TaskWords[word]);
  258.             break;
  259.         }
  260.     }
  261.  
  262. void showLearnDialog() {
  263.     string s = selectCourseText;
  264.     for (unsigned short i = 0; i < coursesNumber; i++) {
  265.         if (!coursesUnlocked[i])
  266.             continue;
  267.  
  268.         if (i == 2) {
  269.             s+=txt("3.%s\n", courses[2]);
  270.             for (int j = 0; j < advancedProgrammingCourses; j++) {
  271.                 s+=txt("     %c.%s [%c]", (char)('a'+j), advProgCourses[j], (advProgCoursesFinished[j])?'X':' ');
  272.                 if (!advProgCoursesFinished[j] && !pq.hasCourse('a'+j))
  273.                     s+=txt(takeCourseCost, advProgLearningCost[j]);
  274.                 else if (pq.hasCourse('a'+j))
  275.                     s+=takingThisCourse;
  276.                 else
  277.                     s+="\n";
  278.             }
  279.             continue;
  280.         }
  281.  
  282.         s+=txt("%c.%s [%c]", (char)('1'+i), courses[i], (coursesFinished[i])?'X':' ');
  283.         if (!coursesFinished[i] && !pq.hasCourse(i))
  284.             s+=txt(takeCourseCost, learningCost[i]);
  285.         else if (pq.hasCourse(i)) {
  286.             s+=takingThisCourse;
  287.         }
  288.         else
  289.             s+="\n";
  290.     }
  291.  
  292.     drawWindow(s, coursesTitle ,"", number_or_enter);
  293.     unsigned char answer = 0;
  294.     answer = getKey();
  295.  
  296.     if (answer == ENTER_KEY)
  297.         return;
  298.  
  299.     else if (answer >='1' && answer <=(char)('0'+coursesNumber) && answer != '3') {
  300.         answer -- ;
  301.         answer -='0';
  302.  
  303.         if (coursesUnlocked[answer]){
  304.             if (coursesFinished[answer]) {
  305.                 drawModalWindow(courseAlreadyPassed, errorMsg);
  306.                 wait();
  307.                 showLearnDialog();
  308.                 return;
  309.                 }
  310.             else if (playerMoney > learningCost[answer] && !pq.hasCourse(answer)) {
  311.                 drawModalWindow(txt(successfullyEnrolled, courses[answer]).c_str(), congratsMsg);
  312.                 wait();
  313.                 increaseMoney(-learningCost[answer]);
  314.                 addTimer(event(learningTime[answer], COURSE, answer));
  315.                 }
  316.             else if (playerMoney <= learningCost[answer] && !pq.hasCourse(answer)){
  317.                 drawModalWindow(notEnoughMoney, errorMsg);
  318.                 wait();
  319.                 showLearnDialog();
  320.                 }
  321.             else
  322.                 showLearnDialog();
  323.             return;
  324.             }
  325.         else
  326.             showLearnDialog();
  327.         }
  328.     else if (answer == '3' && coursesUnlocked[2]) {
  329.         drawModalWindow(ae_advanced_courses,errorMsg);
  330.         wait();
  331.         showLearnDialog();
  332.         }
  333.     /// only small letters
  334.     else if (answer >='a' && answer <= 'e') {
  335.         answer -= 'a';
  336.         if (coursesUnlocked[2] && playerMoney > advProgLearningCost[answer] && !advProgCoursesFinished[answer] && !pq.hasCourse('a'+answer)) {
  337.             drawModalWindow(txt(successfullyEnrolled,advProgCourses[answer]).c_str(), congratsMsg);
  338.             wait();
  339.  
  340.             increaseMoney(-advProgLearningCost[answer]);
  341.             int learnCourseNumber = answer+'a';
  342.             if (advProgLearningTime[answer] != 0 && learnCourseNumber != -1)
  343.                 addTimer(event(advProgLearningTime[answer], COURSE, learnCourseNumber));
  344.             return;
  345.             }
  346.         else if (coursesUnlocked[2] && playerMoney <= advProgLearningCost[answer] && !advProgCoursesFinished[answer] && !pq.hasCourse('a'+answer)) {
  347.             drawModalWindow(notEnoughMoney, errorMsg);
  348.             wait();
  349.             showLearnDialog();
  350.             }
  351.         else if (advProgCoursesFinished[answer]) {
  352.             drawModalWindow(courseAlreadyPassed, errorMsg);
  353.             wait();
  354.             showLearnDialog();
  355.             return;
  356.             }
  357.         else
  358.             showLearnDialog();
  359.         }
  360.     else if (answer == '3') {
  361.         showLearnDialog();
  362.         }
  363.     else if (coursesUnlocked[2]) {
  364.         drawModalWindow(ae_advanced_courses,errorMsg);
  365.         wait();
  366.         showLearnDialog();
  367.         }
  368.     else
  369.         showLearnDialog();
  370.     return;
  371.     }
  372.  
  373. void startNewGame() {
  374.     // Intro
  375.     drawWindow(gameIntroTextPart1, introTitle, "", enter, true);
  376.     wait();
  377.     drawWindow(gameIntroTextPart2, introTitle, "", enter, true);
  378.     wait();
  379.     drawWindow(gameIntroTextPart3, introTitle, "", enter, true);
  380.     wait();
  381.     // Goals
  382.     drawWindow(gameIntroPlan, 0, "", enter, true);
  383.     wait();
  384.     // Desktop
  385.     drawWindow("", introDesktop, "", enter);
  386.     wait();
  387.     // Top line
  388.     drawWindow(introStatusLine,"", getStatusLine(), enter);
  389.     wait();
  390.     // Keys
  391.     drawWindow("", "", "", introAllowedKeys);
  392.     wait('1');
  393.     // Dialog window
  394.     drawModalWindow(introFinished, introLetsBegin);
  395.     wait();
  396.     // Start game!
  397.     mainGameCycle();
  398.     }
  399.  
  400. void showFinalStats() {
  401.     // Show player stats
  402.     int minutesInGame = (time(NULL) - playerStartedPlaying)/60;
  403.     playerSpentRealDays += (gameTime - playerPrevUndo);
  404.     long unsigned int val[9] = {
  405.         static_cast<long unsigned int>(playerSpentRealDays),
  406.         static_cast<long unsigned int>(minutesInGame),
  407.         playerMessagesRead,
  408.         playerHelped,
  409.         playerDidntHelped,
  410.         static_cast<long unsigned int>(playerMoneyEarned),
  411.         static_cast<long unsigned int>(playerMoneySpent),
  412.         static_cast<long unsigned int>(playerMoneySpentForCharity),
  413.         playerTimeHops};
  414.     string s = "\n<c>";
  415.     s += playerStatsTitle;
  416.     for (int i = 0; i < 9; i++) {
  417.         s += txt(playerStats[i], static_cast<int>(val[i]));
  418.         if (i == 0 || i == 3 || i == 4)
  419.             s += getWordEnding(val[i], i);
  420.         }
  421.     s += playerStatsEnd;
  422.     drawWindow(s);
  423.     wait(ESCAPE_KEY, ESCAPE_KEY);
  424.     }
  425.  
  426. bool isGameOver() {
  427.     if (noPopularity && !finalCardUnlocked[3] && !pq.containsType(SPECIAL_LETTER, LETTER_FINALPATH_NOPOPULARITY)) {
  428.         addTimer(event(2.0, SPECIAL_LETTER, LETTER_FINALPATH_NOPOPULARITY));
  429.         return false;
  430.         }
  431.  
  432.     if ((playerPath == DEFAULT_PATH || playerPath == STARTUP_PATH) && playerMoney < 0.0 && !finalCardUnlocked[4]  && !pq.containsType(SPECIAL_LETTER, LETTER_FINALPATH_NOMONEY)) {
  433.         addTimer(event(2.0, SPECIAL_LETTER, LETTER_FINALPATH_NOMONEY));
  434.         return false;
  435.         }
  436.     // You win!
  437.     if (finishedCoursesCount >= 12 && finalCardsUnlocked >= 5 && gameTime <= 22.0 && allAchievesShowed) {
  438.         std::string s = youWin;
  439.         s += gameOver;
  440.         drawWindow(s, congratsMsg, "", pressF);
  441.         wait('f','F');
  442.         showFinalStats();
  443.         return true;
  444.         }
  445.     // Game over
  446.     if (playerMoney < 0 && karma() < 0 && noPopularity && floor(unreadMessages) == 0 && gameTime > 22.0 &&
  447.         achievesShowed[1] && finalCardUnlocked[3] && finalCardUnlocked[4] &&
  448.         !pq.containsType(SPECIAL_LETTER) && !pq.containsType(MESSAGE)) {
  449.         string s = gameOverLogo;
  450.         s += gameOverText;
  451.         drawWindow(s, gameOverTitle, "", pressF);
  452.         wait('f','F');
  453.         showFinalStats();
  454.         return true;
  455.         }
  456.     return false;
  457.     }
  458.  
  459. int getAvailableCoursesCount() {
  460.     int a = 0;
  461.     for (unsigned int i = 0; i < coursesNumber; i++) {
  462.         if (i!= 2 && coursesUnlocked[i] && !coursesFinished[i])
  463.             a++;
  464.         }
  465.     // Anvanced programming
  466.     if (coursesUnlocked[2]) {
  467.         for (int i = 0; i < advancedProgrammingCourses; i++)
  468.             if (!advProgCoursesFinished[i])
  469.                 a++;
  470.         }
  471.     return a;
  472.     }
  473.  
  474. std::string getStatusLine() {
  475.     string s = txt(statusLine, (int)gameTime, (int)playerMoney, karma(), (int)floor(unreadMessages));
  476.     if (showCoursesTab)
  477.         s += txt(statusLineCoursesNotFinished, getAvailableCoursesCount());
  478.     return s;
  479.     }
  480.  
  481. string getBottomLine() {
  482.     string s;
  483.     if (showCoursesTab)
  484.         s += bottomLineCourses;
  485.     s += bottomLineMsgAchieves;
  486.     if (charityUnlocked) {
  487.         s += charityTitle;
  488.         s += "[R]    ";
  489.         }
  490.     s += bottomLineSpeedAndExit;
  491.     return s;
  492.     }
  493.  
  494. int showUnreadMessages() {
  495.     int newTaskLevel = chooseCourse();
  496.  
  497.     unsigned int task = rand() % taskCount[newTaskLevel];
  498.     unsigned int word = rand() % level1TaskWordNumber;
  499.     unsigned int yes =  rand() % levelYesAnswerNumber;
  500.     unsigned int no  =  rand() % levelNoAnswerNumber;
  501.  
  502.     string s = printExactTask(newTaskLevel, task, word);
  503.     s += txt(yourAnswer,levelYesAnswer[yes],levelNoAnswer[no]);
  504.     string buttons = one_or_two;
  505.     buttons += escToBreakReading;
  506.     drawWindow(s, unread_message, getStatusLine(), buttons);
  507.     return newTaskLevel;
  508.     }
  509.  
  510. bool messageGetAnswer(int level) {
  511.     long double reward = taskReward[level] + rand() % 20;
  512.     char answerKey = getAnswer('1', '2', ESCAPE_KEY);
  513.  
  514.     switch (answerKey) {
  515.         case '1':
  516.             // Player Stats
  517.             playerHelped++;
  518.             increaseMoney(reward);
  519.             increaseKarma(reward/5.0, false);
  520.             if (playerPath == WORK_PATH && controlFreelanceAttempts) {
  521.                 playerFreelanceAttempts++;
  522.                 if (playerFreelanceAttempts > 5) {
  523.                     addTimer(event(1.0, SPECIAL_LETTER, LETTER_ANGRYBOSS_2));
  524.                     controlFreelanceAttempts = false;
  525.                     }
  526.                 }
  527.             // Repairing computer takes 0.5 days
  528.             gameTime += 0.5;
  529.             break;
  530.         case '2':
  531.             // Player Stats
  532.             playerDidntHelped++;
  533.             increaseKarma(-reward/2.0, false);
  534.             break;
  535.         case ESCAPE_KEY:
  536.             return true;
  537.             break;
  538.         }
  539.     return false;
  540.     }
  541.  
  542. double showChangeSpeedDialog(string e = "") {
  543.     string s;
  544.     if (dt >= 1.0)
  545.         s = txt(daysPerSecond, (int)dt);
  546.     else {
  547.         s = txt(daysPerSecond, 0);
  548.         s += txt(".%d", (int)(dt*10)%10); // Actually not the best way of printing double values
  549.         }
  550.     if (!e.empty())
  551.         s += e;
  552.     drawModalWindow(s.c_str(), changeSpeedTitle, changeSpeedButtons);
  553.     char ch = getAnswer('+','-','Y','y');
  554.     if (ch == 'y' || ch == 'Y')
  555.         return dt;
  556.     else if (ch == '+') {
  557.         if (dt >= 1.0)
  558.             dt += 1.0;
  559.         else
  560.             dt += 0.10;
  561.         if (dt >= 10.0) {
  562.             dt = 10.0;
  563.             return showChangeSpeedDialog(" max");
  564.             }
  565.         return showChangeSpeedDialog();
  566.         }
  567.     else if (ch == '-') {
  568.         if (dt >= 2.0)
  569.             dt -= 1.0;
  570.         else
  571.             dt -= 0.10;
  572.         if (dt <= 0.10) {
  573.             dt = 0.10;
  574.             return showChangeSpeedDialog(" min");
  575.             }
  576.         return showChangeSpeedDialog();
  577.         }
  578.     return dt;
  579.     }
  580.  
  581. // returns 1 if program have to be closed
  582. bool keyPressed(char k) {
  583.     switch (k) {
  584.         // Courses
  585.         case 'c':
  586.         case 'C':
  587.             if (!showCoursesTab)
  588.                 break;
  589.             showLearnDialog();
  590.             break;
  591.         // Achieves
  592.         case 'a':
  593.         case 'A':
  594.             showAchievesScreen();
  595.             wait();
  596.             break;
  597.         // Messages
  598.         case 'm':
  599.         case 'M': {
  600.             int oldPopularity = karma();
  601.             // Player Stats
  602.             bool breakReading = false;
  603.             while (floor(unreadMessages) > 0) {
  604.                 breakReading = messageGetAnswer(showUnreadMessages());
  605.                 if (breakReading) {
  606.                     break;
  607.                     }
  608.                 else {
  609.                     if (floor(unreadMessages) > 0.0) unreadMessages--;
  610.                     playerMessagesRead++;
  611.                     }
  612.                 }
  613.             if (!breakReading) {
  614.                 drawModalWindow(noUnreadMessages);
  615.                 wait();
  616.                 }
  617.             if (karma() != oldPopularity)
  618.                 history.insert(event(gameTime, INCREASEPOPULARITY, karma()-oldPopularity));
  619.             // Hack
  620.             #ifdef _KOS32
  621.             initSendingMessage();
  622.             #endif
  623.             }
  624.             break;
  625.         case 'u':
  626.         case 'U':
  627.             undo(gameTime - 10);
  628.             break;
  629.         case 'r':
  630.         case 'R': {
  631.             if (!charityUnlocked)
  632.                 break;
  633.  
  634.             if (playerMoney <= 0) {
  635.                 drawModalWindow(notEnoughMoneyForCharity, 0, enter);
  636.                 wait();
  637.                 break;
  638.                 }
  639.             drawModalWindow(charityQuestion, charityTitle, yesNoDialog);
  640.             char ch = getAnswer('y', 'n', 'Y', 'N');
  641.             if (ch == 'y' || ch == 'Y') {
  642.                 // Player Stats
  643.                 playerMoneySpentForCharity += (playerMoney / 2.0);
  644.  
  645.                 increaseMoney(-playerMoney / 2.0);
  646.                 increaseKarma(abs(karma()));
  647.                 }
  648.             }
  649.             break;
  650.         case 's':
  651.         case 'S':
  652.             dt = showChangeSpeedDialog();
  653.             break;
  654.         // Escape
  655.         case ESCAPE_KEY: {
  656.             drawModalWindow(doYouReallyWantToExit,0 , yesNoDialog);
  657.             char ans = getAnswer('y','Y','n','N');
  658.             if (ans == 'y' || ans == 'Y')
  659.                 return true;
  660.             }
  661.             break;
  662.         }
  663.     return false;
  664.     }
  665.  
  666. void breakingNews(int newsID) {
  667.     drawWindow(news[newsID], breaking_news, getStatusLine(), enter);
  668.     wait();
  669.     }
  670.  
  671. void checkPq() {
  672.     vector<event> lateEvents;
  673.     // Messages in PQ are in reverse order
  674.     for (int i = pq.n()-1; i >= 0; i--) {
  675.         event* e = pq.get(i);
  676.         if (e->time > dt) {
  677.             e->time -= dt;
  678.             continue;
  679.             }
  680.         switch (e->type) {
  681.             case COURSE: {
  682.                 string s;
  683.  
  684.                 if (e->idata >= 0 && e->idata < (int)coursesNumber) {
  685.                     s += txt(courseSuccessfullyFinished,courses[e->idata]);
  686.                     // Not the best fix for the bug with mobile dev
  687.                     if (!coursesFinished[e->idata])
  688.                         finishedCoursesCount++;
  689.                     coursesFinished[e->idata] = true;
  690.                     }
  691.                 // Advanced programming
  692.                 else if (e->idata >='a' && e->idata <= 'e') {
  693.                     e->idata -= 'a';
  694.                     s += txt(courseSuccessfullyFinished,advProgCourses[e->idata]);
  695.                     if (!advProgCoursesFinished[e->idata])
  696.                         finishedCoursesCount++;
  697.                     advProgCoursesFinished[e->idata] = true;
  698.                     }
  699.                 increaseKarma(+5);
  700.  
  701.                 pq.delMin();
  702.                 drawModalWindow(s.c_str(), congratsMsg);
  703.                 wait();
  704.                 }
  705.                 break;
  706.             case SPECIAL_LETTER:
  707.                 switch (e->idata) {
  708.                     case LETTER_RETURN_TO_DAY_21:
  709.                         drawModalWindow(returnToDay21, 0, pressP);
  710.                         wait('p','P');
  711.                         returnTo21HintShowed = true;
  712.                         break;
  713.                     case LETTER_SHITCODE_1:
  714.                     case LETTER_SHITCODE_2:
  715.                     case LETTER_SHITCODE_3: {
  716.                         if (playerPath == WORK_PATH)
  717.                             break;
  718.                         string s;
  719.                         int letterIndex = e->idata*2;
  720.                         s += specialLetters[letterIndex];
  721.                         s += shitCodeYourAnswer;
  722.                         drawWindow(s, new_letter, getStatusLine(), one_or_two);
  723.  
  724.                         char ch = getAnswer('1', '2');
  725.                         if (ch == '1') {
  726.                             if (e->idata == LETTER_SHITCODE_1 && advProgCoursesFinished[0] && advProgCoursesFinished[1]) {
  727.                                 // sussessfull
  728.                                 increaseMoney(300.0);
  729.                                 increaseKarma(+5);
  730.                                 drawWindow(txt(specialLetters[24], (int)gameTime, 300), answerLetter, getStatusLine(), enter);
  731.                                 shitLettersFinished[0] = true;
  732.                                 }
  733.                             else if (e->idata == LETTER_SHITCODE_2 && advProgCoursesFinished[2]) {
  734.                                 // sussessfull
  735.                                 increaseMoney(500.0);
  736.                                 increaseKarma(+5);
  737.                                 drawWindow(txt(specialLetters[24], (int)gameTime, 500), answerLetter, getStatusLine(), enter);
  738.                                 shitLettersFinished[1] = true;
  739.                                 }
  740.                             else if (e->idata == LETTER_SHITCODE_3 && advProgCoursesFinished[3]) {
  741.                                 // sussessfull
  742.                                 increaseMoney(800.0);
  743.                                 increaseKarma(+5);
  744.                                 drawWindow(txt(specialLetters[24], (int)gameTime, 800), answerLetter, getStatusLine(), enter);
  745.                                 shitLettersFinished[2] = true;
  746.                                 }
  747.                             else {
  748.                                 drawModalWindow(specialLetters[letterIndex+1], answerLetter);
  749.                                 coursesUnlocked[2] = true;
  750.                                 shitCodeDetected = true;
  751.                                 increaseKarma(-5);
  752.                                 }
  753.  
  754.                             if (shitLettersFinished[0] && shitLettersFinished[1] && shitLettersFinished[2])
  755.                                 achievesPicked[0] = true;
  756.  
  757.                             wait();
  758.                             }
  759.                         else if (ch == '2') {
  760.                             increaseKarma(-2);
  761.                             }
  762.                         }
  763.                         break;
  764.                     case LETTER_BOTSMANN:
  765.                         if (playerPath == DEFAULT_PATH) {
  766.                             string s = specialLetters[6];
  767.                             drawWindow(s, new_letter, getStatusLine(), enter);
  768.                             wait();
  769.                             lateEvents.push_back(event(10.0, SPECIAL_LETTER, LETTER_ASTRA));
  770.                             }
  771.                         break;
  772.                     case LETTER_ASTRA:
  773.                         if (playerPath == DEFAULT_PATH) {
  774.                             string s = specialLetters[7];
  775.                             s += shitCodeYourAnswer;
  776.  
  777.                             drawWindow(s, new_letter, getStatusLine(), one_or_two);
  778.                             char ch = getAnswer('1', '2');
  779.                             if (ch == '1') {
  780.                                 s = specialLetters[8];
  781.                                 drawWindow(s, new_letter, getStatusLine(), enter);
  782.                                 increaseKarma(-10);
  783.                                 }
  784.                             else if (ch == '2') {
  785.                                 s = specialLetters[9];
  786.                                 drawWindow(s, new_letter, getStatusLine(), enter);
  787.                                 // Unlock mobile development
  788.                                 coursesUnlocked[3] = true;
  789.                                 if (!coursesFinished[3] && !pq.hasCourse(3));
  790.                                     lateEvents.push_back(event(learningTime[3], COURSE, 3));
  791.                                 changePath(WORK_PATH);
  792.                                 changeSalary(800);
  793.                                 changeSalaryFirstDay((int)gameTime);
  794.                                 increaseKarma(+5);
  795.                                 }
  796.                             wait();
  797.                             }
  798.                         break;
  799.                     case LETTER_UNNAMEDSTUDIO_1: {
  800.                         string s = specialLetters[10];
  801.                         if (playerPath == WORK_PATH) {
  802.                             lateEvents.push_back(event(15.0, SPECIAL_LETTER, LETTER_ANGRYBOSS_1));
  803.                             drawWindow(s, new_letter, getStatusLine(), enter);
  804.                             wait();
  805.                             }
  806.                         else if(playerPath == DEFAULT_PATH) {
  807.                             s += unnamedStudio1Answer;
  808.                             drawWindow(s, new_letter, getStatusLine(), one_or_two);
  809.                             char ch = getAnswer('1', '2');
  810.                             if (ch == '1') {
  811.                                 changePath(STARTUP_PATH);
  812.                                 increaseMoney(-500);
  813.                                 lateEvents.push_back(event(12.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_2));
  814.                                 }
  815.                             }
  816.                         }
  817.                         break;
  818.                     case LETTER_UNNAMEDSTUDIO_2: {
  819.                         string s = specialLetters[13];
  820.                         if (coursesFinished[4])
  821.                             s+=specialLetters[27];
  822.                         else
  823.                             s+=specialLetters[26];
  824.                         drawWindow(s, new_letter, getStatusLine(), enter);
  825.                         // Design basics course unlocked
  826.                         coursesUnlocked[4] = true;
  827.                         unnamedStudioLettersSent[1] = true;
  828.                         wait();
  829.                         }
  830.                         break;
  831.                     case LETTER_UNNAMEDSTUDIO_3: {
  832.                         int needMoney = (int)(playerMoney * 0.5);
  833.                         if (needMoney > 50000)
  834.                             needMoney = 50000;
  835.                         else if (needMoney < 0)
  836.                             needMoney *= -1;
  837.                         string s = txt(specialLetters[14], needMoney);
  838.                         s += unnamedStudio3Answer;
  839.                         drawWindow(s, new_letter, getStatusLine(), one_or_two);
  840.  
  841.                         char ch = getAnswer('1', '2');
  842.                         if (ch == '1') {
  843.                             increaseMoney(-needMoney);
  844.                             lateEvents.push_back(event(15.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_4));
  845.                             }
  846.                         else if(ch == '2') {
  847.                             changePath(DEFAULT_PATH);
  848.                             achievesPicked[5] = true;
  849.                             increaseKarma(-10);
  850.                             }
  851.                         }
  852.                         break;
  853.                     case LETTER_UNNAMEDSTUDIO_4: {
  854.                         string s = specialLetters[15];
  855.                         // Creating web-sites
  856.                         if (coursesFinished[5]) {
  857.                             s+=specialLetters[29];
  858.                             drawWindow(s, new_letter, getStatusLine(), enter);
  859.                             wait();
  860.                             // Waiting for 40 days
  861.                             lateEvents.push_back(event(40.0, SPECIAL_LETTER, LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO));
  862.                             break;
  863.                             }
  864.                         int needMoney = (int)(playerMoney * 0.5);
  865.                         if (needMoney > 50000)
  866.                             needMoney = 50000;
  867.                         else if (needMoney < 0)
  868.                             needMoney *= -1;
  869.                         s += txt(specialLetters[28], needMoney);
  870.                         s += unnamedStudio4Answer;
  871.                         drawWindow(s, new_letter, getStatusLine(), one_two_or_three);
  872.                         char ch = getAnswer('1', '2', '3');
  873.                         if (ch == '1') {
  874.                             increaseMoney(-needMoney);
  875.                             increaseKarma(+5);
  876.                             // Pay for the webmaster
  877.                             lateEvents.push_back(event(40.0, NEWS, 3));
  878.                             }
  879.                         else if (ch == '2') {
  880.                             coursesUnlocked[5] = true;
  881.                             lateEvents.push_back(event(40.0, SPECIAL_LETTER, LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO));
  882.                             }
  883.                         else if (ch == '3') {
  884.                             changePath(DEFAULT_PATH);
  885.                             achievesPicked[5] = true;
  886.                             increaseKarma(-5);
  887.                             }
  888.                         }
  889.                         break;
  890.                     case LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO:
  891.                         if (coursesFinished[5]) {
  892.                             lateEvents.push_back(event(1.0, NEWS, 3));
  893.                             }
  894.                         else {
  895.                             // SHow letter about fail
  896.                             drawWindow(specialLetters[16], new_letter, getStatusLine(), enter);
  897.                             wait();
  898.                             drawModalWindow(startupFailedTip);
  899.                             wait();
  900.                             increaseMoney(1000);
  901.                             achievesPicked[5] = true;
  902.                             increaseKarma(-15);
  903.                             }
  904.                         break;
  905.                     case LETTER_UNNAMEDSTUDIO_5: {
  906.                         drawWindow(specialLetters[17], new_letter, getStatusLine(), enter);
  907.                         increaseMoney(200000);
  908.                         increaseKarma(+20);
  909.                         wait();
  910.                         achievesPicked[2] = true;
  911.                         lateEvents.push_back(event(5.0, SPECIAL_LETTER, LETTER_FINALPATH_STARTUP));
  912.                         }
  913.                         break;
  914.                     case LETTER_ANGRYBOSS_1: {
  915.                         string s = specialLetters[11];
  916.                         s += angryBossAnswer;
  917.                         drawWindow(s, new_letter, getStatusLine(), one_or_two);
  918.                         char ch = getAnswer('1', '2');
  919.                         if (ch == '1') {
  920.                             changeSalary(1200);
  921.                             coursesUnlocked[5] = true;
  922.                             coursesUnlocked[6] = true;
  923.                             coursesUnlocked[7] = true;
  924.                             playerFreelanceAttempts = 0;
  925.                             controlFreelanceAttempts = true;
  926.                             lateEvents.push_back(event(20.0, SPECIAL_LETTER, NO_POPULARITY_HINT));
  927.                             }
  928.                         else if(ch == '2') {
  929.                             changePath(STARTUP_PATH);
  930.                             changeSalary(0);
  931.                             lateEvents.push_back(event(9.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_2));
  932.                             achievesPicked[4] = true;
  933.                             increaseKarma(-10);
  934.                             }
  935.                         }
  936.                         break;
  937.                     case NO_POPULARITY_HINT:
  938.                         if (!noPopularity && controlFreelanceAttempts && playerFreelanceAttempts < 5) {
  939.                             drawWindow(specialLetters[25], new_letter, getStatusLine(), enter);
  940.                             wait();
  941.                             }
  942.                         break;
  943.                     case LETTER_ANGRYBOSS_2:
  944.                         drawWindow(specialLetters[12], new_letter, getStatusLine(), enter);
  945.                         changePath(DEFAULT_PATH);
  946.                         changeSalary(0);
  947.                         wait();
  948.                         increaseKarma(-20);
  949.                         break;
  950.                     case LETTER_BORING_WORK:
  951.                         if (playerPath == WORK_PATH) {
  952.                             drawWindow(specialLetters[18], new_letter, getStatusLine(), enter);
  953.                             wait();
  954.                             lateEvents.push_back(event(15.0, SPECIAL_LETTER, LETTER_PERSISTENT_AND_PATIENT));
  955.                             }
  956.                         break;
  957.                     case LETTER_BORING_DEFAULT_PATH:
  958.                         if (playerPath == DEFAULT_PATH && !pq.containsType(SPECIAL_LETTER, LETTER_FINALPATH_DEF
  959.                             && !pq.containsType(SPECIAL_LETTER, LETTER_BORING_DEFAULT_PATH))) {
  960.                             drawWindow(specialLetters[19], new_letter, getStatusLine(), enter);
  961.                             wait();
  962.                             lateEvents.push_back(event(5.0, SPECIAL_LETTER, LETTER_FINALPATH_DEF));
  963.                             }
  964.                         break;
  965.                     case LETTER_FINALPATH_DEF:
  966.                         if (playerPath == DEFAULT_PATH) {
  967.                             drawWindow(defaultFinalCard, finalCard, getStatusLine(), pressF);
  968.                             unlockFinalCards(0);
  969.                             wait('f', 'F');
  970.                             }
  971.                         break;
  972.                     case LETTER_FINALPATH_WORK:
  973.                         if (playerPath == WORK_PATH) {
  974.                             drawWindow(workFinalCard, finalCard, getStatusLine(), pressF);
  975.                             unlockFinalCards(1);
  976.                             wait('f', 'F');
  977.                             }
  978.                     break;
  979.                     case LETTER_FINALPATH_STARTUP:
  980.                         if (playerPath == STARTUP_PATH) {
  981.                             drawWindow(startupFinalCard, finalCard, getStatusLine(), pressF);
  982.                             unlockFinalCards(2);
  983.                             wait('f', 'F');
  984.                             }
  985.                         break;
  986.                     case LETTER_FINALPATH_NOPOPULARITY:
  987.                         drawWindow(zeroKarmaFinalCard, finalCard, getStatusLine(), pressF);
  988.                         unlockFinalCards(3);
  989.                         wait('f', 'F');
  990.                         break;
  991.                     case LETTER_FINALPATH_NOMONEY:
  992.                         drawWindow(noMoneyFinalCard, finalCard, getStatusLine(), pressF);
  993.                         unlockFinalCards(4);
  994.                         wait('f', 'F');
  995.                         break;
  996.                     case LETTER_PERSISTENT_AND_PATIENT:
  997.                         achievesPicked[7] = true;
  998.                         lateEvents.push_back(event(2.0, SPECIAL_LETTER, LETTER_FINALPATH_WORK));
  999.                         break;
  1000.                     case LETTER_TEST_OF_KNOWLEDGE:
  1001.                         drawWindow(specialLetters[20], new_letter, getStatusLine(), enter);
  1002.                         wait();
  1003.                         nextKnowledgeLetterIndex = 0;
  1004.                         lateEvents.push_back(event(5.0, SPECIAL_LETTER, LETTER_KNOWLEDGE_QUESTION));
  1005.                         break;
  1006.                     case LETTER_KNOWLEDGE_QUESTION: {
  1007.                         if (playerPath != WORK_PATH) {
  1008.                             break;
  1009.                             }
  1010.  
  1011.                         if (klowledgeCorrectAnswers == 5) {
  1012.                             /// премия
  1013.                             drawWindow(specialLetters[21], new_letter, getStatusLine(), enter);
  1014.                             increaseMoney(400);
  1015.                             wait();
  1016.                             klowledgeCorrectAnswers = 0;
  1017.                             }
  1018.                         if (klowledgeUncorrectAnswers >= 5) {
  1019.                             /// Didn't pass the knowledge check
  1020.                             drawWindow(specialLetters[22], new_letter, getStatusLine(), enter);
  1021.                             wait();
  1022.                             changePath(DEFAULT_PATH);
  1023.                             changeSalary(0);
  1024.                             break;
  1025.                             }
  1026.                         if (nextKnowledgeLetterIndex > 27) {
  1027.                             /// Knowledge check finished
  1028.                             /// TODO: add a timer for this
  1029.                             drawWindow(specialLetters[23], new_letter, getStatusLine(), enter);
  1030.                             achievesPicked[6] = true;
  1031.                             wait();
  1032.                             lateEvents.push_back(event(30.0, SPECIAL_LETTER, LETTER_BORING_WORK));
  1033.                             }
  1034.                         else {
  1035.                             string s = knowledgeCheck[nextKnowledgeLetterIndex];
  1036.                             short yes = rand() % 2 + 1; // 1 or 2
  1037.                             short no = (yes == 1)?2:1;
  1038.                             s+=txt(yourAnswer, knowledgeCheck[nextKnowledgeLetterIndex+yes], knowledgeCheck[nextKnowledgeLetterIndex+no]);
  1039.                             drawWindow(s, new_letter, getStatusLine(), one_or_two);
  1040.                             char ch = getAnswer('1', '2');//getch();
  1041.                             if ((ch - '0') == yes) {
  1042.                                 drawModalWindow(rightAnswer, congratsMsg);
  1043.                                 klowledgeCorrectAnswers++;
  1044.                                 }
  1045.                             else {
  1046.                                 drawModalWindow(wrongAnswer, failMsg);
  1047.                                 klowledgeUncorrectAnswers++;
  1048.                                 }
  1049.                             wait();
  1050.                             nextKnowledgeLetterIndex+=3;
  1051.                             lateEvents.push_back(event(7.0, SPECIAL_LETTER, LETTER_KNOWLEDGE_QUESTION));
  1052.                             }
  1053.                         }
  1054.                         break;
  1055.                     default:
  1056.                     break;
  1057.                 }
  1058.                 pq.delMin();
  1059.                 break;
  1060.             case MESSAGE:
  1061.                 unreadMessages+=(karma() > 0)?dt * karma()/50:0;
  1062.                 pq.delMin();
  1063.                 lateEvents.push_back(event(2*dt, MESSAGE));
  1064.                 break;
  1065.             case NEWS: {
  1066.                 switch (e->idata) {
  1067.                     // Programming in the world
  1068.                     case 0:
  1069.                         showCoursesTab = true;
  1070.                         coursesUnlocked[1] = true;
  1071.                         break;
  1072.                     // Java programmers
  1073.                     case 1:
  1074.                         coursesUnlocked[2] = true;
  1075.                         break;
  1076.                     // Mobile development
  1077.                     case 2:
  1078.                         coursesUnlocked[3] = true;
  1079.                         break;
  1080.                     case 3:
  1081.                         // Your game is very popular
  1082.                         lateEvents.push_back(event(3.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_5));
  1083.                         break;
  1084.                     default:
  1085.                         break;
  1086.                     }
  1087.                 breakingNews(e->idata);
  1088.                 pq.delMin();
  1089.                 }
  1090.                 break;
  1091.             case NOPOPULARITY:
  1092.             case INCREASEPOPULARITY:
  1093.                 // nop
  1094.                 pq.delMin();
  1095.                 break;
  1096.             default:
  1097.                 break;
  1098.             }
  1099.         }
  1100.     // Add new messages only after checking all existing messages
  1101.     for (unsigned short i = 0; i< lateEvents.size(); i++) {
  1102.         event e = lateEvents[i];
  1103.         if (e.type == MESSAGE) {
  1104.             initSendingMessage();
  1105.             continue;
  1106.             }
  1107.         else if (e.type == SPECIAL_LETTER && e.idata == LETTER_ASTRA)
  1108.             astraLetterSent = true;
  1109.         addTimer(e);
  1110.         }
  1111.     }
  1112.  
  1113. void makeStory() {
  1114.     switch (playerPath) {
  1115.         case DEFAULT_PATH:
  1116.             if (playerMoney > 200.0 && !showCoursesTab && !pq.containsType(NEWS, 0)) { // Basics of programming
  1117.                 addTimer(event(10.0, NEWS, 0));
  1118.                 newsShowed[0] = true;
  1119.                 }
  1120.             else if (playerMoney > 300.0 && !shitCodeDetected && !shitLettersSent[0] && coursesFinished[1]) {
  1121.                 addTimer(event(9.0, SPECIAL_LETTER, LETTER_SHITCODE_1));
  1122.                 shitLettersSent[0] = true;
  1123.                 }
  1124.             else if (playerMoney > 400.0 && !newsShowed[2] && coursesFinished[1] && !pq.containsType(NEWS, 2)) {   // Frappy Perd
  1125.                 addTimer(event(9.0, NEWS, 2));
  1126.                 newsShowed[2] = true;
  1127.                 }
  1128.             else if (playerMoney > 500 && !sentBotsmannLetter && coursesFinished[1]) {   // Letter from Botsmann
  1129.                 addTimer(event(17.0, SPECIAL_LETTER, LETTER_BOTSMANN));
  1130.                 sentBotsmannLetter = true;
  1131.                 }
  1132.             else if (playerMoney > 600.0 && !shitCodeDetected && !shitLettersSent[1] && coursesFinished[1] && !pq.containsType(SPECIAL_LETTER, LETTER_SHITCODE_1)) {
  1133.                 addTimer(event(13.0, SPECIAL_LETTER, LETTER_SHITCODE_2));
  1134.                 shitLettersSent[1] = true;
  1135.                 }
  1136.             else if (playerMoney > 800.0 && sentBotsmannLetter && !unnamedStudioLettersSent[0] && coursesFinished[3] && astraLetterSent && !pq.containsType(SPECIAL_LETTER, LETTER_ASTRA)) { // Startup
  1137.                 addTimer(event(15.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_1));
  1138.                 unnamedStudioLettersSent[0] = true;
  1139.                 }
  1140.             else if (playerMoney > 1000.0 && !shitCodeDetected && !shitLettersSent[2] && coursesFinished[1] && !pq.containsType(SPECIAL_LETTER, LETTER_SHITCODE_2)) { // Send 3 letters with checking for a shit code
  1141.                 addTimer(event(14.0, SPECIAL_LETTER, LETTER_SHITCODE_3));
  1142.                 shitLettersSent[2] = true;
  1143.                 }
  1144.             else if (getAvailableCoursesCount() == 0 && playerMoney > 2000 && sentBotsmannLetter
  1145.                 && unnamedStudioLettersSent[0] && !finalCardUnlocked[0] && !pq.containsType(SPECIAL_LETTER, LETTER_BORING_DEFAULT_PATH)) {
  1146.                 addTimer(event(10.0, SPECIAL_LETTER, LETTER_BORING_DEFAULT_PATH));
  1147.                 }
  1148.             break;
  1149.         case WORK_PATH:
  1150.             if (playerSalary == 1200 && noPopularity && !knowledgeLetterSent && unnamedStudioLettersSent[0] && controlFreelanceAttempts && playerFreelanceAttempts <= 5) {
  1151.                 addTimer(event(5.0, SPECIAL_LETTER, LETTER_TEST_OF_KNOWLEDGE));
  1152.                 knowledgeLetterSent = true;
  1153.                 }
  1154.             else if (playerMoney > 500.0 && !newsShowed[2] && !pq.containsType(NEWS, 2)) {  // Frappy Perd
  1155.                 addTimer(event(7.0, NEWS, 2));
  1156.                 newsShowed[2] = true;
  1157.                 }
  1158.             else if (playerMoney > 600.0 && !unnamedStudioLettersSent[0] && coursesFinished[3]) { // Startup
  1159.                 addTimer(event(14.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_1));
  1160.                 unnamedStudioLettersSent[0] = true;
  1161.                 }
  1162.             break;
  1163.         case STARTUP_PATH:
  1164.             if (unnamedStudioLettersSent[1] && !unnamedStudioLettersSent[2] && coursesFinished[4]) {
  1165.                 addTimer(event(15.0, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_3));
  1166.                 unnamedStudioLettersSent[2] = true;
  1167.                 }
  1168.             else if (playerMoney > 600.0 && !shitCodeDetected && !shitLettersSent[1] && coursesFinished[1] && !pq.containsType(SPECIAL_LETTER, LETTER_SHITCODE_1)) {
  1169.                 addTimer(event(14.0, SPECIAL_LETTER, LETTER_SHITCODE_2));
  1170.                 shitLettersSent[1] = true;
  1171.                 }
  1172.             else if (playerMoney > 1000.0 && !shitCodeDetected && !shitLettersSent[2] && coursesFinished[1] && !pq.containsType(SPECIAL_LETTER, LETTER_SHITCODE_2)) { // Send 3 letters with checking for a shit code
  1173.                 addTimer(event(19.0, SPECIAL_LETTER, LETTER_SHITCODE_3));
  1174.                 shitLettersSent[2] = true;
  1175.                 }
  1176.             break;
  1177.         }
  1178.     // News about JAVA programmers
  1179.     if (playerMoney > 1500.0 && !newsShowed[1] && shitLettersSent[0] && shitLettersSent[1]
  1180.         && shitLettersSent[2] && !shitCodeDetected && coursesFinished[1]  && !coursesUnlocked[2]) { // JAVA programmers
  1181.         addTimer(event(5.0, NEWS, 1));
  1182.         newsShowed[1] = true;
  1183.         }
  1184.     if (!returnTo21HintShowed && finishedCoursesCount == 12 && finalCardsUnlocked == 5 && gameTime > 21.0 && allAchievesShowed && !pq.containsType(SPECIAL_LETTER, LETTER_RETURN_TO_DAY_21)) {
  1185.         addTimer(event(2.0, SPECIAL_LETTER, LETTER_RETURN_TO_DAY_21));
  1186.         }
  1187.     }
  1188.  
  1189. // Desktop is your main screen
  1190. string getDesktop(int &lines) {
  1191.     string s = "\n\n";
  1192.     // Information about courses
  1193.     if (!pq.hasCourses()) {
  1194.         s += noCurrentCurses;
  1195.         }
  1196.     else {
  1197.         // Check courses in pq
  1198.         s += "    ";
  1199.         s += coursesTitle;
  1200.         s += ":\n";
  1201.         for(int i = 0; i< pq.n(); i++) {
  1202.             event* e = pq.get(i);
  1203.             if (e->type == COURSE) {
  1204.                 s += "     ";
  1205.                 if (e->idata >= 0 && e->idata <= 7) {
  1206.                     s += courses[e->idata];
  1207.                     s += " ";
  1208.                     int percent = (int)(100 * (learningTime[e->idata] - e->time) / learningTime[e->idata]);
  1209.                     for (int i = 0; i < 20; i++) {
  1210.                         if (i > 2*percent/10)
  1211.                             s += " ";
  1212.                         else
  1213.                             s += pseudoEqual;
  1214.                         }
  1215.                     s += txt(" %d%\n", percent);
  1216.                     }
  1217.                 else if (e->idata >= 'a' && e->idata <= 'e') {
  1218.                     s += advProgCourses[e->idata - 'a'];
  1219.                     s += " ";
  1220.                     int percent = (int)(100 * (advProgLearningTime[e->idata-'a'] - e->time) / advProgLearningTime[e->idata - 'a']);
  1221.                     for (int i = 0; i < 20; i++) {
  1222.                         if (i > 2*percent/10)
  1223.                             s+=" ";
  1224.                         else
  1225.                             s+=pseudoEqual;
  1226.                         }
  1227.                     s += txt(" %d%\n", percent);
  1228.                     }
  1229.                 }
  1230.             }
  1231.         }
  1232.  
  1233.     for(unsigned int i = 0; i < s.length(); i++)
  1234.         if (s[i] == '\n')
  1235.             lines++;
  1236.  
  1237.     return s;
  1238.     }
  1239.  
  1240. void mainGameCycle() {
  1241.     float hintBegins = 0.0; //days
  1242.     unsigned short hintIndex = 0;
  1243.  
  1244.     playerMoney = 100;
  1245.     dt = 0.5;
  1246.     int update = 0;
  1247.  
  1248.     // Add initial letter
  1249.     initSendingMessage();
  1250.  
  1251.     string status = playerStatus;
  1252.     status += helpDesker;
  1253.     int currentPath = playerPath;
  1254.  
  1255.     playerStartedPlaying = time(NULL);
  1256.     string buffer, oldStatusLine;
  1257.     while (1) {
  1258.         // We update screen 10 times a second
  1259.         if (update % 10 == 0)
  1260.             increaseMoney(-dt * 1.0); // Money needs per day
  1261.  
  1262.         int lines = 0;
  1263.  
  1264.         string s = getDesktop(lines);
  1265.         for (int i = 0; i < 11-lines; i++)
  1266.             s += "\n";
  1267.         s += "    ";
  1268.         s += hintOfTheMonth;
  1269.         if (gameTime > hintBegins + 30) {
  1270.             hintBegins = gameTime;
  1271.             hintIndex++;
  1272.             if (hintIndex >= hintsCount)
  1273.                 hintIndex = 0;
  1274.             }
  1275.         // This could happen when jumping in the past
  1276.         else if (hintBegins > gameTime)
  1277.             hintBegins = gameTime;
  1278.  
  1279.         s += "    ";
  1280.         s += hints[hintIndex];
  1281.  
  1282.         if (playerPath != currentPath) {
  1283.             currentPath = playerPath;
  1284.             status=playerStatus;
  1285.             if (playerPath == DEFAULT_PATH)      status += helpDesker;
  1286.             else if (playerPath == WORK_PATH)    status += worker;
  1287.             else if (playerPath == STARTUP_PATH) status += startupper;
  1288.             }
  1289.         // redraw screen only if necessary
  1290.         string newStatusLine = getStatusLine();
  1291.         if (!(s == buffer) || !(oldStatusLine == newStatusLine))
  1292.             drawWindow(s, status.c_str(), newStatusLine, getBottomLine());
  1293.         buffer = s;
  1294.         oldStatusLine = newStatusLine;
  1295.         if (update % 10 == 0) {
  1296.             checkAchieves();
  1297.             if (isGameOver())
  1298.                 break;
  1299.  
  1300.             if (playerSalary != 0 && gameTime-playerSalaryFirstDay >= 30) {
  1301.                 increaseMoney(playerSalary);
  1302.                 changeSalaryFirstDay(playerSalaryFirstDay+30);
  1303.                 }
  1304.             makeStory();
  1305.             checkPq();
  1306.             }
  1307.  
  1308.         // Some key was pressed
  1309.         if (kbhit()) {
  1310.             char k = getch();
  1311.             #ifdef _KOS32
  1312.             // We have to make a second call for special keys in KolibriOS
  1313.             if (k == 0)
  1314.                 k = getch();
  1315.             #endif
  1316.             if (keyPressed(k))
  1317.                 return;
  1318.             // Force redraw desktop
  1319.             buffer = "";
  1320.             oldStatusLine = "";
  1321.             }
  1322.  
  1323.         if (update % 10 == 0)
  1324.             gameTime += dt;
  1325.  
  1326.         int delayTime = 10;
  1327.         #ifdef _KOS32
  1328.         __menuet__delay100(delayTime);
  1329.         #else
  1330.         usleep(delayTime*10000);
  1331.         #endif
  1332.         update++;
  1333.         }
  1334.     }
  1335.  
  1336. // Jump in the past to a certain time
  1337. void undo(long double toTime) {
  1338.     if (toTime < 0 )
  1339.         return;
  1340.  
  1341.     if (playerMoney < 100) {
  1342.         drawModalWindow(need100ForUndo, errorMsg);
  1343.         wait();
  1344.         return;
  1345.         }
  1346.     // Jump cost: $100
  1347.     increaseMoney(-100);
  1348.     drawModalWindow(prepareForTimeJump);
  1349.     wait();
  1350.  
  1351.     while(!pq.empty())
  1352.         pq.delMin();
  1353.  
  1354.     // Player Stats
  1355.     playerTimeHops++;
  1356.     playerSpentRealDays += (gameTime - playerPrevUndo);
  1357.     playerPrevUndo = toTime;
  1358.  
  1359.     vector<event> lateEvents;
  1360.     history.prepareForUndo();
  1361.     while (!history.empty() && history.getMax().time > toTime) {
  1362.         event e = history.getMax();
  1363.         switch (e.type) {
  1364.             case INCREASEPOPULARITY:
  1365.                 increaseKarma(-e.idata, false);
  1366.                 break;
  1367.             case NOPOPULARITY:
  1368.                 noPopularity = false;
  1369.                 break;
  1370.             case CHANGEPATH:
  1371.                 playerPath = e.idata; // Previous path
  1372.                 break;
  1373.             case CHANGESALARY:
  1374.                 playerSalary = e.idata;
  1375.                 break;
  1376.             case CHANGESALARYFIRSTDAY:
  1377.                 playerSalaryFirstDay = e.idata;
  1378.                 break;
  1379.         // Letters' time is time of sending a letter + time to end timer = time to get letter in the future
  1380.         case SPECIAL_LETTER:
  1381.             switch(e.idata) {
  1382.                 case LETTER_BOTSMANN:
  1383.                     sentBotsmannLetter = false;
  1384.                     astraLetterSent = false;
  1385.                     break;
  1386.                 case LETTER_ASTRA:
  1387.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_BOTSMANN, toTime))
  1388.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_ASTRA));
  1389.                     break;
  1390.                 case LETTER_UNNAMEDSTUDIO_2:
  1391.                     // Unnamed letter 1 -> AngryBoss -> Unnamed letter 2
  1392.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_ANGRYBOSS_1, e.time)) {
  1393.                         if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_ANGRYBOSS_1, toTime))
  1394.                             lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_2));
  1395.                         }
  1396.                     // Unnamed letter 1 -> Unnamed letter 2
  1397.                     else {
  1398.                         if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_1, toTime))
  1399.                             lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_2));
  1400.                         }
  1401.                     break;
  1402.                 case NO_POPULARITY_HINT:
  1403.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_ANGRYBOSS_1, toTime))
  1404.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, NO_POPULARITY_HINT));
  1405.                     break;
  1406.                 case LETTER_ANGRYBOSS_1:
  1407.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_1, toTime)) {
  1408.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_ANGRYBOSS_1));
  1409.                         controlFreelanceAttempts = false;
  1410.                         }
  1411.                     break;
  1412.                 case LETTER_UNNAMEDSTUDIO_1:
  1413.                     unnamedStudioLettersSent[0] = false;
  1414.                     break;
  1415.                 case LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO:
  1416.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_4, toTime))
  1417.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO));
  1418.                     break;
  1419.                 case LETTER_UNNAMEDSTUDIO_4:
  1420.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_3, toTime))
  1421.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_4));
  1422.                     break;
  1423.                 case LETTER_UNNAMEDSTUDIO_3:
  1424.                         unnamedStudioLettersSent[2] = false;
  1425.                     break;
  1426.                 case LETTER_UNNAMEDSTUDIO_5:
  1427.                     if (history.containsTypeBefore(NEWS, 3, toTime) && !pq.containsType(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_5))
  1428.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_5));
  1429.                     break;
  1430.                 case LETTER_FINALPATH_DEF:
  1431.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_BORING_DEFAULT_PATH, toTime))
  1432.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_FINALPATH_DEF));
  1433.                     break;
  1434.                 case LETTER_FINALPATH_STARTUP:
  1435.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_5, toTime) && !pq.containsType(SPECIAL_LETTER, LETTER_FINALPATH_STARTUP))
  1436.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_FINALPATH_STARTUP));
  1437.                     break;
  1438.                 case LETTER_FINALPATH_WORK:
  1439.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_PERSISTENT_AND_PATIENT, toTime))
  1440.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_FINALPATH_WORK));
  1441.                     break;
  1442.                 case LETTER_ANGRYBOSS_2:
  1443.                     if (e.time - toTime <= 1)
  1444.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_ANGRYBOSS_2));
  1445.                     break;
  1446.                 case LETTER_PERSISTENT_AND_PATIENT:
  1447.                     if (history.containsTypeBefore(SPECIAL_LETTER,LETTER_BORING_WORK, toTime))
  1448.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_PERSISTENT_AND_PATIENT));
  1449.                     break;
  1450.                 case LETTER_BORING_WORK:
  1451.                     if (e.time - toTime <= 40)
  1452.                         lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_BORING_WORK));
  1453.                     break;
  1454.                 case LETTER_TEST_OF_KNOWLEDGE:
  1455.                     knowledgeLetterSent = false;
  1456.                     playerFreelanceAttempts = 0;
  1457.                     klowledgeCorrectAnswers = 0;
  1458.                     klowledgeUncorrectAnswers = 0;
  1459.                     break;
  1460.                 case LETTER_SHITCODE_1:
  1461.                     shitCodeDetected = false;
  1462.                     lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_SHITCODE_1));
  1463.                     shitLettersSent[1] = false;
  1464.                     shitLettersSent[2] = false;
  1465.                     break;
  1466.                 case LETTER_SHITCODE_2:
  1467.                     shitCodeDetected = false;
  1468.                     lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_SHITCODE_2));
  1469.                     shitLettersSent[2] = false;
  1470.                     break;
  1471.                 case LETTER_SHITCODE_3:
  1472.                     shitCodeDetected = false;
  1473.                     lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_SHITCODE_3));
  1474.                     break;
  1475.                 case LETTER_BORING_DEFAULT_PATH:
  1476.                     lateEvents.push_back(event(e.time - toTime, SPECIAL_LETTER, LETTER_BORING_DEFAULT_PATH));
  1477.                     break;
  1478.                 }
  1479.                 break;
  1480.             case NEWS:
  1481.                 // News about JAVA programmers
  1482.                 newsShowed[e.idata] = false;
  1483.                 if (e.idata == 3) {
  1484.                     // Unnamed letter 3 -> Unnamed letter 4 -> Need webmaster -> news about Java
  1485.                     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO, e.time)) {
  1486.                         if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_WEBMASTER_CHECK_UNNAMEDSTUDIO, toTime))
  1487.                             lateEvents.push_back(event(e.time - toTime, NEWS, 3));
  1488.                         }
  1489.                     // Unnamed letter 3 -> Unnamed letter 4 -> news about Java
  1490.                     else {
  1491.                         if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_UNNAMEDSTUDIO_4, toTime))
  1492.                             lateEvents.push_back(event(e.time - toTime, NEWS, 3));
  1493.                         }
  1494.                     }
  1495.             break;
  1496.             case COURSE: {
  1497.                 // e.time  - course finished
  1498.                 // e.idata - course number
  1499.                 long double courseStarted = 0;
  1500.                 bool courseFinished = false;
  1501.                 if (e.idata >='a' && e.idata <= 'e') {
  1502.                     courseStarted = e.time - advProgLearningTime[e.idata-'a'];
  1503.                     courseFinished = advProgCoursesFinished[e.idata-'a'];
  1504.                     }
  1505.                 else if(e.idata >= 0 && e.idata < (int)coursesNumber) {
  1506.                     courseStarted = e.time - learningTime[e.idata];
  1507.                     courseFinished = coursesFinished[e.idata];
  1508.                     }
  1509.  
  1510.                 if (!courseFinished && toTime >= courseStarted)
  1511.                     // Player is currently taking a course
  1512.                     lateEvents.push_back(event(e.time - toTime, COURSE, e.idata));
  1513.                     }
  1514.                 break;
  1515.             case MESSAGE:
  1516.                 break;
  1517.             }
  1518.         history.delMax();
  1519.         }
  1520.     unreadMessages = 0;
  1521.     gameTime = toTime;
  1522.     timeHops++;
  1523.  
  1524.     for (unsigned short i = 0; i< lateEvents.size(); i++)
  1525.         addTimer(lateEvents[i]);
  1526.  
  1527.  
  1528.     // Messages would not arive without this:
  1529.     if (!pq.containsType(MESSAGE))
  1530.         initSendingMessage();
  1531.  
  1532.     // Undo shouldn't stop during the knowledge check
  1533.     if (history.containsTypeBefore(SPECIAL_LETTER, LETTER_TEST_OF_KNOWLEDGE, toTime)
  1534.         && !history.containsTypeBefore(SPECIAL_LETTER, LETTER_BORING_WORK, toTime)) {
  1535.         double newTime = toTime-10;
  1536.         for (int i = 0; i < history.n(); i++) {
  1537.             event* e = history.get(i);
  1538.             if (e->type == SPECIAL_LETTER && e->idata == LETTER_TEST_OF_KNOWLEDGE) {
  1539.                 newTime = e->time - 10;
  1540.                 break;
  1541.                 }
  1542.             }
  1543.         drawModalWindow(cantStopDuringKnowledgeCheck, errorMsg);
  1544.         wait();
  1545.         undo(newTime);
  1546.         }
  1547.     }
  1548.  
  1549. int chooseCourse() {
  1550.     double p[coursesNumber];
  1551.     long double sum = 0.0;
  1552.     for (unsigned int i = 0; i < coursesNumber; i++) {
  1553.         if (coursesFinished[i])
  1554.             p[i] = probability[i];
  1555.         else
  1556.             p[i] = 0.0;
  1557.         sum += p[i];
  1558.         }
  1559.     if (sum <= 0.001)
  1560.         return 0;
  1561.     int N = (int) (sum * 100);
  1562.     int r = rand() % N;
  1563.  
  1564.     int n1 = (p[0] / sum) * N;
  1565.     int n2 = (p[1] / sum) * N;
  1566.     int n3 = (p[2] / sum) * N;
  1567.     int n4 = (p[3] / sum) * N;
  1568.     int n5 = (p[4] / sum) * N;
  1569.  
  1570.     if (r >= 0 && r < n1)
  1571.         return 0;
  1572.     else if (coursesFinished[1] && r >= n1 && r < n1+n2)
  1573.         return 1;
  1574.     else if (coursesFinished[2] && r >= n1+n2 && r < n1+n2+n3)
  1575.         return 2;
  1576.     else if (coursesFinished[3] && r >= n1+n2+n3 && r < n1+n2+n3+n4)
  1577.         return 3;
  1578.     else if (coursesFinished[4] && r >= n1+n2+n3+n4 && r < n1+n2+n3+n4+n5)
  1579.         return 4;
  1580.     else if (coursesFinished[5] && r >= n1+n2+n3+n4+n5 && r <= N)
  1581.         return 5;
  1582.  
  1583.     return 0;
  1584.     }
  1585.