• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eric15342335 / comp2113-engg1340-group-project / #42

18 Jun 2024 02:15AM UTC coverage: 73.706%. Remained the same
#42

push

travis-ci

eric15342335
Fix duplicated HSI record when loadsave (#143)

Fix cmake exe unicode bug (#145)

Makefile and main.cpp was unrelated changes.

Makefile cleanup

2 of 2 new or added lines in 1 file covered. (100.0%)

25 existing lines in 2 files now uncovered.

1452 of 1970 relevant lines covered (73.71%)

713.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.01
/src/main.cpp
1
/// @file main.cpp
2
/// file with the main() function
3
/*
4
This program is free software: you can redistribute it and/or modify it under the
5
terms of the GNU Lesser General Public License as published by the Free Software
6
Foundation, either version 3 of the License, or (at your option) any later version.
7

8
This program is distributed in the hope that it will be useful, but WITHOUT ANY
9
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10
PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
11

12
You should have received a copy of the GNU Lesser General Public License along with this
13
program. If not, see <https://www.gnu.org/licenses/>.
14
*/
15

16
#include "controls.h"
17
#include "draw.h"
18
#include "events.h"
19
#include "file_io.h"
20
#include "format.h"
21
#include "graph.h"
22
#include "nonstdlibs/VariadicTable.h"
23
#include "random_price.h"
24
#include "stock.h"
25

26
#include <cmath>
27
#include <fstream>
28
#include <numeric>
29

30
#ifdef _WIN32
31
#define NOMINMAX 1          // Prevent Windows.h from defining min and max macros
32
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
33
#include <windows.h>
34
/** @brief Enable Windows VT processing for ANSI escape codes
35
 * @details Without this, ANSI escape codes will not work on Windows 10.
36
 * E.g. text color, cursor position, etc.
37
 */
38
void enableWindowsVTProcessing(void) {
39
    // Set the console to UTF-8 mode
40
    SetConsoleOutputCP(65001);
41
    // Get the current console mode
42
    DWORD consoleMode;
43
    GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &consoleMode);
44
    // Enable virtual terminal processing
45
    consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
46
    SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), consoleMode);
47
    std::cout << "Experimental Windows VT processing enabled." << std::endl;
48
}
49
#else
50
#define enableWindowsVTProcessing() // Do nothing
51
#endif
52

53
/**
54
 * <value> / 100 means charging <value>% more/portion of the money involved in stock
55
 * operations.
56
 */
57
const float trading_fees_percent = 0.1 / 100;
58

59
/** Player's balance */
60
float balance = 1000.0f;
61
/** Number of rounds played */
62
unsigned int rounds_played = 1;
63

64
/** Player's name */
65
std::string playerName;
66

67
std::string vectorToString(const std::vector<unsigned int> & vec) {
1,080✔
68
    return std::accumulate(
69
        vec.begin(), vec.end(), std::string(), [](const std::string & s, int v) {
2,160✔
70
            return s.empty() ? std::to_string(v) : s + " " + std::to_string(v);
738✔
71
        });
2,160✔
72
}
73

74
void get_hsi(std::vector<Stock> stocks_list, std::vector<float> & hsi_history) {
47✔
75
    float hsi = 0;
47✔
76
    std::string filesave =
77
        SAVE_FOLDER_PREFIX + playerName + "/hsi" + SAVE_FILE_EXTENSION_TXT;
94✔
78
    std::vector<float> total;
47✔
79
    for (unsigned int i = 0; i < stocks_list.size(); i++) {
987✔
80
        total.emplace_back(
940✔
81
            stocks_list[i].get_price() / stocks_list[i].get_initial_price() * 1000 *
1,880✔
82
            static_cast<float>(std::pow(2, stocks_list[i].get_split_count())));
940✔
83
        // HSI formula = (price/initial price) * 1000 * 2^split count
84
    }
85
    hsi = std::reduce(total.begin(), total.end()) / total.size();
47✔
86
    hsi_history.emplace_back(hsi);
47✔
87
    std::ofstream fout;
47✔
88
    fout.open(filesave.c_str(), std::ios::app);
47✔
89
    fout << hsi << ' ';
47✔
90
    fout.close();
47✔
91
}
47✔
92

