1
0
Fork 0
trader/src/game.c

885 lines
25 KiB
C

/************************************************************************
* *
* Star Traders: A Game of Interstellar Trading *
* Copyright (C) 1990-2024, John Zaitseff *
* *
************************************************************************/
/*
Author: John Zaitseff <J.Zaitseff@zap.org.au>
$Id$
This file, game.c, contains the implementation of the starting and
ending game functions used in Star Traders, as well as the functions
for displaying the galaxy map and the player's status.
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 https://www.gnu.org/licenses/.
*/
#include "trader.h"
/************************************************************************
* Module-specific function prototypes *
************************************************************************/
/*
Function: ask_number_players - Ask for the number of players
Parameters: (none)
Returns: int - Number of players, 0 to load game, ERR to cancel
This internal function asks the user how many people will play. It
returns a number 1 to MAX_PLAYERS as a response, or 0 if a previous
game is to be loaded, or ERR if the user wishes to abort.
Please note that the window opened by this function is NOT closed!
*/
static int ask_number_players (void);
/*
Function: ask_game_number - Ask for the game number
Parameters: (none)
Returns: int - Game number (1-9) or ERR to cancel
This internal function asks the user which game number to load. It
returns a number 1 to 9 as a response, or ERR if the user wishes to
cancel loading a game.
Please note that the window opened by this function is NOT closed!
*/
static int ask_game_number (void);
/*
Function: ask_player_names - Ask for each of the players' names
Parameters: (none)
Returns: (nothing)
This internal function asks each player to type in their name. After
doing so, the players are asked whether they need instructions on
playing the game.
On entry, the global variable number_players is used to determine how
many people are playing. On exit, each player[].name is set. The
windows created by this function ARE closed, but not any other window.
Note also that txrefresh() is NOT called.
*/
static void ask_player_names (void);
/*
Function: cmp_player - Compare two player[] elements for sorting
Parameters: a, b - Pointers to elements to compare
Returns: int - Comparison of a and b
This internal function compares two player[] elements (of type
player_info_t) on the basis of total value, and returns -1 if a > b, 0
if a == b and 1 if a < b (note the change from the usual order!). It
is used for sorting players into descending total value order. Note
that the sort_value field MUST be computed before calling qsort()!
*/
static int cmp_player (const void *a, const void *b);
/************************************************************************
* Game function definitions *
************************************************************************/
/* These functions are documented either in the file "game.h" or in the
comments above. */
/***********************************************************************/
// init_game: Initialise a new game or load an old one
void init_game (void)
{
// Try to load an old game, if possible
if (game_num != 0) {
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int width;
mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, WIN_COLS - 7,
&width, 1, _("Loading game %d... "), game_num);
newtxwin(5, width + 5, 6, WCENTER, true, attr_status_window);
centerch(curwin, 2, 0, chbuf, 1, &width);
wrefresh(curwin);
game_loaded = load_game(game_num);
deltxwin();
txrefresh();
free(chbuf);
}
// Initialise game data, if not already loaded
if (! game_loaded) {
number_players = 0;
while (number_players == 0) {
int choice = ask_number_players();
if (choice == ERR) {
abort_game = true;
return;
} else if (choice == 0) {
choice = ask_game_number();
if (choice != ERR) {
// Try to load the game, if possible
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int width;
game_num = choice;
mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1,
WIN_COLS - 7, &width, 1,
_("Loading game %d... "), game_num);
newtxwin(5, width + 5, 9, WCENTER, true, attr_status_window);
centerch(curwin, 2, 0, chbuf, 1, &width);
wrefresh(curwin);
game_loaded = load_game(game_num);
deltxwin();
txrefresh();
free(chbuf);
}
deltxwin(); // "Enter game number" window
deltxwin(); // "Number of players" window
txrefresh();
} else {
number_players = choice;
}
}
if (! game_loaded) {
wchar_t *buf = xmalloc(BUFSIZE * sizeof(wchar_t));
ask_player_names();
deltxwin(); // "Number of players" window
txrefresh();
// Initialise player data (other than names)
for (int i = 0; i < number_players; i++) {
player[i].cash = INITIAL_CASH;
player[i].debt = 0.0;
player[i].in_game = true;
for (int j = 0; j < MAX_COMPANIES; j++) {
player[i].stock_owned[j] = 0;
}
}
// Initialise company data
for (int i = 0; i < MAX_COMPANIES; i++) {
xmbstowcs(buf, gettext(company_name[i]), BUFSIZE);
company[i].name = xwcsdup(buf);
company[i].share_price = 0.0;
company[i].share_return = INITIAL_RETURN;
company[i].stock_issued = 0;
company[i].max_stock = 0;
company[i].on_map = false;
}
// Initialise galaxy map
for (int x = 0; x < MAX_X; x++) {
for (int y = 0; y < MAX_Y; y++) {
galaxy_map[x][y] = (randf() < STAR_RATIO) ?
MAP_STAR : MAP_EMPTY;
}
}
// Miscellaneous initialisation
interest_rate = INITIAL_INTEREST_RATE;
max_turn = option_max_turn ? option_max_turn : DEFAULT_MAX_TURN;
turn_number = 1;
// Select who is to go first
if (number_players == 1) {
first_player = 0;
current_player = 0;
} else {
first_player = randi(number_players);
current_player = first_player;
txdlgbox(MAX_DLG_LINES, 50, 8, WCENTER, attr_normal_window,
attr_title, attr_normal, attr_highlight, 0,
attr_waitforkey, _(" First Player "),
_("The first player to go is ^{%ls^}."),
player[first_player].name);
txrefresh();
}
free(buf);
}
}
quit_selected = false;
abort_game = false;
}
/***********************************************************************/
// ask_number_players: Ask for the number of players
static int ask_number_players (void)
{
wchar_t *keycode_contgame = xmalloc(BUFSIZE * sizeof(wchar_t));
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int lines, maxwidth;
int widthbuf[2];
int ret;
bool done;
lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 2,
WIN_COLS - 7, widthbuf, 2,
/* TRANSLATORS: The keycode <C> should be modified to
match that (or those) specified with msgctxt
"input|ContinueGame". */
_("Enter number of players [^{1^}-^{%d^}] "
"or ^{<C>^} to continue a game: "), MAX_PLAYERS);
assert(lines == 1 || lines == 2);
maxwidth = (lines == 1 ? widthbuf[0] : MAX(widthbuf[0], widthbuf[1])) + 5;
newtxwin(lines + 4, maxwidth, 3, WCENTER, true, attr_normal_window);
leftch(curwin, 2, 2, chbuf, lines, widthbuf);
free(chbuf);
curs_set(CURS_ON);
wrefresh(curwin);
/* TRANSLATORS: This string specifies the keycodes used to continue a
game; these must NOT contain any numeric digit from 1 to 9. The
first character (keyboard input code) is used to print the user's
response if one of those keys is pressed. Both upper and
lower-case versions should be present. */
xmbstowcs(keycode_contgame, pgettext("input|ContinueGame", "Cc"), BUFSIZE);
done = false;
while (! done) {
wint_t key;
if (gettxchar(curwin, &key) == OK) {
// Ordinary wide character
if (key >= L'1' && key <= MAX_PLAYERS + L'0') {
left(curwin, getcury(curwin), getcurx(curwin), A_BOLD,
0, 0, 1, "%lc", key);
wrefresh(curwin);
ret = key - L'0';
done = true;
} else if (wcschr(keycode_contgame, key) != NULL) {
left(curwin, getcury(curwin), getcurx(curwin), A_BOLD,
0, 0, 1, "%lc", (wint_t) *keycode_contgame);
wrefresh(curwin);
ret = 0;
done = true;
} else {
beep();
}
} else {
// Function or control key
switch (key) {
case KEY_ESC:
case KEY_CANCEL:
case KEY_EXIT:
case KEY_CTRL('C'):
case KEY_CTRL('G'):
case KEY_CTRL('\\'):
ret = ERR;
done = true;
break;
default:
beep();
}
}
}
curs_set(CURS_OFF);
free(keycode_contgame);
return ret;
}
/***********************************************************************/
// ask_game_number: Ask for the game number
int ask_game_number (void)
{
chtype *chbuf;
int lines, maxwidth;
int widthbuf[2];
int ret;
bool done;
chbuf = xmalloc(BUFSIZE * sizeof(chtype));
lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 2,
WIN_COLS - 7, widthbuf, 2,
_("Enter game number [^{1^}-^{9^}] "
"or ^{<CTRL><C>^} to cancel: "));
assert(lines == 1 || lines == 2);
maxwidth = (lines == 1 ? widthbuf[0] : MAX(widthbuf[0], widthbuf[1])) + 5;
newtxwin(lines + 4, maxwidth, 6, WCENTER, true, attr_normal_window);
leftch(curwin, 2, 2, chbuf, lines, widthbuf);
free(chbuf);
curs_set(CURS_ON);
wrefresh(curwin);
done = false;
while (! done) {
wint_t key;
if (gettxchar(curwin, &key) == OK) {
// Ordinary wide character
if (key >= L'1' && key <= L'9') {
left(curwin, getcury(curwin), getcurx(curwin), A_BOLD,
0, 0, 1, "%lc", key);
wrefresh(curwin);
ret = key - L'0';
done = true;
} else {
beep();
}
} else {
// Function or control key
switch (key) {
case KEY_ESC:
case KEY_CANCEL:
case KEY_EXIT:
case KEY_CTRL('C'):
case KEY_CTRL('G'):
case KEY_CTRL('\\'):
ret = ERR;
done = true;
break;
default:
beep();
}
}
}
curs_set(CURS_OFF);
return ret;
}
/***********************************************************************/
// ask_player_names: Ask for each of the players' names
void ask_player_names (void)
{
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int width;
if (number_players == 1) {
// Ask for the player's name
newtxwin(5, WIN_COLS - 4, 9, WCENTER, true, attr_normal_window);
left(curwin, 2, 2, attr_normal, 0, 0, 1, _("Please enter your name: "));
int x = getcurx(curwin);
int w = getmaxx(curwin) - x - 2;
player[0].name = NULL;
player[0].name_utf8 = NULL;
while (true) {
int ret = gettxstr(curwin, &player[0].name, NULL, false,
2, x, w, attr_input_field);
if (ret == OK && wcslen(player[0].name) != 0) {
break;
} else {
beep();
}
}
mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 1,
WIN_COLS - YESNO_COLS - 6, &width, 1,
/* TRANSLATORS: Note that you should replace "Y" and "N"
with the localised upper-case characters used in the
"input|Yes" and "input|No" strings. There are other
strings in this game, each with "[^{Y^}/^{N^}]", that
need the same replacement. */
_("Do you need any instructions? [^{Y^}/^{N^}] "));
newtxwin(5, width + YESNO_COLS + 4, 6, WCENTER, true,
attr_normal_window);
leftch(curwin, 2, 2, chbuf, 1, &width);
if (answer_yesno(curwin)) {
show_help();
}
} else {
// Ask for all of the player names
bool entered[MAX_PLAYERS];
bool done, modified;
int cur, len, i;
newtxwin(number_players + 5, WIN_COLS - 4, 9, WCENTER,
true, attr_normal_window);
center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Enter Player Names "));
for (i = 0; i < number_players; i++) {
player[i].name = NULL;
player[i].name_utf8 = NULL;
entered[i] = false;
left(curwin, i + 3, 2, attr_normal, 0, 0, 1,
/* xgettext:c-format, range: 1..8 */
_("Player %d: "), i + 1);
}
int x = getcurx(curwin);
int w = getmaxx(curwin) - x - 2;
cur = 0;
done = false;
while (! done) {
int ret = gettxstr(curwin, &player[cur].name, &modified, true,
3 + cur, x, w, attr_input_field);
switch (ret) {
case OK:
// Make sure name is not an empty string
len = wcslen(player[cur].name);
entered[cur] = (len != 0);
if (len == 0) {
beep();
}
// Make sure name has not been entered already
for (i = 0; i < number_players; i++) {
if (i != cur && player[i].name != NULL
&& wcscmp(player[i].name, player[cur].name) == 0) {
entered[cur] = false;
beep();
break;
}
}
// Move to first name for which ENTER has not been pressed
done = true;
for (cur = 0; cur < number_players; cur++) {
if (! entered[cur]) {
done = false;
break;
}
}
break;
case ERR:
beep();
break;
case KEY_UP:
// Scroll up to previous name (with wrap-around)
if (modified) {
entered[cur] = false;
}
if (cur == 0) {
cur = number_players - 1;
} else {
cur--;
}
break;
case KEY_DOWN:
// Scroll down to next name (with wrap-around)
if (modified) {
entered[cur] = false;
}
if (cur == number_players - 1) {
cur = 0;
} else {
cur++;
}
break;
default:
beep();
}
}
mkchstr(chbuf, BUFSIZE, attr_normal, attr_keycode, 0, 1,
WIN_COLS - YESNO_COLS - 6, &width, 1,
_("Does any player need instructions? [^{Y^}/^{N^}] "));
newtxwin(5, width + YESNO_COLS + 4, 6, WCENTER, true,
attr_normal_window);
leftch(curwin, 2, 2, chbuf, 1, &width);
if (answer_yesno(curwin)) {
show_help();
}
}
deltxwin(); // "Need instructions?" window
deltxwin(); // "Enter player names" window
free(chbuf);
}
/***********************************************************************/
// end_game: Finish playing the current game
void end_game (void)
{
chtype *chbuf;
int lines, widthbuf[5];
if (abort_game) {
// init_game() was cancelled by user
return;
}
chbuf = xmalloc(BUFSIZE * sizeof(chtype));
txdlgbox(MAX_DLG_LINES, 50, 9, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight, 0, 0,
attr_error_waitforkey, _(" Game Over "),
ngettext("The game is over after one turn.",
"The game is over after %d turns.",
turn_number - 1), turn_number - 1);
for (int i = 0; i < number_players; i++) {
show_status(i);
}
if (number_players == 1) {
txdlgbox(MAX_DLG_LINES, 60, 8, WCENTER, attr_normal_window,
attr_title, attr_normal, attr_highlight, 0, attr_waitforkey,
_(" Total Value "),
/* xgettext:c-format */
_("Your total value was ^{%N^}."), total_value(0));
} else {
// Sort players on the basis of total value
for (int i = 0; i < number_players; i++) {
player[i].sort_value = total_value(i);
}
qsort(player, number_players, sizeof(player_info_t), cmp_player);
lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_highlight,
attr_blink, 5, WIN_COLS - 8, widthbuf, 5,
(player[0].sort_value == 0) ?
_("The winner is ^{%ls^}\n"
"who is ^[*** BANKRUPT ***^]") :
/* xgettext:c-format */
_("The winner is ^{%ls^}\n"
"with a value of ^{%N^}."),
player[0].name, player[0].sort_value);
newtxwin(number_players + lines + 8, WIN_COLS - 4, 3, WCENTER,
true, attr_normal_window);
center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Game Winner "));
centerch(curwin, 3, 0, chbuf, lines, widthbuf);
int w = getmaxx(curwin);
mvwhline(curwin, lines + 4, 2, ' ' | attr_subtitle, w - 4);
left(curwin, lines + 4, ORDINAL_COLS + 4, attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "Player" is used as a column title in a
table containing all player names. */
pgettext("subtitle", "Player"));
right(curwin, lines + 4, w - 4, attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "Total Value" refers to the total worth
(shares, cash and debt) of any given player. %ls is the
currency symbol of the current locale. */
pgettext("subtitle", "Total Value (%ls)"), currency_symbol);
for (int i = 0; i < number_players; i++) {
right(curwin, i + lines + 5, ORDINAL_COLS + 2, attr_normal, 0, 0,
1, gettext(ordinal[i + 1]));
left(curwin, i + lines + 5, ORDINAL_COLS + 4, attr_normal, 0, 0,
1, "%ls", player[i].name);
right(curwin, i + lines + 5, w - 2, attr_normal, 0, 0,
1, " %!N ", player[i].sort_value);
}
wait_for_key(curwin, getmaxy(curwin) - 2, attr_waitforkey);
deltxwin();
}
free(chbuf);
}
/***********************************************************************/
// show_map: Display the galaxy map on the screen
void show_map (bool closewin)
{
newtxwin(MAX_Y + 4, WIN_COLS, 1, WCENTER, true, attr_map_window);
// Draw various borders and highlights
mvwaddch(curwin, 2, 0, ACS_LTEE);
whline(curwin, ACS_HLINE, getmaxx(curwin) - 2);
mvwaddch(curwin, 2, getmaxx(curwin) - 1, ACS_RTEE);
mvwhline(curwin, 1, 2, ' ' | attr_mapwin_title, getmaxx(curwin) - 4);
// Display current player and turn number
left(curwin, 1, 4, attr_mapwin_title, attr_mapwin_highlight, 0, 1,
_("Player: ^{%ls^}"), player[current_player].name);
right(curwin, 1, getmaxx(curwin) - 2, attr_mapwin_title,
attr_mapwin_highlight, attr_mapwin_blink, 1,
(turn_number != max_turn) ? _(" Turn: ^{%d^} ") :
_(" ^[*** Last Turn ***^] "), turn_number);
// Display the actual map
for (int y = 0; y < MAX_Y; y++) {
wmove(curwin, y + 3, 2);
for (int x = 0; x < MAX_X; x++) {
chtype *mapstr = CHTYPE_MAP_VAL(galaxy_map[x][y]);
while (*mapstr != 0) {
waddch(curwin, *mapstr++);
}
}
}
if (closewin) {
// Wait for the user to press any key
wrefresh(curwin);
newtxwin(WIN_LINES - MAX_Y - 5, WIN_COLS, MAX_Y + 5, WCENTER,
true, attr_normal_window);
wait_for_key(curwin, 2, attr_waitforkey);
deltxwin(); // Wait for key window
deltxwin(); // Galaxy map window
txrefresh();
}
}
/***********************************************************************/
// show_status: Display the player's status
void show_status (int num)
{
double val;
int w, i, line;
assert(num >= 0 && num < number_players);
newtxwin(MAX_COMPANIES + 15, WIN_COLS, 1, WCENTER, true,
attr_normal_window);
center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Stock Portfolio "));
center(curwin, 2, 0, attr_normal, attr_highlight, 0, 1,
_("Player: ^{%ls^}"), player[num].name);
val = total_value(num);
if (val == 0.0) {
center(curwin, 11, 0, attr_normal, attr_highlight, attr_blink, 1,
/* TRANSLATORS: The current player is bankrupt (has no
shares or cash, ie, whose total value is zero) */
_("^[* * * B A N K R U P T * * *^]"));
} else {
w = getmaxx(curwin);
// Check to see if any companies are on the map
bool none = true;
for (i = 0; i < MAX_COMPANIES; i++) {
if (company[i].on_map) {
none = false;
break;
}
}
if (none) {
center(curwin, 8, 0, attr_normal, attr_highlight, 0, 1,
_("No companies on the map"));
} else {
mvwhline(curwin, 4, 2, ' ' | attr_subtitle, w - 4);
mvwhline(curwin, 5, 2, ' ' | attr_subtitle, w - 4);
left(curwin, 4, 4, attr_subtitle, 0, 0, 2,
/* TRANSLATORS: "Company" is a two-line column label in
a table containing a list of companies. */
pgettext("subtitle", " \nCompany"));
right(curwin, 4, w - 4, attr_subtitle, 0, 0, 2,
/* TRANSLATORS: "Ownership" is a two-line column label
in a table containing the current player's
percentage ownership in any given company. The
maximum column width is 10 characters (see
OWNERSHIP_COLS in src/intf.h). */
pgettext("subtitle", "Ownership\n(%%)"));
right(curwin, 4, w - 6 - OWNERSHIP_COLS, attr_subtitle, 0, 0, 2,
/* TRANSLATORS: "Holdings" is a two-line column label
in a table containing the number of shares the
current player owns in any given company. The
maximum column width is 10 characters (see
STOCK_OWNED_COLS in src/intf.h). */
pgettext("subtitle", "Holdings\n(shares)"));
right(curwin, 4, w - 8 - OWNERSHIP_COLS - STOCK_OWNED_COLS,
attr_subtitle, 0, 0, 2,
/* TRANSLATORS: "Return" is a two-line column label in
a table containing the share return as a percentage
in any given company. The maximum column width is
10 characters (see SHARE_RETURN_COLS in src/intf.h). */
pgettext("subtitle", "Return\n(%%)"));
right(curwin, 4, w - 10 - OWNERSHIP_COLS - STOCK_OWNED_COLS
- SHARE_RETURN_COLS, attr_subtitle, 0, 0, 2,
/* TRANSLATORS: "Price per share" is a two-line column
label in a table containing the price per share in
any given company. %ls is the currency symbol in
the current locale. The maximum column width is 12
characters INCLUDING the currency symbol (see
SHARE_PRICE_COLS in src/intf.h). */
pgettext("subtitle", "Price per\nshare (%ls)"),
currency_symbol);
for (line = 6, i = 0; i < MAX_COMPANIES; i++) {
if (company[i].on_map) {
left(curwin, line, 4, attr_normal, 0, 0, 1, "%ls",
company[i].name);
right(curwin, line, w - 2, attr_normal, 0, 0, 1, "%.2f ",
(company[i].stock_issued == 0) ? 0.0 :
((double) player[num].stock_owned[i] * 100.0)
/ company[i].stock_issued);
right(curwin, line, w - 4 - OWNERSHIP_COLS, attr_normal,
0, 0, 1, "%'ld ", player[num].stock_owned[i]);
right(curwin, line, w - 6 - OWNERSHIP_COLS
- STOCK_OWNED_COLS, attr_normal, 0, 0, 1, "%.2f ",
company[i].share_return * 100.0);
right(curwin, line, w - 8 - OWNERSHIP_COLS
- STOCK_OWNED_COLS - SHARE_RETURN_COLS, attr_normal,
0, 0, 1, " %!N ", company[i].share_price);
line++;
}
}
}
line = MAX_COMPANIES + 7;
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int width, x;
mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, w / 2, &width, 1,
/* TRANSLATORS: The "Total value", "Current cash",
"Current debt" and "Interest rate" labels MUST all be
the same length (ie, right-padded with spaces as
needed) and must have at least one trailing space so
that the display routines work correctly. The maximum
length of each label is 36 characters.
Note that some of these labels are used for both the
Player Status window and the Trading Bank window. */
pgettext("label", "Total value: "));
x = (w + width - (TOTAL_VALUE_COLS + 2)) / 2;
right(curwin, line, x, attr_normal, attr_highlight, 0, 1,
pgettext("label", "Current cash: "));
right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal,
attr_highlight, 0, 1, " ^{%N^} ", player[num].cash);
line++;
if (player[num].debt != 0.0) {
right(curwin, line, x, attr_normal, attr_highlight, 0, 1,
pgettext("label", "Current debt: "));
right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal,
attr_highlight, 0, 1, " ^{%N^} ", player[num].debt);
line++;
right(curwin, line, x, attr_normal, attr_highlight, 0, 1,
pgettext("label", "Interest rate: "));
right(curwin, line, x + TOTAL_VALUE_COLS + 2, attr_normal,
attr_highlight, 0, 1, " ^{%.2f%%^} ", interest_rate * 100.0);
line++;
}
rightch(curwin, line + 1, x, chbuf, 1, &width);
whline(curwin, ' ' | attr_title, TOTAL_VALUE_COLS + 2);
right(curwin, line + 1, x + TOTAL_VALUE_COLS + 2, attr_title, 0, 0, 1,
" %N ", val);
free(chbuf);
}
wait_for_key(curwin, getmaxy(curwin) - 2, attr_waitforkey);
deltxwin();
txrefresh();
}
/***********************************************************************/
// total_value: Calculate a player's total financial worth
double total_value (int num)
{
double val;
assert(num >= 0 && num < number_players);
val = player[num].cash - player[num].debt;
for (int i = 0; i < MAX_COMPANIES; i++) {
if (company[i].on_map) {
val += player[num].stock_owned[i] * company[i].share_price;
}
}
return val;
}
/***********************************************************************/
// cmp_player: Compare two player[] elements for sorting
int cmp_player (const void *a, const void *b)
{
/* This implementation assumes that each player[] element has already
had its sort_value set to the result of total_value(). Note also
that the function result is reversed from the normal order, so
that players are sorted into descending order */
const player_info_t *aa = (const player_info_t *) a;
const player_info_t *bb = (const player_info_t *) b;
if (aa->sort_value > bb->sort_value) {
return -1;
} else if (aa->sort_value < bb->sort_value) {
return 1;
} else {
return 0;
}
}
/***********************************************************************/
// End of file