/************************************************************************
*                                                                       *
*             Star Traders: A Game of Interstellar Trading              *
*                Copyright (C) 1990-2026, John Zaitseff                 *
*                                                                       *
************************************************************************/

/*
  Author: John Zaitseff <J.Zaitseff@zap.org.au>
  SPDX-License-Identifier: GPL-3.0-or-later
  $Id: 15e3c19ee5f2f35f48826ba5956e6ef9a985fea1 $

  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