93
/**
94
 * @brief hiding mean/sd/uplim/lowlim/event_id columns in the table
95
 */
96
enum mode { normal, dev };
97

98
/** Print the table of stocks. We put it in a function so we can call it multiple times.
99
 * @param stocks_list A vector of stocks. The stocks to be printed.
100
 * @param _playerBal How much money the player has.
101
 * @param m mode to hide mean/sd/uplim/lowlim/event_id columns in the table
102
 */
103
void print_table(std::vector<Stock> stocks_list, float _playerBal, mode m = dev) {
54✔
104
    std::vector<std::string> defaultColumns = {
105
        "#", "Category", "Name", "$Price", "Change", R"(%Change)", "#Has", "#Max"};
594✔
106
    VariadicTable<unsigned int, std::string, std::string, float, float, float,
107
        unsigned int, unsigned int>
108
        defaultTable(defaultColumns);
54✔
109
    if (m == dev) {
54✔
110
        defaultColumns.emplace_back(" Mean ");
54✔
111
        defaultColumns.emplace_back(" SD ");
54✔
112
        defaultColumns.emplace_back(" up ");
54✔
113
        defaultColumns.emplace_back(" low ");
54✔
114
        defaultColumns.emplace_back("event_id");
54✔
115
        // Create a table, note that R"(% Change)" is a raw string literal (C++11
116
        // feature).
117
        VariadicTable<unsigned int, std::string, std::string, float, float, float,
118
            unsigned int, unsigned int, float, float, float, float, std::string>
119
            devTable({defaultColumns});
54✔
120
        /* Set the precision and format of the columns.
121
         * Note: Precision and Format is ignored for std::string columns. */
122
        devTable.setColumnPrecision({0, 0, 0, 2, 2, 2, 0, 0, 1, 0, 0, 0, 0});
54✔
123
        devTable.setColumnFormat({VariadicTableColumnFormat::AUTO,
54✔
124
            VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::AUTO,
125
            VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
126
            VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
127
            VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
128
            VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
129
            VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::AUTO});
130
        for (unsigned int i = 0; i < stocks_list.size(); i++) {
1,134✔
131
            std::map<stock_modifiers, float> modifiers =
132
                getProcessedModifiers(stocks_list[i]);
1,080✔
133
            devTable.addRow(i + 1, stocks_list[i].category_name(),
5,400✔
134
                stocks_list[i].get_name(), stocks_list[i].get_price(),
3,240✔
135
                stocks_list[i].delta_price(),
1,080✔
136
                stocks_list[i].delta_price_percentage() * 100,
1,080✔
137
                stocks_list[i].get_quantity(),
1,080✔
138
                stocks_list[i].num_stocks_affordable(_playerBal, trading_fees_percent),
1,080✔
139
                modifiers[mean], modifiers[standard_deviation], modifiers[upper_limit],
1,080✔
140
                modifiers[lower_limit], vectorToString(stocks_list[i].get_event_ids()));
2,160✔
141
        }
1,080✔
142
        devTable.print(std::cout);
54✔
143
    }
54✔
144
    else {
145
        /* Set the precision and format of the columns.
146
         * Note: Precision and Format is ignored for std::string columns. */
147
        defaultTable.setColumnPrecision({0, 0, 0, 2, 2, 2, 0, 0});
×
148
        defaultTable.setColumnFormat(
×
149
            {VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::AUTO,
150
                VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::FIXED,
151
                VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
152
                VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED});
UNCOV
153
        for (unsigned int i = 0; i < stocks_list.size(); i++) {
×
UNCOV
154
            defaultTable.addRow(i + 1, stocks_list[i].category_name(),
×
UNCOV
155
                stocks_list[i].get_name(), stocks_list[i].get_price(),
×
UNCOV
156
                stocks_list[i].delta_price(),
×
UNCOV
157
                stocks_list[i].delta_price_percentage() * 100,
×
UNCOV
158
                stocks_list[i].get_quantity(),
×
UNCOV
159
                stocks_list[i].num_stocks_affordable(_playerBal, trading_fees_percent));
×
160
        }
UNCOV
161
        defaultTable.print(std::cout);
×
162
    }
163
    // Modify the stringstream so that for the column "Change", the text
164
    // "Increase" is green and "Decrease" is red.
165
    // @note This is a workaround because VariadicTable does not support
166
    // modifying the text color of a specific cell.
167
    // Warning: This is a hack and may not work in the future!
168
    for (unsigned int i = 0; i < stocks_list.size(); i++) {
1,134✔
169
        std::string index = std::to_string(i + 1);
1,080✔
170
        if (i < 10 - 1) {
1,080✔
171
            index = " " + index;
486✔
172
        }
173
        if (stocks_list[i].delta_price() > 0) {
1,080✔
174
            std::cout << setCursorPosition(i + 9, 3) << textGreen << index;
489✔
175
        }
176
        else if (stocks_list[i].delta_price() < 0) {
591✔
177
            std::cout << setCursorPosition(i + 9, 3) << textRed << index;
511✔
178
        }
179
    }
1,080✔
180
    std::cout << textWhite;
54✔
181
    /* Display 2 decimal places for balance.
182
     * This line reverts the precision back to default after the table is printed.
183
     * Since the table uses std::auto (VariadicTableColumnFormat::AUTO), we need to
184
     * revert it back to default.
185
     */
186
    std::cout << std::fixed << std::setprecision(2);
54✔
187
}
54✔
188

