1
0
Fork 0
trader/src/move.c

1297 lines
37 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, move.c, contains the implementation of functions that make
and process a game move 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 https://www.gnu.org/licenses/.
*/
#include "trader.h"
/************************************************************************
* Module-specific macros *
************************************************************************/
// Calculate positions near (x,y), taking the edge of the galaxy into account
#define GALAXY_MAP_LEFT(x, y) (((x) <= 0) ? MAP_EMPTY : galaxy_map[(x) - 1][(y)])
#define GALAXY_MAP_RIGHT(x, y) (((x) >= (MAX_X - 1)) ? MAP_EMPTY : galaxy_map[(x) + 1][(y)])
#define GALAXY_MAP_UP(x, y) (((y) <= 0) ? MAP_EMPTY : galaxy_map[(x)][(y) - 1])
#define GALAXY_MAP_DOWN(x, y) (((y) >= (MAX_Y - 1)) ? MAP_EMPTY : galaxy_map[(x)][(y) + 1])
#define assign_vals(x, y, left, right, up, down) \
do { \
(left) = GALAXY_MAP_LEFT((x), (y)); \
(right) = GALAXY_MAP_RIGHT((x), (y)); \
(up) = GALAXY_MAP_UP((x), (y)); \
(down) = GALAXY_MAP_DOWN((x), (y)); \
} while (0)
/************************************************************************
* Module-specific function prototypes *
************************************************************************/
/*
Function: bankrupt_player - Make the current player bankrupt
Parameters: forced - True if bankruptcy is forced by Bank
Returns: (nothing)
This function makes the current player bankrupt, whether by their own
choice or as a result of action by the Interstellar Trading Bank. All
shares are returned to the appropriate companies, any debt is cancelled
and any cash is confiscated. On exit, quit_selected is true if all
players are bankrupt.
*/
static void bankrupt_player (bool forced);
/*
Function: try_start_new_company - See if a new company can be started
Parameters: x, y - Coordinates of position on map
Returns: (nothing)
This function attempts to establish a new company if the position (x,y)
is in a suitable location and if no more than MAX_COMPANIES are already
present.
*/
static void try_start_new_company (int x, int y);
/*
Function: merge_companies - Merge two companies together
Parameters: a, b - Companies to merge
Returns: (nothing)
This function merges two companies on the galaxy map; the one with the
highest value takes over. The parameters a and b are actual values
from the galaxy map.
*/
static void merge_companies (map_val_t a, map_val_t b);
/*
Function: include_outpost - Include any outposts into the company
Parameters: num - Company on which to operate
x, y - Coordinates of position on map
Returns: (nothing)
This function includes the outpost at (x,y) into company num,
increasing the share price by calling inc_share_price(). It also
checks surrounding locations for further outposts to include.
*/
static void include_outpost (int num, int x, int y);
/*
Function: inc_share_price - Increase the share price of a company
Parameters: num - Company on which to operate
inc - Base increment for the share price
Returns: (nothing)
This function increments the share price, maximum stock available and
the share return of company num, using inc as the basis for doing so.
*/
static void inc_share_price (int num, double inc);
/*
Function: adjust_values - Adjust various company-related values
Parameters: (none)
Returns: (nothing)
This function adjusts the cost of shares for companies on the galaxy
map, their return, the Bank interest rate, etc.
*/
static void adjust_values (void);
/*
Function: cmp_game_move - Compare two game_move[] elements for sorting
Parameters: a, b - Elements to compare
Returns: int - Comparison of a and b
This internal function compares two game_move[] elements (of type
move_rec_t) and returns -1 if a < b, 0 if a == b and 1 if a > b. It is
used for sorting game moves into ascending order.
*/
static int cmp_game_move (const void *a, const void *b);
/************************************************************************
* Game move function definitions *
************************************************************************/
// These functions are documented in the file "move.h"
/***********************************************************************/
// select_moves: Select NUMBER_MOVES random moves
void select_moves (void)
{
int count;
int x, y, i, j;
int tx, ty;
bool unique;
// How many empty spaces are there in the galaxy map?
count = 0;
for (x = 0; x < MAX_X; x++) {
for (y = 0; y < MAX_Y; y++) {
if (galaxy_map[x][y] == MAP_EMPTY) {
count++;
}
}
}
if (count < NUMBER_MOVES) {
quit_selected = true;
return;
}
// Generate unique random moves
for (i = 0; i < NUMBER_MOVES; i++) {
do {
do {
tx = randi(MAX_X);
ty = randi(MAX_Y);
} while (galaxy_map[tx][ty] != MAP_EMPTY);
unique = true;
for (j = i - 1; j >= 0; j--) {
if (tx == game_move[j].x && ty == game_move[j].y) {
unique = false;
break;
}
}
} while (! unique);
game_move[i].x = tx;
game_move[i].y = ty;
}
// Sort moves from left to right
qsort(game_move, NUMBER_MOVES, sizeof(move_rec_t), cmp_game_move);
quit_selected = false;
}
/***********************************************************************/
// get_move: Wait for the player to enter their move
selection_t get_move (void)
{
selection_t selection = SEL_NONE;
if (quit_selected || abort_game) {
return SEL_QUIT;
}
// Display map without closing window
show_map(false);
// Display current move choices on the galaxy map
for (int i = 0; i < NUMBER_MOVES; i++) {
chtype *movestr = CHTYPE_GAME_MOVE(i);
wmove(curwin, game_move[i].y + 3, game_move[i].x * 2 + 2);
while (*movestr != 0) {
waddch(curwin, *movestr++);
}
}
wrefresh(curwin);
// Show menu of choices for the player
newtxwin(5, WIN_COLS, 19, WCENTER, false, 0);
while (selection == SEL_NONE) {
chtype *promptbuf = xmalloc(BUFSIZE * sizeof(chtype));
int promptend, promptwidth;
wbkgdset(curwin, attr_normal_window);
werase(curwin);
box(curwin, 0, 0);
left(curwin, 2, 2, attr_normal, attr_keycode, 0, 1,
/* TRANSLATORS: Each label may be up to 37 characters wide
(for <1> and <2>) or 38 characters wide (for <3> and
<CTRL><C>). The sequences "^{" and "^}" change the
character rendition (attributes) and take up no space. */
_("^{<1>^} Display stock portfolio"));
left(curwin, 3, 2, attr_normal, attr_keycode, 0, 1,
_("^{<2>^} Declare bankruptcy"));
left(curwin, 2, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1,
_("^{<3>^} Save and end the game"));
left(curwin, 3, getmaxx(curwin) / 2, attr_normal, attr_keycode, 0, 1,
_("^{<CTRL><C>^} Quit the game"));
mkchstr(promptbuf, BUFSIZE, attr_normal, attr_keycode, attr_choice,
1, getmaxx(curwin) - 4, &promptwidth, 1,
/* TRANSLATORS: The maximum column width is either 38
characters (including the trailing space), or 76
characters minus the length of the longest of the four
strings above this one. The sequences "^{", "^}",
"^[" and "^]" do not take up any room. "%lc" takes up
either one or two columns, depending on the appropriate
"output|GameMoves" string in the current PO file. */
_("Select move [^[%lc^]-^[%lc^]/^{1^}-^{3^}/^{<CTRL><C>^}]: "),
(wint_t) PRINTABLE_GAME_MOVE(0),
(wint_t) PRINTABLE_GAME_MOVE(NUMBER_MOVES - 1));
if (promptwidth > getmaxx(curwin) / 2 - 2) {
promptend = promptwidth + 2;
leftch(curwin, 1, 2, promptbuf, 1, &promptwidth);
} else {
promptend = getmaxx(curwin) / 2;
rightch(curwin, 1, promptend, promptbuf, 1, &promptwidth);
}
curs_set(CURS_ON);
wrefresh(curwin);
free(promptbuf);
// Get the actual selection made by the player
while (selection == SEL_NONE) {
wint_t key;
if (gettxchar(curwin, &key) == OK) {
// Ordinary wide character
int i;
bool found;
if (iswupper(*keycode_game_move)) {
key = towupper(key);
} else if (iswlower(*keycode_game_move)) {
key = towlower(key);
}
for (i = 0, found = false; keycode_game_move[i] != L'\0'; i++) {
if (keycode_game_move[i] == (wchar_t) key) {
found = true;
selection = (selection_t) i;
curs_set(CURS_OFF);
left(curwin, 1, promptend, attr_normal, attr_choice,
0, 1,
/* TRANSLATORS: A game usually consists of
DEFAULT_MAX_TURN (50) turns. On each
turn, the computer randomly selects
NUMBER_MOVES (20) moves (positions on the
map); each player selects just one move
per turn. "Move" refers to the player's
choice. */
_("Move ^{%lc^}"),
(wint_t) PRINTABLE_GAME_MOVE(i));
break;
}
}
if (! found) {
switch (key) {
case L'1':
curs_set(CURS_OFF);
show_status(current_player);
curs_set(CURS_ON);
break;
case L'2':
selection = SEL_BANKRUPT;
curs_set(CURS_OFF);
left(curwin, 1, promptend, attr_normal,
attr_normal | A_BOLD, 0, 1,
_("^{<2>^} (Declare bankruptcy)"));
break;
case L'3':
selection = SEL_SAVE;
curs_set(CURS_OFF);
left(curwin, 1, promptend, attr_normal,
attr_normal | A_BOLD, 0, 1,
_("^{<3>^} (Save and end the game)"));
break;
default:
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('\\'):
selection = SEL_QUIT;
curs_set(CURS_OFF);
left(curwin, 1, promptend, attr_normal,
attr_normal | A_BOLD, 0, 1,
_("^{<CTRL><C>^} (Quit the game)"));
break;
default:
beep();
}
}
}
// Clear the menu choices (but not the prompt!)
mvwhline(curwin, 2, 2, ' ' | attr_normal, getmaxx(curwin) - 4);
mvwhline(curwin, 3, 2, ' ' | attr_normal, getmaxx(curwin) - 4);
// Ask the player to confirm their choice
right(curwin, 2, promptend, attr_normal, attr_keycode, 0, 1,
_("Are you sure? [^{Y^}/^{N^}] "));
wrefresh(curwin);
if (! answer_yesno(curwin)) {
selection = SEL_NONE;
}
// Save the game if required
if (selection == SEL_SAVE) {
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int width;
bool saved = false;
if (game_loaded) {
// Save the game to the same game number
mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1, WIN_COLS
- 7, &width, 1, _("Saving game %d... "), game_num);
newtxwin(5, width + 5, 7, WCENTER, true, attr_status_window);
centerch(curwin, 2, 0, chbuf, 1, &width);
wrefresh(curwin);
saved = save_game(game_num);
deltxwin();
txrefresh();
}
if (! saved) {
// Ask which game to save
bool done;
int widthbuf[2];
int lines, maxwidth;
int choice;
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, 8, WCENTER, true,
attr_normal_window);
leftch(curwin, 2, 2, chbuf, lines, widthbuf);
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);
choice = 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('\\'):
choice = ERR;
done = true;
break;
default:
beep();
}
}
}
curs_set(CURS_OFF);
if (choice != ERR) {
// Try to save the game, if possible
game_num = choice;
mkchstr(chbuf, BUFSIZE, attr_status_window, 0, 0, 1,
WIN_COLS - 7, &width, 1,
_("Saving game %d... "), game_num);
newtxwin(5, width + 5, 7, WCENTER, true,
attr_status_window);
centerch(curwin, 2, 0, chbuf, 1, &width);
wrefresh(curwin);
saved = save_game(game_num);
deltxwin();
txrefresh();
}
deltxwin(); // "Enter game number" window
txrefresh();
}
if (saved) {
selection = SEL_QUIT;
} else {
// Make the next try at saving ask the player for a game number
game_loaded = false;
game_num = 0;
selection = SEL_NONE;
}
free(chbuf);
}
}
return selection;
}
/***********************************************************************/
// process_move: Process the move selected by the player
void process_move (selection_t selection)
{
if (selection == SEL_QUIT) {
// The players want to end the game
quit_selected = true;
}
if (quit_selected || abort_game) {
deltxwin(); // "Select move" window
deltxwin(); // Galaxy map window
txrefresh();
return;
}
if (selection == SEL_BANKRUPT) {
// A player wants to give up: make them bankrupt
bankrupt_player(false);
} else {
// Process a selection from game_move[]
assert(selection >= SEL_MOVE_FIRST && selection <= SEL_MOVE_LAST);
map_val_t left, right, up, down;
map_val_t nearby, cur;
int x = game_move[selection].x;
int y = game_move[selection].y;
assign_vals(x, y, left, right, up, down);
if ( left == MAP_EMPTY && right == MAP_EMPTY
&& up == MAP_EMPTY && down == MAP_EMPTY) {
// The position is out in the middle of nowhere...
galaxy_map[x][y] = MAP_OUTPOST;
} else if ( ! IS_MAP_COMPANY(left) && ! IS_MAP_COMPANY(right)
&& ! IS_MAP_COMPANY(up) && ! IS_MAP_COMPANY(down)) {
// See if a company can be established
try_start_new_company(x, y);
} else {
// See if two (or more!) companies can be merged
if (IS_MAP_COMPANY(left) && IS_MAP_COMPANY(right)
&& left != right) {
galaxy_map[x][y] = left;
merge_companies(left, right);
assign_vals(x, y, left, right, up, down);
}
if (IS_MAP_COMPANY(left) && IS_MAP_COMPANY(up)
&& left != up) {
galaxy_map[x][y] = left;
merge_companies(left, up);
assign_vals(x, y, left, right, up, down);
}
if (IS_MAP_COMPANY(left) && IS_MAP_COMPANY(down)
&& left != down) {
galaxy_map[x][y] = left;
merge_companies(left, down);
assign_vals(x, y, left, right, up, down);
}
if (IS_MAP_COMPANY(right) && IS_MAP_COMPANY(up)
&& right != up) {
galaxy_map[x][y] = right;
merge_companies(right, up);
assign_vals(x, y, left, right, up, down);
}
if (IS_MAP_COMPANY(right) && IS_MAP_COMPANY(down)
&& right != down) {
galaxy_map[x][y] = right;
merge_companies(right, down);
assign_vals(x, y, left, right, up, down);
}
if (IS_MAP_COMPANY(up) && IS_MAP_COMPANY(down)
&& up != down) {
galaxy_map[x][y] = up;
merge_companies(up, down);
assign_vals(x, y, left, right, up, down);
}
}
// See if an existing company can be expanded
nearby = (IS_MAP_COMPANY(left) ? left :
(IS_MAP_COMPANY(right) ? right :
(IS_MAP_COMPANY(up) ? up :
(IS_MAP_COMPANY(down) ? down :
MAP_EMPTY))));
if (nearby != MAP_EMPTY) {
galaxy_map[x][y] = nearby;
inc_share_price(MAP_TO_COMPANY(nearby), SHARE_PRICE_INC);
}
/* If a company expanded (or merged or formed), see if share
price should be incremented */
cur = galaxy_map[x][y];
if (IS_MAP_COMPANY(cur)) {
// Is a star nearby?
if (left == MAP_STAR) {
inc_share_price(MAP_TO_COMPANY(cur), SHARE_PRICE_INC_STAR);
}
if (right == MAP_STAR) {
inc_share_price(MAP_TO_COMPANY(cur), SHARE_PRICE_INC_STAR);
}
if (up == MAP_STAR) {
inc_share_price(MAP_TO_COMPANY(cur), SHARE_PRICE_INC_STAR);
}
if (down == MAP_STAR) {
inc_share_price(MAP_TO_COMPANY(cur), SHARE_PRICE_INC_STAR);
}
// Is an outpost nearby?
if (left == MAP_OUTPOST) {
include_outpost(MAP_TO_COMPANY(cur), x - 1, y);
}
if (right == MAP_OUTPOST) {
include_outpost(MAP_TO_COMPANY(cur), x + 1, y);
}
if (up == MAP_OUTPOST) {
include_outpost(MAP_TO_COMPANY(cur), x, y - 1);
}
if (down == MAP_OUTPOST) {
include_outpost(MAP_TO_COMPANY(cur), x, y + 1);
}
}
}
if (! quit_selected) {
adjust_values();
}
deltxwin(); // "Select move" window
deltxwin(); // Galaxy map window
txrefresh();
}
/***********************************************************************/
// next_player: Get the next player
void next_player (void)
{
int i;
bool all_out;
all_out = true;
for (i = 0; i < number_players; i++) {
if (player[i].in_game) {
all_out = false;
break;
}
}
if (all_out) {
quit_selected = true;
} else {
do {
current_player++;
if (current_player == number_players) {
current_player = 0;
}
if (current_player == first_player) {
turn_number++;
}
} while (! player[current_player].in_game);
}
}
/************************************************************************
* Module-specific function definitions *
************************************************************************/
// These functions are documented at the start of this file
/***********************************************************************/
// bankrupt_player: Make the current player bankrupt
void bankrupt_player (bool forced)
{
if (forced) {
txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight, 0, 0,
attr_error_waitforkey, _(" Bankruptcy Court "),
/* TRANSLATORS: %ls is the player's name. */
_("%ls has been declared bankrupt "
"by the Interstellar Trading Bank."),
player[current_player].name);
} else {
txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight, 0, 0,
attr_error_waitforkey, _(" Bankruptcy Court "),
/* TRANSLATORS: %ls is the player's name. */
_("%ls has declared bankruptcy."),
player[current_player].name);
}
txrefresh();
// Confiscate all assets belonging to player
player[current_player].in_game = false;
for (int i = 0; i < MAX_COMPANIES; i++) {
company[i].stock_issued -= player[current_player].stock_owned[i];
player[current_player].stock_owned[i] = 0;
}
player[current_player].cash = 0.0;
player[current_player].debt = 0.0;
// Is anyone still left in the game?
bool all_out = true;
for (int i = 0; i < number_players; i++) {
if (player[i].in_game) {
all_out = false;
break;
}
}
if (all_out) {
quit_selected = true;
}
}
/***********************************************************************/
// try_start_new_company: See it a new company can be started
void try_start_new_company (int x, int y)
{
bool all_on_map;
map_val_t left, right, up, down;
int i, j;
assert(x >= 0 && x < MAX_X);
assert(y >= 0 && y < MAX_Y);
assign_vals(x, y, left, right, up, down);
if ( left != MAP_OUTPOST && left != MAP_STAR
&& right != MAP_OUTPOST && right != MAP_STAR
&& up != MAP_OUTPOST && up != MAP_STAR
&& down != MAP_OUTPOST && down != MAP_STAR) {
return;
}
all_on_map = true;
for (i = 0; i < MAX_COMPANIES; i++) {
if (! company[i].on_map) {
all_on_map = false;
break;
}
}
if (all_on_map) {
// The galaxy cannot support any more companies
galaxy_map[x][y] = MAP_OUTPOST;
} else {
// Create the new company
txdlgbox(MAX_DLG_LINES, 50, 7, WCENTER, attr_normal_window,
attr_title, attr_normal, attr_highlight, 0, attr_waitforkey,
_(" New Company "),
_("A new company has been formed!\nIts name is ^{%ls^}."),
company[i].name);
txrefresh();
galaxy_map[x][y] = (map_val_t) COMPANY_TO_MAP(i);
company[i].share_price = INITIAL_SHARE_PRICE;
company[i].share_return = INITIAL_RETURN;
company[i].stock_issued = INITIAL_STOCK_ISSUED;
company[i].max_stock = INITIAL_MAX_STOCK;
company[i].on_map = true;
for (j = 0; j < number_players; j++) {
player[j].stock_owned[i] = 0;
}
player[current_player].stock_owned[i] = INITIAL_STOCK_ISSUED;
}
}
/***********************************************************************/
// merge_companies: Merge two companies together
void merge_companies (map_val_t a, map_val_t b)
{
int aa = MAP_TO_COMPANY(a);
int bb = MAP_TO_COMPANY(b);
assert(aa >= 0 && aa < MAX_COMPANIES);
assert(bb >= 0 && bb < MAX_COMPANIES);
double val_aa = company[aa].share_price * company[aa].stock_issued *
(1.0 + company[aa].share_return);
double val_bb = company[bb].share_price * company[bb].stock_issued *
(1.0 + company[bb].share_return);
double bonus;
long int old_stock, new_stock, total_new;
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
int lines, width, widthbuf[4];
chtype *chbuf_aa, *chbuf_bb;
int width_aa, width_bb;
int x, y, w, i, ln;
if (val_aa < val_bb) {
// Make sure aa is the dominant company
map_val_t t;
int tt;
t = a; a = b; b = t;
tt = aa; aa = bb; bb = tt;
}
// Display information about the merger
lines = mkchstr(chbuf, BUFSIZE, attr_normal, attr_highlight, 0, 4,
WIN_COLS - 8, widthbuf, 4,
_("^{%ls^} has just merged into ^{%ls^}.\n"
"Please note the following transactions:\n"),
company[bb].name, company[aa].name);
newtxwin(number_players + lines + 10, WIN_COLS - 4, lines + 6
- number_players, WCENTER, true, attr_normal_window);
center(curwin, 1, 0, attr_title, 0, 0, 1, _(" Company Merger "));
centerch(curwin, 3, 0, chbuf, lines, widthbuf);
mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, getmaxx(curwin) / 2,
&width_aa, 1, "%ls", company[aa].name);
chbuf_aa = xchstrdup(chbuf);
mkchstr(chbuf, BUFSIZE, attr_highlight, 0, 0, 1, getmaxx(curwin) / 2,
&width_bb, 1, "%ls", company[bb].name);
chbuf_bb = xchstrdup(chbuf);
mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, getmaxx(curwin) / 2,
&width, 1,
/* TRANSLATORS: "Old stock" refers to the company that has
just ceased existence due to a merger.
Note that the "Old stock" and "New stock" labels MUST be
the same length and must contain a trailing space for the
display routines to work correctly. The maximum length of
each label is 36 characters. */
pgettext("label", "Old stock: "));
w = getmaxx(curwin);
x = (w + width - MAX(width_aa, width_bb)) / 2;
rightch(curwin, lines + 3, x, chbuf, 1, &width);
leftch(curwin, lines + 3, x, chbuf_bb, 1, &width_bb);
right(curwin, lines + 4, x, attr_normal, 0, 0, 1,
/* TRANSLATORS: "New stock" refers to the company that has
absorbed the other due to a merger. */
pgettext("label", "New stock: "));
leftch(curwin, lines + 4, x, chbuf_aa, 1, &width_aa);
mvwhline(curwin, lines + 6, 2, ' ' | attr_subtitle, w - 4);
left(curwin, lines + 6, 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 + 6, w - 4, attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "Bonus" refers to the bonus cash amount paid to
each player after two companies merge. %ls is the currency
symbol in the current locale. The maximum column width is
12 characters INCLUDING the currency symbol (see
MERGE_BONUS_COLS in src/intf.h). */
pgettext("subtitle", "Bonus (%ls)"), currency_symbol);
right(curwin, lines + 6, w - 6 - MERGE_BONUS_COLS, attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "Total" refers to the total number of shares in
the new company after a merger. The maximum column width is
8 characters (see MERGE_TOTAL_STOCK_COLS in src/intf.h). */
pgettext("subtitle", "Total"));
right(curwin, lines + 6, w - 8 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS,
attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "New" refers to how many (new) shares each
player receives in the surviving company after a merger.
The maximum column width is 8 characters (see
MERGE_NEW_STOCK_COLS in src/intf.h). */
pgettext("subtitle", "New"));
right(curwin, lines + 6, w - 10 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS
- MERGE_NEW_STOCK_COLS, attr_subtitle, 0, 0, 1,
/* TRANSLATORS: "Old" refers to how many shares each player had
in the company ceasing existence. The maximum column width
is 8 characters (see MERGE_OLD_STOCK_COLS in src/intf.h). */
pgettext("subtitle", "Old"));
total_new = 0;
for (ln = lines + 7, i = 0; i < number_players; i++) {
if (player[i].in_game) {
// Calculate new stock and any bonus
old_stock = player[i].stock_owned[bb];
new_stock = (double) old_stock * MERGE_STOCK_RATIO;
total_new += new_stock;
bonus = (company[bb].stock_issued == 0) ? 0.0 : MERGE_BONUS_RATE
* ((double) player[i].stock_owned[bb]
/ company[bb].stock_issued) * company[bb].share_price;
player[i].stock_owned[aa] += new_stock;
player[i].stock_owned[bb] = 0;
player[i].cash += bonus;
mkchstr(chbuf, BUFSIZE, attr_normal, 0, 0, 1, w - 12
- MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS
- MERGE_NEW_STOCK_COLS - MERGE_OLD_STOCK_COLS,
&width, 1, "%ls", player[i].name);
leftch(curwin, ln, 4, chbuf, 1, &width);
right(curwin, ln, w - 4, attr_normal, 0, 0, 1, "%!N", bonus);
right(curwin, ln, w - 6 - MERGE_BONUS_COLS, attr_normal, 0, 0, 1,
"%'ld", player[i].stock_owned[aa]);
right(curwin, ln, w - 8 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS,
attr_normal, 0, 0, 1, "%'ld", new_stock);
right(curwin, ln, w - 10 - MERGE_BONUS_COLS - MERGE_TOTAL_STOCK_COLS
- MERGE_NEW_STOCK_COLS, attr_normal, 0, 0, 1, "%'ld",
old_stock);
ln++;
}
}
// Adjust the company records appropriately
company[aa].stock_issued += total_new;
company[aa].max_stock += total_new;
company[aa].share_price += company[bb].share_price
* (randf() * (MERGE_PRICE_ADJUST_MAX - MERGE_PRICE_ADJUST_MIN)
+ MERGE_PRICE_ADJUST_MIN);
company[bb].stock_issued = 0;
company[bb].max_stock = 0;
company[bb].on_map = false;
// Adjust the galaxy map appropriately
for (x = 0; x < MAX_X; x++) {
for (y = 0; y < MAX_Y; y++) {
if (galaxy_map[x][y] == b) {
galaxy_map[x][y] = a;
}
}
}
wait_for_key(curwin, getmaxy(curwin) - 2, attr_waitforkey);
deltxwin(); // "Company merger" window
txrefresh();
free(chbuf_bb);
free(chbuf_aa);
free(chbuf);
}
/***********************************************************************/
// include_outpost: Include any outposts into the company
void include_outpost (int num, int x, int y)
{
map_val_t left, right, up, down;
assert(num >= 0 && num < MAX_COMPANIES);
assert(x >= 0 && x < MAX_X);
assert(y >= 0 && y < MAX_Y);
assign_vals(x, y, left, right, up, down);
galaxy_map[x][y] = (map_val_t) COMPANY_TO_MAP(num);
inc_share_price(num, SHARE_PRICE_INC_OUTPOST);
// Outposts next to stars are more valuable: increment again
if (left == MAP_STAR) {
inc_share_price(num, SHARE_PRICE_INC_OUTSTAR);
}
if (right == MAP_STAR) {
inc_share_price(num, SHARE_PRICE_INC_OUTSTAR);
}
if (up == MAP_STAR) {
inc_share_price(num, SHARE_PRICE_INC_OUTSTAR);
}
if (down == MAP_STAR) {
inc_share_price(num, SHARE_PRICE_INC_OUTSTAR);
}
// Include any nearby outposts
if (left == MAP_OUTPOST) {
include_outpost(num, x - 1, y);
}
if (right == MAP_OUTPOST) {
include_outpost(num, x + 1, y);
}
if (up == MAP_OUTPOST) {
include_outpost(num, x, y - 1);
}
if (down == MAP_OUTPOST) {
include_outpost(num, x, y + 1);
}
}
/***********************************************************************/
// inc_share_price: Increase the share price of a company
void inc_share_price (int num, double inc)
{
assert(num >= 0 && num < MAX_COMPANIES);
company[num].share_price += inc * (randf()
* (PRICE_INC_ADJUST_MAX - PRICE_INC_ADJUST_MIN) + PRICE_INC_ADJUST_MIN);
company[num].max_stock += inc * (randf()
* (MAX_STOCK_RATIO_MAX - MAX_STOCK_RATIO_MIN) + MAX_STOCK_RATIO_MIN);
if (randf() < CHANGE_RETURN_GROWING) {
double change = randf() * GROWING_MAX_CHANGE;
if (randf() < DEC_RETURN_GROWING) {
change = -change;
}
company[num].share_return += change;
if ( company[num].share_return > MAX_COMPANY_RETURN
|| company[num].share_return < MIN_COMPANY_RETURN) {
company[num].share_return -= 2.0 * change;
}
}
}
/***********************************************************************/
// adjust_values: Adjust various company-related values
void adjust_values (void)
{
int which;
// Declare a company bankrupt!
if (randf() > (1.0 - COMPANY_BANKRUPTCY)) {
which = randi(MAX_COMPANIES);
if (company[which].on_map && company[which].share_return <= 0.0) {
if (randf() < ALL_ASSETS_TAKEN) {
txdlgbox(MAX_DLG_LINES, 60, 6, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight,
attr_error_normal, 0, attr_error_waitforkey,
_(" Bankruptcy Court "),
/* TRANSLATORS: %ls represents the company name. */
_("%ls has been declared bankrupt "
"by the Interstellar Trading Bank.\n\n"
"^{All assets have been taken "
"to repay outstanding loans.^}"),
company[which].name);
txrefresh();
} else {
double rate = randf();
chtype *chbuf = xmalloc(BUFSIZE * sizeof(chtype));
chtype *chbuf_amt;
int w, x, lines, width, width_amt, widthbuf[6];
for (int i = 0; i < number_players; i++) {
if (player[i].in_game) {
player[i].cash += player[i].stock_owned[which]
* company[which].share_price * rate;
}
}
lines = mkchstr(chbuf, BUFSIZE, attr_error_highlight,
attr_error_normal, 0, 6, 60 - 4, widthbuf, 6,
/* TRANSLATORS: %ls represents the company name. */
_("%ls has been declared bankrupt by the "
"Interstellar Trading Bank.\n\n"
"^{The Bank has agreed to pay stock holders ^}"
"%.2f%%^{ of the share value on each share "
"owned.^}"),
company[which].name, rate * 100.0);
newtxwin(9 + lines, 60, 4, WCENTER, true, attr_error_window);
w = getmaxx(curwin);
center(curwin, 1, 0, attr_error_title, 0, 0, 1,
_(" Bankruptcy Court "));
centerch(curwin, 3, 0, chbuf, lines, widthbuf);
mkchstr(chbuf, BUFSIZE, attr_error_highlight, 0, 0, 1, w / 2,
&width_amt, 1, "%N", company[which].share_price);
chbuf_amt = xchstrdup(chbuf);
mkchstr(chbuf, BUFSIZE, attr_error_normal, 0, 0, 1, w / 2,
&width, 1,
/* TRANSLATORS: The label "Amount paid per share"
refers to payment made by the Interstellar
Trading Bank to each player upon company
bankruptcy. This label MUST be the same
length as "Old share value" and MUST have at
least one trailing space for the display
routines to work correctly. The maximum
length is 28 characters. */
pgettext("label", "Amount paid per share: "));
x = (w + width - width_amt) / 2;
right(curwin, lines + 4, x, attr_error_normal, 0, 0, 1,
/* TRANSLATORS: "Old share value" refers to the
share price of a company before it was forced
into bankruptcy by the Bank. This label must be
the same width as "Amount paid per share". */
pgettext("label", "Old share value: "));
leftch(curwin, lines + 4, x, chbuf_amt, 1, &width_amt);
rightch(curwin, lines + 5, x, chbuf, 1, &width);
left(curwin, lines + 5, x, attr_error_highlight, 0, 0, 1,
"%N", company[which].share_price * rate);
wait_for_key(curwin, getmaxy(curwin) - 2, attr_error_waitforkey);
deltxwin();
txrefresh();
free(chbuf_amt);
free(chbuf);
}
for (int i = 0; i < number_players; i++) {
player[i].stock_owned[which] = 0;
}
company[which].share_price = 0.0;
company[which].share_return = 0.0;
company[which].stock_issued = 0;
company[which].max_stock = 0;
company[which].on_map = false;
for (int x = 0; x < MAX_X; x++) {
for (int y = 0; y < MAX_Y; y++) {
if (galaxy_map[x][y] == COMPANY_TO_MAP((unsigned int) which)) {
galaxy_map[x][y] = MAP_EMPTY;
}
}
}
}
}
// Increase or decrease company return
if (randf() < CHANGE_COMPANY_RETURN) {
which = randi(MAX_COMPANIES);
if (company[which].on_map) {
double change = randf() * RETURN_MAX_CHANGE;
if (randf() < DEC_COMPANY_RETURN) {
change = -change;
}
company[which].share_return += change;
if ( company[which].share_return > MAX_COMPANY_RETURN
|| company[which].share_return < MIN_COMPANY_RETURN) {
company[which].share_return -= 2.0 * change;
}
}
}
// Increase or decrease share price
if (randf() < CHANGE_SHARE_PRICE) {
which = randi(MAX_COMPANIES);
if (company[which].on_map) {
double change = randf() * company[which].share_price
* PRICE_CHANGE_RATE;
if (randf() < DEC_SHARE_PRICE) {
change = -change;
}
company[which].share_price += change;
}
}
// Give the current player the companies' dividends
for (int i = 0; i < MAX_COMPANIES; i++) {
if (company[i].on_map && company[i].stock_issued != 0) {
player[current_player].cash +=
player[current_player].stock_owned[i]
* company[i].share_price * company[i].share_return
+ ((double) player[current_player].stock_owned[i]
/ company[i].stock_issued) * company[i].share_price
* OWNERSHIP_BONUS;
}
}
// Has the player lost money due to negative share returns?
if (player[current_player].cash < 0.0) {
double borrowed = -player[current_player].cash;
txdlgbox(MAX_DLG_LINES, 60, 7, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight, 0, 0,
attr_error_waitforkey, _(" Interstellar Trading Bank "),
/* xgettext:c-format */
_("You were forced to borrow %N\n"
"to cover losses from company shares."),
borrowed);
txrefresh();
player[current_player].cash = 0.0;
player[current_player].debt += borrowed;
}
// Change the interest rate
if (randf() < CHANGE_INTEREST_RATE) {
double change = randf() * INTEREST_MAX_CHANGE;
if (randf() < DEC_INTEREST_RATE) {
change = -change;
}
interest_rate += change;
if ( interest_rate > MAX_INTEREST_RATE
|| interest_rate < MIN_INTEREST_RATE) {
interest_rate -= 2.0 * change;
}
}
// Calculate current player's debt
player[current_player].debt *= interest_rate + 1.0;
// Check if a player's debt is too large
if (total_value(current_player) <= -MAX_OVERDRAFT) {
double impounded = MIN(player[current_player].cash,
player[current_player].debt);
txdlgbox(MAX_DLG_LINES, 60, 7, WCENTER, attr_error_window,
attr_error_title, attr_error_highlight, attr_error_normal,
0, attr_error_waitforkey, _(" Interstellar Trading Bank "),
/* xgettext:c-format */
_("Your debt has amounted to %N!\n"
"^{The Bank has impounded ^}%N^{ from your cash.^}"),
player[current_player].debt, impounded);
txrefresh();
player[current_player].cash -= impounded;
player[current_player].debt -= impounded;
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;
}
// Shall we declare them bankrupt?
if (total_value(current_player) <= 0.0 && randf() < MAKE_BANKRUPT) {
bankrupt_player(true);
}
}
}
/***********************************************************************/
// cmp_game_move: Compare two game_move[] elements for sorting
int cmp_game_move (const void *a, const void *b)
{
const move_rec_t *aa = (const move_rec_t *) a;
const move_rec_t *bb = (const move_rec_t *) b;
if (aa->x < bb->x) {
return -1;
} else if (aa->x > bb->x) {
return 1;
} else {
if (aa->y < bb->y) {
return -1;
} else if (aa->y > bb->y) {
return 1;
} else {
return 0;
}
}
}
/***********************************************************************/
// End of file