/************************************************************************ * * * Star Traders: A Game of Interstellar Trading * * Copyright (C) 1990-2011, John Zaitseff * * * ************************************************************************/ /* Author: John Zaitseff $Id$ This file, exch.c, contains the implementation of functions dealing with the Interstellar Stock Exchange and Trading Bank as used in Star Traders. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ #include "trader.h" /************************************************************************ * Internal function declarations * ************************************************************************/ void visit_bank (void); void trade_shares (int num, bool *bid_used); /************************************************************************ * Stock Exchange function definitions * ************************************************************************/ /*----------------------------------------------------------------------- Function: exchange_stock - Visit the Interstellar Stock Exchange Arguments: (none) Returns: (nothing) This function allows the current player (in current_player) to buy, sell and bid for shares in companies that appear on the galaxy map. */ void exchange_stock (void) { selection_t selection = SEL_NONE; bool bid_used = false; bool all_off_map; int i, line; if (quit_selected || abort_game || ! player[current_player].in_game) { return; } newtxwin(17, WIN_COLS, 1, WCENTER(WIN_COLS), false, 0); while (selection != SEL_EXIT) { selection = SEL_NONE; // Display (or refresh) the Stock Exchange window wbkgd(curwin, ATTR_NORMAL_WINDOW); werase(curwin); box(curwin, 0, 0); center(curwin, 1, ATTR_TITLE, " Interstellar Stock Exchange "); center2(curwin, 2, ATTR_NORMAL, ATTR_HIGHLIGHT, "Player: ", "%s", player[current_player].name); all_off_map = true; for (i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map) { all_off_map = false; break; } } if (all_off_map) { center(curwin, 8, ATTR_NORMAL, "No companies on the map"); } else { char *buf = malloc(BUFSIZE); if (buf == NULL) { err_exit_nomem(); } // Handle the locale's currency symbol snprintf(buf, BUFSIZE, "share (%s)", localeconv_info.currency_symbol); wattrset(curwin, ATTR_SUBTITLE); mvwprintw(curwin, 4, 2, " %-22s %12s %10s %10s %10s ", "", "Price per", "", "Shares", "Shares"); mvwprintw(curwin, 5, 2, " %-22s %12s %10s %10s %10s ", "Company", buf, "Return (%)", "issued", "left"); wattrset(curwin, ATTR_NORMAL); for (line = 6, i = 0; i < MAX_COMPANIES; i++) { if (company[i].on_map) { mvwaddch(curwin, line, 2, PRINTABLE_MAP_VAL(COMPANY_TO_MAP(i)) | ATTR_CHOICE); l_strfmon(buf, BUFSIZE, "%!12n", company[i].share_price); mvwprintw(curwin, line, 4, "%-22s %12s %10.2f %'10ld %'10ld ", company[i].name, buf, company[i].share_return * 100.0, company[i].stock_issued, company[i].max_stock - company[i].stock_issued); line++; } } free(buf); } wrefresh(curwin); // Show menu of choices for the player newtxwin(6, WIN_COLS, 18, WCENTER(WIN_COLS), true, ATTR_NORMAL_WINDOW); wmove(curwin, 3, 2); attrpr(curwin, ATTR_KEYCODE, "<1>"); waddstr(curwin, " Display stock portfolio"); wmove(curwin, 4, 2); attrpr(curwin, ATTR_KEYCODE, "<2>"); waddstr(curwin, " Display galaxy map"); wmove(curwin, 3, 40); attrpr(curwin, ATTR_KEYCODE, "<3>"); waddstr(curwin, " Visit the Trading Bank"); wmove(curwin, 4, 40); attrpr(curwin, ATTR_KEYCODE, "<4>"); waddstr(curwin, " Exit the Stock Exchange"); mvwaddstr(curwin, 1, 18, "Enter selection "); waddstr(curwin, "["); attrpr(curwin, ATTR_HIGHLIGHT, "Company letter"); waddstr(curwin, "/"); attrpr(curwin, ATTR_KEYCODE, "1"); waddstr(curwin, "-"); attrpr(curwin, ATTR_KEYCODE, "4"); waddstr(curwin, "]: "); curs_set(CURS_ON); wrefresh(curwin); // Get the actual selection made by the player while (selection == SEL_NONE) { int key = toupper(gettxchar(curwin)); if (IS_COMPANY_KEY(key)) { if (company[KEY_TO_COMPANY(key)].on_map) { selection = KEY_TO_COMPANY(key); } else { beep(); } } else { switch (key) { case '1': curs_set(CURS_OFF); show_status(current_player); curs_set(CURS_ON); break; case '2': curs_set(CURS_OFF); show_map(true); curs_set(CURS_ON); break; case '3': selection = SEL_BANK; break; case '4': case ' ': case KEY_CANCEL: case KEY_EXIT: case KEY_CTRL('C'): case KEY_CTRL('G'): case KEY_CTRL('\\'): selection = SEL_EXIT; break; default: beep(); break; } } } curs_set(CURS_OFF); deltxwin(); // "Enter selection" window txrefresh(); if (selection == SEL_BANK) { // Visit the Interstellar Trading Bank visit_bank(); } else if (selection == SEL_EXIT) { // Exit the Stock Exchange: nothing more to do ; } else { trade_shares(selection, &bid_used); } } deltxwin(); // "Stock Exchange" window txrefresh(); } /*----------------------------------------------------------------------- Function: visit_bank - Visit the Interstellar Trading Bank Arguments: (none) Returns: (nothing) This function allows the current player to borrow or repay money from the Interstellar Trading Bank. */ void visit_bank (void) { double credit_limit; int key; bool done; double val, max; char *buf; buf = malloc(BUFSIZE); if (buf == NULL) { err_exit_nomem(); } credit_limit = (total_value(current_player) - player[current_player].debt) * CREDIT_LIMIT_RATE; if (credit_limit < 0.0) { credit_limit = 0.0; } // Show the informational part of the Bank newtxwin(10, WIN_COLS - 4, 5, WCENTER(WIN_COLS - 4), true, ATTR_NORMAL_WINDOW); center(curwin, 1, ATTR_TITLE, " Interstellar Trading Bank "); l_strfmon(buf, BUFSIZE, "%18n", player[current_player].cash); center2(curwin, 3, ATTR_NORMAL, ATTR_HIGHLIGHT, "Current cash: ", " %s ", buf); l_strfmon(buf, BUFSIZE, "%18n", player[current_player].debt); center2(curwin, 4, ATTR_NORMAL, ATTR_HIGHLIGHT, "Current debt: ", " %s ", buf); center2(curwin, 5, ATTR_NORMAL, ATTR_HIGHLIGHT, "Interest rate: ", " %17.2f%% ", interest_rate * 100.0); l_strfmon(buf, BUFSIZE, "%18n", credit_limit); center2(curwin, 7, ATTR_HIGHLIGHT, ATTR_TITLE, "Credit limit: ", " %s ", buf); wrefresh(curwin); // Show menu of choices for the player newtxwin(7, WIN_COLS - 4, 15, WCENTER(WIN_COLS - 4), true, ATTR_NORMAL_WINDOW); center2(curwin, 3, ATTR_KEYCODE, ATTR_NORMAL, "<1>", " Borrow money "); center2(curwin, 4, ATTR_KEYCODE, ATTR_NORMAL, "<2>", " Repay debt "); center2(curwin, 5, ATTR_KEYCODE, ATTR_NORMAL, "<3>", " Exit from the Bank"); mvwaddstr(curwin, 1, 24, "Enter selection "); waddstr(curwin, "["); attrpr(curwin, ATTR_KEYCODE, "1"); waddstr(curwin, "-"); attrpr(curwin, ATTR_KEYCODE, "3"); waddstr(curwin, "]: "); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { key = gettxchar(curwin); switch (key) { case '1': case '2': case '3': case ' ': case KEY_CANCEL: case KEY_EXIT: case KEY_CTRL('C'): case KEY_CTRL('G'): case KEY_CTRL('\\'): done = true; break; default: beep(); break; } } curs_set(CURS_OFF); wechochar(curwin, key | A_BOLD); switch (key) { case '1': // Borrow money from the Bank if (credit_limit == 0.0) { newtxwin(7, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " Insufficient Credit Limit "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "The Bank will not lend you any more money"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else { int x, y, n; int ret; wbkgd(curwin, ATTR_NORMAL_WINDOW); werase(curwin); box(curwin, 0, 0); mvwprintw(curwin, 3, 10, "How much do you wish to borrow? "); wattron(curwin, A_BOLD); if (localeconv_info.p_cs_precedes == 1) { wprintw(curwin, "%s%s", localeconv_info.currency_symbol, (localeconv_info.p_sep_by_space == 1) ? " " : ""); n = 10; } else { getyx(curwin, y, x); n = strlen(localeconv_info.currency_symbol) + 10 + (localeconv_info.p_sep_by_space == 1); mvwprintw(curwin, y, getmaxx(curwin) - n, "%s%s", (localeconv_info.p_sep_by_space == 1) ? " " : "", localeconv_info.currency_symbol); wmove(curwin, y, x); } wattroff(curwin, A_BOLD); x = getcurx(curwin); ret = gettxdouble(curwin, &val, 0.0, credit_limit + ROUNDING_AMOUNT, 0.0, credit_limit, 3, x, getmaxx(curwin) - x - n, ATTR_INPUT_FIELD); if (ret == OK && val > ROUNDING_AMOUNT) { player[current_player].cash += val; player[current_player].debt += val * (interest_rate + 1.0); } } break; case '2': // Repay a debt if (player[current_player].debt == 0.0) { newtxwin(7, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " No Debt "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "You have no debt to repay"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else if (player[current_player].cash == 0.0) { newtxwin(7, 60, 8, WCENTER(60), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " No Cash "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "You have no cash with which to repay the debt!"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else { int x, y, n; int ret; wbkgd(curwin, ATTR_NORMAL_WINDOW); werase(curwin); box(curwin, 0, 0); mvwprintw(curwin, 3, 10, "How much do you wish to repay? "); wattron(curwin, A_BOLD); if (localeconv_info.p_cs_precedes == 1) { wprintw(curwin, "%s%s", localeconv_info.currency_symbol, (localeconv_info.p_sep_by_space == 1) ? " " : ""); n = 10; } else { getyx(curwin, y, x); n = strlen(localeconv_info.currency_symbol) + 10 + (localeconv_info.p_sep_by_space == 1); mvwprintw(curwin, y, getmaxx(curwin) - n, "%s%s", (localeconv_info.p_sep_by_space == 1) ? " " : "", localeconv_info.currency_symbol); wmove(curwin, y, x); } wattroff(curwin, A_BOLD); x = getcurx(curwin); max = MIN(player[current_player].cash, player[current_player].debt); ret = gettxdouble(curwin, &val, 0.0, max + ROUNDING_AMOUNT, 0.0, max, 3, x, getmaxx(curwin) - x - n, ATTR_INPUT_FIELD); if (ret == OK) { player[current_player].cash -= val; player[current_player].debt -= val; if (player[current_player].cash < ROUNDING_AMOUNT) { player[current_player].cash = 0.0; } if (player[current_player].debt < ROUNDING_AMOUNT) { player[current_player].debt = 0.0; } } } break; default: break; } deltxwin(); // "Enter selection" window deltxwin(); // Trading Bank window txrefresh(); free(buf); } /*----------------------------------------------------------------------- Function: trade_stock - Trade stock in a particular company Arguments: num - Company with which to trade bid_used - Has the player used up their bid? Returns: (nothing) This function allows the current player to buy and sell stock in company num. The variable *bid_used is set to true if the player tries to bid for more shares to be released by the company. */ void trade_shares (int num, bool *bid_used) { bool done; int key, ret, x; long maxshares, val; double ownership; char *buf; assert(num >= 0 && num < MAX_COMPANIES); buf = malloc(BUFSIZE); if (buf == NULL) { err_exit_nomem(); } ownership = (company[num].stock_issued == 0) ? 0.0 : ((double) player[current_player].stock_owned[num] / company[num].stock_issued); // Show the informational part of the trade window newtxwin(9, WIN_COLS - 4, 5, WCENTER(WIN_COLS - 4), true, ATTR_NORMAL_WINDOW); center(curwin, 1, ATTR_TITLE, " Stock Transaction in %s ", company[num].name); mvwaddstr(curwin, 3, 2, "Shares issued: "); attrpr(curwin, ATTR_HIGHLIGHT, "%'12ld", company[num].stock_issued); mvwaddstr(curwin, 4, 2, "Shares left: "); attrpr(curwin, ATTR_HIGHLIGHT, "%'12ld", company[num].max_stock - company[num].stock_issued); mvwaddstr(curwin, 5, 2, "Price per share: "); l_strfmon(buf, BUFSIZE, "%12n", company[num].share_price); attrpr(curwin, ATTR_HIGHLIGHT, "%12s", buf); mvwaddstr(curwin, 6, 2, "Return: "); attrpr(curwin, ATTR_HIGHLIGHT, "%11.2f%%", company[num].share_return * 100.0); mvwaddstr(curwin, 3, 38, "Current holdings: "); attrpr(curwin, ATTR_HIGHLIGHT, " %'16ld ", player[current_player].stock_owned[num]); mvwaddstr(curwin, 4, 38, "Percentage owned: "); attrpr(curwin, ATTR_HIGHLIGHT, " %'15.2f%% ", ownership * 100.0); wmove(curwin, 6, 38); attrpr(curwin, ATTR_HIGHLIGHT, "Current cash: "); l_strfmon(buf, BUFSIZE, "%16n", player[current_player].cash); attrpr(curwin, ATTR_TITLE, " %16s ", buf); wrefresh(curwin); // Show menu of choices for the player newtxwin(7, WIN_COLS - 4, 14, WCENTER(WIN_COLS - 4), true, ATTR_NORMAL_WINDOW); wmove(curwin, 3, 2); attrpr(curwin, ATTR_KEYCODE, "<1>"); waddstr(curwin, " Buy stock from company"); wmove(curwin, 4, 2); attrpr(curwin, ATTR_KEYCODE, "<2>"); waddstr(curwin, " Sell stock back to company"); wmove(curwin, 3, 38); attrpr(curwin, ATTR_KEYCODE, "<3>"); waddstr(curwin, " Bid company to issue more shares"); wmove(curwin, 4, 38); attrpr(curwin, ATTR_KEYCODE, "<4>"); waddstr(curwin, " Exit to the Stock Exchange"); mvwaddstr(curwin, 1, 24, "Enter selection "); waddstr(curwin, "["); attrpr(curwin, ATTR_KEYCODE, "1"); waddstr(curwin, "-"); attrpr(curwin, ATTR_KEYCODE, "4"); waddstr(curwin, "]: "); curs_set(CURS_ON); wrefresh(curwin); done = false; while (! done) { key = gettxchar(curwin); switch (key) { case '1': case '2': case '3': case '4': case ' ': case KEY_CANCEL: case KEY_EXIT: case KEY_CTRL('C'): case KEY_CTRL('G'): case KEY_CTRL('\\'): done = true; break; default: beep(); break; } } curs_set(CURS_OFF); wechochar(curwin, key | A_BOLD); switch (key) { case '1': // Buy stock in company maxshares = player[current_player].cash / company[num].share_price; if (company[num].max_stock - company[num].stock_issued == 0) { newtxwin(7, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " No Shares Available "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "No more shares are available for purchase"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else if (maxshares <= 0) { newtxwin(7, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " Insufficient Cash "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "Not enough cash to purchase shares"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else { maxshares = MIN(maxshares, company[num].max_stock - company[num].stock_issued); wbkgd(curwin, ATTR_NORMAL_WINDOW); werase(curwin); box(curwin, 0, 0); center3(curwin, 2, ATTR_NORMAL, ATTR_NORMAL, ATTR_HIGHLIGHT, "You can purchase up to ", " shares.", "%'ld", maxshares); mvwprintw(curwin, 4, 10, "How many shares do you wish to purchase? "); x = getcurx(curwin); ret = gettxlong(curwin, &val, 0, maxshares, 0, maxshares, 4, x, getmaxx(curwin) - x - 10, ATTR_INPUT_FIELD); if (ret == OK) { player[current_player].cash -= val * company[num].share_price; player[current_player].stock_owned[num] += val; company[num].stock_issued += val; } } break; case '2': // Sell stock back to company maxshares = player[current_player].stock_owned[num]; if (maxshares == 0) { newtxwin(7, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " No Shares "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "You do not have any shares to sell"); wait_for_key(curwin, 5, ATTR_ERROR_WAITFORKEY); deltxwin(); } else { wbkgd(curwin, ATTR_NORMAL_WINDOW); werase(curwin); box(curwin, 0, 0); center3(curwin, 2, ATTR_NORMAL, ATTR_NORMAL, ATTR_HIGHLIGHT, "You can sell up to ", " shares.", "%'ld", maxshares); mvwprintw(curwin, 4, 10, "How many shares do you wish to sell? "); x = getcurx(curwin); ret = gettxlong(curwin, &val, 0, maxshares, 0, maxshares, 4, x, getmaxx(curwin) - x - 10, ATTR_INPUT_FIELD); if (ret == OK) { company[num].stock_issued -= val; player[current_player].stock_owned[num] -= val; player[current_player].cash += val * company[num].share_price; } } break; case '3': // Bid company to issue more shares maxshares = 0; if (! *bid_used && randf() < ownership && randf() < BID_CHANCE) { maxshares = randf() * ownership * MAX_SHARES_BIDDED; company[num].max_stock += maxshares; } *bid_used = true; if (maxshares == 0) { newtxwin(8, 50, 8, WCENTER(50), true, ATTR_ERROR_WINDOW); center(curwin, 1, ATTR_ERROR_TITLE, " No Shares Issued "); center(curwin, 3, ATTR_ERROR_HIGHLIGHT, "%s", company[num].name); center(curwin, 4, ATTR_ERROR_HIGHLIGHT, "has refused to issue more shares"); wait_for_key(curwin, 6, ATTR_ERROR_WAITFORKEY); deltxwin(); } else { newtxwin(8, 50, 8, WCENTER(50), true, ATTR_NORMAL_WINDOW); center(curwin, 1, ATTR_TITLE, " Shares Issued "); center(curwin, 3, ATTR_HIGHLIGHT, "%s", company[num].name); center(curwin, 4, ATTR_HIGHLIGHT, "has issued %'ld more shares", maxshares); wait_for_key(curwin, 6, ATTR_WAITFORKEY); deltxwin(); } break; default: break; } deltxwin(); // "Enter selection" window deltxwin(); // Stock Transaction window txrefresh(); free(buf); }