189
/**
190
 * Get all the ongoing events.
191
 * @param stocks_list A vector of stocks.
192
 * @return A vector of Stock_event
193
 */
194
std::vector<Stock_event> get_ongoing_events(std::vector<Stock> stocks_list) {
105✔
195
    // Return a vector of ongoing events without duplicates
196
    std::vector<Stock_event> ongoing_events = {};
105✔
197
    for (unsigned int i = 0; i < stocks_list.size(); i++) {
2,205✔
198
        std::list<Stock_event> events = stocks_list[i].get_events();
2,100✔
199
        for (const Stock_event & event : events) {
2,832✔
200
            // Side note: Events with duration <= 0 are automatically removed from the
201
            // stock's event list. By stock.cpp Stock::next_round() which uses
202
            // Stock::remove_obselete_event()
203
            if (event.duration > 0) {
732✔
204
                // If the event is not in the ongoing_events, add it.
205
                if (std::find(ongoing_events.begin(), ongoing_events.end(), event) ==
732✔
206
                    ongoing_events.end()) {
1,464✔
207
                    ongoing_events.emplace_back(event);
193✔
208
                }
209
            }
210
        }
211
    }
2,100✔
212
    return ongoing_events;
105✔
UNCOV
213
}
×
214

215
/**
216
 * @brief Generate new events and apply them to the stocks. Should be called at the
217
 * beginning of each round.
218
 * @param stocks_list A vector of stocks. Pass by reference to modify the stocks.
219
 */
