Stock Market Simulator main e8c3612
A game that provides a realistic stock buying experience with unpredictable trends to test investment strategies.
Loading...
Searching...
No Matches
main.cpp
Go to the documentation of this file.
1/// @file main.cpp
2/// file with the main() function
3/*
4This program is free software: you can redistribute it and/or modify it under the
5terms of the GNU Lesser General Public License as published by the Free Software
6Foundation, either version 3 of the License, or (at your option) any later version.
7
8This program is distributed in the hope that it will be useful, but WITHOUT ANY
9WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
10PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
11
12You should have received a copy of the GNU Lesser General Public License along with this
13program. 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 "random_price.h"
23#include "stock.h"
24
25#if defined(__GNUC__) || defined(__clang__)
26#pragma GCC diagnostic push
27#pragma GCC diagnostic ignored "-Wold-style-cast"
28#endif
29#include "nonstdlibs/VariadicTable.h"
30#if defined(__GNUC__) || defined(__clang__)
31#pragma GCC diagnostic pop
32#endif
33
34#include <cmath>
35#include <fstream>
36#include <numeric>
37
38#ifdef _WIN32
39#define NOMINMAX 1 // Prevent Windows.h from defining min and max macros
40#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
41#include <windows.h>
42/** @brief Enable Windows VT processing for ANSI escape codes
43 * @details Without this, ANSI escape codes will not work on Windows 10.
44 * E.g. text color, cursor position, etc.
45 */
47 // Set the console to UTF-8 mode
48 SetConsoleOutputCP(65001);
49 // Get the current console mode
50 DWORD consoleMode;
51 GetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), &consoleMode);
52 // Enable virtual terminal processing
53 consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
54 SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), consoleMode);
55 std::cout << "Experimental Windows VT processing enabled." << std::endl;
56}
57#else
58#define enableWindowsVTProcessing() // Do nothing
59#endif
60
61/**
62 * <value> / 100 means charging <value>% more/portion of the money involved in stock
63 * operations.
64 */
65const float trading_fees_percent = 0.1 / 100;
66
67/** Player's balance */
68float balance = 1000.0f;
69/** Number of rounds played */
70unsigned int rounds_played = 1;
71
72/** Player's name */
73std::string playerName;
74
75std::string vectorToString(const std::vector<unsigned int> & vec) {
76 return std::accumulate(
77 vec.begin(), vec.end(), std::string(), [](const std::string & s, int v) {
78 return s.empty() ? std::to_string(v) : s + " " + std::to_string(v);
79 });
80}
81
82void get_hsi(std::vector<Stock> stocks_list, std::vector<float> & hsi_history) {
83 float hsi = 0;
84 std::string filesave =
86 std::vector<float> total;
87 for (unsigned int i = 0; i < stocks_list.size(); i++) {
88 total.emplace_back(
89 stocks_list[i].get_price() / stocks_list[i].get_initial_price() * 1000 *
90 static_cast<float>(std::pow(2, stocks_list[i].get_split_count())));
91 // HSI formula = (price/initial price) * 1000 * 2^split count
92 }
93 hsi = std::reduce(total.begin(), total.end()) / total.size();
94 hsi_history.emplace_back(hsi);
95 std::ofstream fout;
96 fout.open(filesave.c_str(), std::ios::app);
97 fout << hsi << ' ';
98 fout.close();
99}
100
101/**
102 * @brief hiding mean/sd/uplim/lowlim/event_id columns in the table
103 */
104enum mode { normal, dev };
105
106/** Print the table of stocks. We put it in a function so we can call it multiple times.
107 * @param stocks_list A vector of stocks. The stocks to be printed.
108 * @param _playerBal How much money the player has.
109 * @param m mode to hide mean/sd/uplim/lowlim/event_id columns in the table
110 */
111void print_table(std::vector<Stock> stocks_list, float _playerBal, mode m = dev) {
112 std::vector<std::string> defaultColumns = {
113 "#", "Category", "Name", "$Price", "Change", R"(%Change)", "#Has", "#Max"};
114 VariadicTable<unsigned int, std::string, std::string, float, float, float,
115 unsigned int, unsigned int>
116 defaultTable(defaultColumns);
117 if (m == dev) {
118 defaultColumns.emplace_back(" Mean ");
119 defaultColumns.emplace_back(" SD ");
120 defaultColumns.emplace_back(" up ");
121 defaultColumns.emplace_back(" low ");
122 defaultColumns.emplace_back("event_id");
123 // Create a table, note that R"(% Change)" is a raw string literal (C++11
124 // feature).
125 VariadicTable<unsigned int, std::string, std::string, float, float, float,
126 unsigned int, unsigned int, float, float, float, float, std::string>
127 devTable({defaultColumns});
128 /* Set the precision and format of the columns.
129 * Note: Precision and Format is ignored for std::string columns. */
130 devTable.setColumnPrecision({0, 0, 0, 2, 2, 2, 0, 0, 1, 0, 0, 0, 0});
131 devTable.setColumnFormat({VariadicTableColumnFormat::AUTO,
132 VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::AUTO,
133 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
134 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
135 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
136 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
137 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::AUTO});
138 for (unsigned int i = 0; i < stocks_list.size(); i++) {
139 std::map<stock_modifiers, float> modifiers =
140 getProcessedModifiers(stocks_list[i]);
141 devTable.addRow(i + 1, stocks_list[i].category_name(),
142 stocks_list[i].get_name(), stocks_list[i].get_price(),
143 stocks_list[i].delta_price(),
144 stocks_list[i].delta_price_percentage() * 100,
145 stocks_list[i].get_quantity(),
146 stocks_list[i].num_stocks_affordable(_playerBal, trading_fees_percent),
147 modifiers[mean], modifiers[standard_deviation], modifiers[upper_limit],
148 modifiers[lower_limit], vectorToString(stocks_list[i].get_event_ids()));
149 }
150 devTable.print(std::cout);
151 }
152 else {
153 /* Set the precision and format of the columns.
154 * Note: Precision and Format is ignored for std::string columns. */
155 defaultTable.setColumnPrecision({0, 0, 0, 2, 2, 2, 0, 0});
156 defaultTable.setColumnFormat(
157 {VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::AUTO,
158 VariadicTableColumnFormat::AUTO, VariadicTableColumnFormat::FIXED,
159 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED,
160 VariadicTableColumnFormat::FIXED, VariadicTableColumnFormat::FIXED});
161 for (unsigned int i = 0; i < stocks_list.size(); i++) {
162 defaultTable.addRow(i + 1, stocks_list[i].category_name(),
163 stocks_list[i].get_name(), stocks_list[i].get_price(),
164 stocks_list[i].delta_price(),
165 stocks_list[i].delta_price_percentage() * 100,
166 stocks_list[i].get_quantity(),
167 stocks_list[i].num_stocks_affordable(_playerBal, trading_fees_percent));
168 }
169 defaultTable.print(std::cout);
170 }
171 // Modify the stringstream so that for the column "Change", the text
172 // "Increase" is green and "Decrease" is red.
173 // @note This is a workaround because VariadicTable does not support
174 // modifying the text color of a specific cell.
175 // Warning: This is a hack and may not work in the future!
176 for (unsigned int i = 0; i < stocks_list.size(); i++) {
177 std::string index = std::to_string(i + 1);
178 if (i < 10 - 1) {
179 index = " " + index;
180 }
181 if (stocks_list[i].delta_price() > 0) {
182 std::cout << setCursorPosition(i + 9, 3) << textGreen << index;
183 }
184 else if (stocks_list[i].delta_price() < 0) {
185 std::cout << setCursorPosition(i + 9, 3) << textRed << index;
186 }
187 }
188 std::cout << textWhite;
189 /* Display 2 decimal places for balance.
190 * This line reverts the precision back to default after the table is printed.
191 * Since the table uses std::auto (VariadicTableColumnFormat::AUTO), we need to
192 * revert it back to default.
193 */
194 std::cout << std::fixed << std::setprecision(2);
195}
196
197/**
198 * Get all the ongoing events.
199 * @param stocks_list A vector of stocks.
200 * @return A vector of Stock_event
201 */
202std::vector<Stock_event> get_ongoing_events(std::vector<Stock> stocks_list) {
203 // Return a vector of ongoing events without duplicates
204 std::vector<Stock_event> ongoing_events = {};
205 for (unsigned int i = 0; i < stocks_list.size(); i++) {
206 std::list<Stock_event> events = stocks_list[i].get_events();
207 for (const Stock_event & event : events) {
208 // Side note: Events with duration <= 0 are automatically removed from the
209 // stock's event list. By stock.cpp Stock::next_round() which uses
210 // Stock::remove_obselete_event()
211 if (event.duration > 0) {
212 // If the event is not in the ongoing_events, add it.
213 if (std::find(ongoing_events.begin(), ongoing_events.end(), event) ==
214 ongoing_events.end()) {
215 ongoing_events.emplace_back(event);
216 }
217 }
218 }
219 }
220 return ongoing_events;
221}
222
223/**
224 * @brief Generate new events and apply them to the stocks. Should be called at the
225 * beginning of each round.
226 * @param stocks_list A vector of stocks. Pass by reference to modify the stocks.
227 */
228void new_events_next_round(std::vector<Stock> & stocks_list) {
229 /** @note numEvents is the sum of these three values:
230 * - 1
231 * - A random integer between 0 and 1 (uniform distribution)
232 * - 1 if more than 10 rounds have been played
233 * If there was already more than 5 events, we will not generate more events.
234 */
235 unsigned int numEvents = 1 + random_integer(1) + (rounds_played / 5 > 2) * 1;
236 if (get_ongoing_events(stocks_list).size() > 5) {
237 return;
238 }
239 std::vector<Stock_event> picked_events = pick_events(all_stock_events, numEvents);
240 for (const Stock_event & event : picked_events) {
241 switch (event.type_of_event) {
242 case all_stocks:
243 for (unsigned int i = 0; i < stocks_list.size(); i++) {
244 stocks_list[i].add_event(event);
245 }
246 break;
247 case category:
248 for (unsigned int i = 0; i < stocks_list.size(); i++) {
249 if (stocks_list[i].get_category() == event.category) {
250 stocks_list[i].add_event(event);
251 }
252 }
253 break;
254 case pick_random_stock: {
255 std::vector<unsigned int> stocks_indices_not_suitable = {};
256 while (!stocks_list.empty() &&
257 stocks_list.size() < stocks_indices_not_suitable.size()) {
258 // Pick a random stock
259 unsigned int choice = random_integer(stocks_list.size());
260 Stock lucky_stock = stocks_list[choice];
261 if (!lucky_stock.can_add_event(event)) {
262 stocks_indices_not_suitable.emplace_back(choice);
263 }
264 else {
265 Stock_event modified_event = event;
266 modified_event.text = lucky_stock.get_name() + " " + event.text;
267 lucky_stock.add_event(modified_event);
268 break;
269 }
270 }
271 break;
272 }
273 default:
274 // Should not reach here, but if it does, break the loop
275 // so that the player can continue playing the game.
276 break;
277 }
278 }
279}
280
281void next_round_routine(unsigned int & _rounds, std::vector<Stock> & stocks_list) {
282 _rounds++; // Increment the round number
284 stocks_list); // Generate new events and apply them to the stocks
285 for (unsigned int i = 0; i < stocks_list.size(); i++) {
286 stocks_list[i].next_round(); // Update the stock price
287 }
288}
289
291 std::vector<Stock> & stocks_list, std::vector<float> & hsi_history) {
292 std::string EMPTY_INPUT = "";
293 std::string loadsave = EMPTY_INPUT;
294 while (loadsave.compare(EMPTY_INPUT) == 0) {
295 std::cout << USER_SAVE_OPTION_PROMPT;
296 std::cin >> loadsave;
297 while (!checkValidInput(loadsave)) {
298 std::cout << "Invalid input.\n" << USER_SAVE_OPTION_PROMPT;
299 std::cin >> loadsave; // choose new file or load previous file
300 }
301 if (loadsave.compare(USER_SAVE_OPTION::NEW_GAME) == 0) {
304 }
305 if (loadsave.compare(USER_SAVE_OPTION::LOAD_GAME) == 0) {
306 loadstatus(rounds_played, stocks_list, balance, playerName, hsi_history);
307 }
308 if (loadsave.compare(USER_SAVE_OPTION::DELETE_GAME) == 0) {
309 delsave(loadsave);
310 loadsave = EMPTY_INPUT;
311 }
312 if (loadsave.compare(USER_SAVE_OPTION::EXIT_GAME) == 0) {
313 std::cout << "Goodbye! Hope you had a good luck in the stock market!"
314 << std::endl;
315 exit(EXIT_SUCCESS);
316 }
317 }
318}
319
320int main(void) {
322 std::cout << "The game was compiled on " << __DATE__ << " at " << __TIME__
323 << std::endl;
324
325 bool advance; // Whether to advance to the next round
326 bool gameQuit = false; // Whether the player wants to quit the game
327 bool viewMode = false; // 0 to view table, 1 to view graph
328 bool overlayEvent; // Whether the event bar is being shown
329 bool flush; // Whether the screen needs updating
330 int row; // Number of characters to fit in a column
331 int col; // Number of characters to fit in a row
332 fetchConsoleDimensions(row, col);
333
334 std::vector<Stock> stocks_list;
335 stocks_list.reserve(initial_stock_count);
336 for (int i = 0; i < initial_stock_count; i++) {
337 stocks_list.emplace_back();
338 }
339
340 sortStocksList(stocks_list, by_category, ascending);
341
344 exit(1);
345 }
346
347 drawLogo(row, col);
349 std::vector<float> hsi_history;
350
351 initializePlayerSaves(stocks_list, hsi_history);
352
353 if (hsi_history.empty()) {
354 get_hsi(stocks_list, hsi_history);
355 }
356
357 // Done loading/creating a new file.
358 std::cout << "Current trading fees are charged at " << trading_fees_percent * 100
359 << " %" << std::endl;
361
362 while (!gameQuit) {
363 advance = false;
364 overlayEvent = false;
365 flush = false;
366 if (viewMode) {
367 int indexGraph =
368 integerInput(row, col, "Select stock index to display (0 for HSI): ");
369 while (
370 indexGraph < 0 || indexGraph > static_cast<int>(stocks_list.size())) {
371 std::cout << setCursorPosition(row, 3) << "\x1b[2K";
372 std::cout << "Index out of range!";
374 indexGraph = integerInput(
375 row, col, "Select stock index to display (0 for HSI): ");
376 }
377 std::cout << textClear << setCursorPosition(6, 0);
378 graph_plotting(playerName, indexGraph - 1, col * 2 / 3, row - 10);
379 }
380 else {
381 std::cout << textClear << setCursorPosition(6, 0);
382 print_table(stocks_list, balance); // Print the table of stocks
383 }
385 hsi_history[hsi_history.size() - 1]);
386 drawEventBar(row, col);
387 drawButton(row, col);
388 while (!flush) {
389 optionsInput(row, col, balance, trading_fees_percent, stocks_list,
390 get_ongoing_events(stocks_list), viewMode, advance, overlayEvent, flush,
391 gameQuit);
392 }
393
394 if (advance) {
395 next_round_routine(rounds_played, stocks_list);
396 get_hsi(stocks_list, hsi_history);
398 viewMode = false;
400 }
401 }
402 return EXIT_SUCCESS;
403}
A class that represents a stock object in the game.
Definition stock.h:55
void add_event(const Stock_event &event)
Add an event to the stock.
Definition stock.cpp:210
std::string get_name(void)
Get the name of the stock.
Definition stock.h:159
bool can_add_event(const Stock_event &event)
Check if we can add the event to the stock.
Definition stock.cpp:231
static const std::string DELETE_GAME
Definition file_io.h:25
static const std::string EXIT_GAME
Definition file_io.h:26
static const std::string NEW_GAME
Definition file_io.h:23
static const std::string LOAD_GAME
Definition file_io.h:24
static void sleep(int dur)
Definition format.cpp:52
int integerInput(int row, int col, const std::string &message)
Definition controls.cpp:71
void optionsInput(int row, int col, float &balance, float tax, std::vector< Stock > &stocks, const std::vector< Stock_event > &events, bool &viewMode, bool &advance, bool &overlayEvent, bool &flush, bool &gameQuit)
Definition controls.cpp:17
(Declarations of) human-computer interactions functions.
void drawEventBar(int row, int col)
Definition draw.cpp:73
void drawButton(int row, int col)
Definition draw.cpp:156
void drawRoundInfo(int row, int col, int round, float balance, std::string player, float indexHSI)
Definition draw.cpp:52
void drawLogo(int row, int col)
Definition draw.cpp:19
Function declarations for drawing/display of various elements.
const std::vector< Stock_event > all_stock_events
The list of all events that can be applied to the stocks.
Definition events.cpp:49
bool assertion_check_mutual_exclusivity(void)
Check if there are mutual exclusivity violations in all_stock_events.
Definition events.cpp:1331
std::vector< Stock_event > pick_events(const std::vector< Stock_event > &all_events, unsigned int num_events)
Pick a random event from the list of events.
Definition events.cpp:1348
void assertion_check_uniq_events(void)
Definition events.cpp:1416
Implements event-related data structures and (declarations of) such functions.
@ upper_limit
The upper limit of the stock price percentage change.
Definition events.h:48
@ standard_deviation
Amount of variation of the stock price percentage change.
Definition events.h:32
@ mean
The expectation of the stock price percentage change.
Definition events.h:36
@ lower_limit
The lower limit of the stock price percentage change.
Definition events.h:42
@ pick_random_stock
This event will apply to a randomly selected stock from all of the stocks available currently.
Definition events.h:95
@ category
This event will apply to stocks within the specified category.
Definition events.h:92
@ all_stocks
This event will apply to all stocks.
Definition events.h:90
void loadstatus(unsigned int &rounds_played, vector< Stock > &stocks_list, float &balance, string &playerName, vector< float > &hsi_history)
Load an existing game status from .save files.
Definition file_io.cpp:120
const std::string USER_SAVE_OPTION_PROMPT
Definition file_io.cpp:28
void delsave(string &mode)
Delete a save.
Definition file_io.cpp:158
void createplayer(string &playerName)
Create a player folder.
Definition file_io.cpp:69
void savestatus(unsigned int rounds_played, vector< Stock > stocks_list, float balance, const string &playerName)
Save the game status into *.save files.
Definition file_io.cpp:104
Header files for file operation functions related to the game.
bool checkValidInput(const std::string &input)
Check if the input is valid.
Definition file_io.h:37
const std::string SAVE_FOLDER_PREFIX
Definition file_io.h:45
const std::string SAVE_FILE_EXTENSION_TXT
Definition file_io.h:48
const string textGreen
Definition format.cpp:31
const string textWhite
Definition format.cpp:36
const int sleepMedium
Definition format.cpp:49
const string textRed
Definition format.cpp:30
void fetchConsoleDimensions(int &row, int &col)
Definition format.cpp:62
string setCursorPosition(int offsetY, int offsetX)
Definition format.cpp:56
const string textClear
Definition format.cpp:20
const int sleepLong
Definition format.cpp:50
Header file for the ANSI Escape code related functions.
void graph_plotting(const string &player, int stocknum, int width, int height)
Plot the graph of the stock price history to std::cout.
Definition graph.cpp:135
Declaration of graph plotting function.
#define enableWindowsVTProcessing()
Definition main.cpp:58
mode
hiding mean/sd/uplim/lowlim/event_id columns in the table
Definition main.cpp:104
@ dev
Definition main.cpp:104
@ normal
Definition main.cpp:104
const float trading_fees_percent
/ 100 means charging % more/portion of the money involved in stock operations.
Definition main.cpp:65
void get_hsi(std::vector< Stock > stocks_list, std::vector< float > &hsi_history)
Definition main.cpp:82
float balance
Player's balance.
Definition main.cpp:68
void initializePlayerSaves(std::vector< Stock > &stocks_list, std::vector< float > &hsi_history)
Definition main.cpp:290
int main(void)
Definition main.cpp:320
void new_events_next_round(std::vector< Stock > &stocks_list)
Generate new events and apply them to the stocks.
Definition main.cpp:228
void print_table(std::vector< Stock > stocks_list, float _playerBal, mode m=dev)
Print the table of stocks.
Definition main.cpp:111
void next_round_routine(unsigned int &_rounds, std::vector< Stock > &stocks_list)
Definition main.cpp:281
std::vector< Stock_event > get_ongoing_events(std::vector< Stock > stocks_list)
Get all the ongoing events.
Definition main.cpp:202
unsigned int rounds_played
Number of rounds played.
Definition main.cpp:70
std::string vectorToString(const std::vector< unsigned int > &vec)
Definition main.cpp:75
std::string playerName
Player's name.
Definition main.cpp:73
std::map< stock_modifiers, float > getProcessedModifiers(Stock stock)
Get the processed modifiers for the stock.
unsigned int random_integer(unsigned int max_integer)
python randint like function
Header file for random related functions.
void sortStocksList(std::vector< Stock > &stocks_list, SortingMethods sortMethod, SortingDirections sortDirection)
Sorts the stocks.
Definition stock.cpp:321
Declaration of the Stock class.
@ by_category
Definition stock.h:311
const int initial_stock_count
Initial stock count.
Definition stock.h:28
@ ascending
Definition stock.h:319
The data structure of an event that will be applied to the stocks.
Definition events.h:104
std::string text
The text that will be displayed to the player.
Definition events.h:116