220
void new_events_next_round(std::vector<Stock> & stocks_list) {
44✔
221
    /** @note numEvents is the sum of these three values:
222
     * - 1
223
     * - A random integer between 0 and 1 (uniform distribution)
224
     * - 1 if more than 10 rounds have been played
225
     * If there was already more than 5 events, we will not generate more events.
226
     */
227
    unsigned int numEvents = 1 + random_integer(1) + (rounds_played / 5 > 2) * 1;
44✔
228
    if (get_ongoing_events(stocks_list).size() > 5) {
44✔
UNCOV
229
        return;
×
230
    }
231
    std::vector<Stock_event> picked_events = pick_events(all_stock_events, numEvents);
44✔
232
    for (const Stock_event & event : picked_events) {
112✔
233
        switch (event.type_of_event) {
68✔
234
            case all_stocks:
5✔
235
                for (unsigned int i = 0; i < stocks_list.size(); i++) {
105✔
236
                    stocks_list[i].add_event(event);
100✔
237
                }
238
                break;
5✔
239
            case category:
61✔
240
                for (unsigned int i = 0; i < stocks_list.size(); i++) {
1,281✔
241
                    if (stocks_list[i].get_category() == event.category) {
1,220✔
242
                        stocks_list[i].add_event(event);
75✔
243
                    }
244
                }
245
                break;
61✔
246
            case pick_random_stock: {
2✔
247
                std::vector<unsigned int> stocks_indices_not_suitable = {};
2✔
248
                while (!stocks_list.empty() &&
4✔
249
                       stocks_list.size() < stocks_indices_not_suitable.size()) {
2✔
250
                    // Pick a random stock
251
                    unsigned int choice = random_integer(stocks_list.size());
×
UNCOV
252
                    Stock lucky_stock = stocks_list[choice];
×
UNCOV
253
                    if (!lucky_stock.can_add_event(event)) {
×
UNCOV
254
                        stocks_indices_not_suitable.emplace_back(choice);
×
255
                    }
256
                    else {
UNCOV
257
                        Stock_event modified_event = event;
×
UNCOV
258
                        modified_event.text = lucky_stock.get_name() + " " + event.text;
×
259
                        lucky_stock.add_event(modified_event);
×
UNCOV
260
                        break;
×
261
                    }
262
                }
263
                break;
2✔
264
            }
2✔
UNCOV
265
            default:
×
266
                // Should not reach here, but if it does, break the loop
267
                // so that the player can continue playing the game.
UNCOV
268
                break;
×
269
        }
270
    }
271
}
44✔
272

273
void next_round_routine(unsigned int & _rounds, std::vector<Stock> & stocks_list) {
44✔
274
    _rounds++; // Increment the round number
44✔
275
    new_events_next_round(
44✔
276
        stocks_list); // Generate new events and apply them to the stocks
277
    for (unsigned int i = 0; i < stocks_list.size(); i++) {
924✔
278
        stocks_list[i].next_round(); // Update the stock price
880✔
279
    }
280
}
44✔
281

282
void initializePlayerSaves(
7✔
283
    std::vector<Stock> & stocks_list, std::vector<float> & hsi_history) {
284
    std::string EMPTY_INPUT = "";
7✔
285
    std::string loadsave = EMPTY_INPUT;
7✔
286
    while (loadsave.compare(EMPTY_INPUT) == 0) {
14✔
287
        std::cout << USER_SAVE_OPTION_PROMPT;
9✔
288
        std::cin >> loadsave;
9✔
289
        while (!checkValidInput(loadsave)) {
16✔
290
            std::cout << "Invalid input.\n" << USER_SAVE_OPTION_PROMPT;
7✔
291
            std::cin >> loadsave; // choose new file or load previous file
7✔
292
        }
293
        if (loadsave.compare(USER_SAVE_OPTION::NEW_GAME) == 0) {
9✔
294
            createplayer(playerName);
2✔
295
            savestatus(rounds_played, stocks_list, balance, playerName);
2✔
296
        }
297
        if (loadsave.compare(USER_SAVE_OPTION::LOAD_GAME) == 0) {
9✔
298
            loadstatus(rounds_played, stocks_list, balance, playerName, hsi_history);
3✔
299
        }
300
        if (loadsave.compare(USER_SAVE_OPTION::DELETE_GAME) == 0) {
9✔
301
            delsave(loadsave);
2✔
302
            loadsave = EMPTY_INPUT;
2✔
303
        }
304
        if (loadsave.compare(USER_SAVE_OPTION::EXIT_GAME) == 0) {
9✔
305
            std::cout << "Goodbye! Hope you had a good luck in the stock market!"
2✔
306
                      << std::endl;
2✔
307
            exit(EXIT_SUCCESS);
2✔
308
        }
309
    }
310
}
5✔
311

312
int main(void) {
7✔
313
    enableWindowsVTProcessing();
314
    std::cout << "The game was compiled on " << __DATE__ << " at " << __TIME__
7✔
315
              << std::endl;
7✔
316

317
    bool advance;          // Whether to advance to the next round
318
    bool gameQuit = false; // Whether the player wants to quit the game
7✔
319
    bool viewMode = false; // 0 to view table, 1 to view graph
7✔
320
    bool overlayEvent;     // Whether the event bar is being shown
321
    bool flush;            // Whether the screen needs updating
322
    int row;               // Number of characters to fit in a column
323
    int col;               // Number of characters to fit in a row
324
    fetchConsoleDimensions(row, col);
7✔
325

326
    std::vector<Stock> stocks_list;
7✔
327
    stocks_list.reserve(initial_stock_count);
7✔
328
    for (int i = 0; i < initial_stock_count; i++) {
147✔
329
        stocks_list.emplace_back();
140✔
330
    }
331

332
    sortStocksList(stocks_list, by_category, ascending);
7✔
333

334
    assertion_check_uniq_events();
7✔
335
    if (assertion_check_mutual_exclusivity()) {
7✔
UNCOV
336
        exit(1);
×
337
    }
338

339
    drawLogo(row, col);
7✔
340
    time::sleep(sleepMedium);
7✔
341
    std::vector<float> hsi_history;
7✔
342

343
    initializePlayerSaves(stocks_list, hsi_history);
7✔
344

345
    if (hsi_history.empty()) {
5✔
346
        get_hsi(stocks_list, hsi_history);
3✔
347
    }
348

349
    // Done loading/creating a new file.
350
    std::cout << "Current trading fees are charged at " << trading_fees_percent * 100
5✔
351
              << " %" << std::endl;
5✔
352
    time::sleep(sleepMedium * 2);
5✔
353

354
    while (!gameQuit) {
65✔
355
        advance = false;
60✔
356
        overlayEvent = false;
60✔
357
        flush = false;
60✔
358
        if (viewMode) {
60✔
359
            int indexGraph =
360
                integerInput(row, col, "Select stock index to display (0 for HSI): ");
6✔
361
            while (
6✔
362
                indexGraph < 0 || indexGraph > static_cast<int>(stocks_list.size())) {
6✔
UNCOV
363
                std::cout << setCursorPosition(row, 3) << "\x1b[2K";
×
UNCOV
364
                std::cout << "Index out of range!";
×
UNCOV
365
                time::sleep(sleepMedium);
×
UNCOV
366
                indexGraph = integerInput(
×
367
                    row, col, "Select stock index to display (0 for HSI): ");
368
            }
369
            std::cout << textClear << setCursorPosition(6, 0);
6✔
370
            graph_plotting(playerName, indexGraph - 1, col * 2 / 3, row - 10);
6✔
371
        }
372
        else {
373
            std::cout << textClear << setCursorPosition(6, 0);
54✔
374
            print_table(stocks_list, balance); // Print the table of stocks
54✔
375
        }
376
        drawRoundInfo(row, col, rounds_played, balance, playerName,
60✔
377
            hsi_history[hsi_history.size() - 1]);
60✔
378
        drawEventBar(row, col);
60✔
379
        drawButton(row, col);
60✔
380
        while (!flush) {
121✔
381
            optionsInput(row, col, balance, trading_fees_percent, stocks_list,
61✔
382
                get_ongoing_events(stocks_list), viewMode, advance, overlayEvent, flush,
122✔
383
                gameQuit);
384
        }
385

386
        if (advance) {
60✔
387
            next_round_routine(rounds_played, stocks_list);
44✔
388
            get_hsi(stocks_list, hsi_history);
44✔
389
            savestatus(rounds_played, stocks_list, balance, playerName);
44✔
390
            viewMode = false;
44✔
391
            time::sleep(sleepLong);
44✔
392
        }
393
    }
394
    return EXIT_SUCCESS;
5✔
395
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc