/* ** starlanes v1.2.2 (29-Mar-1997) -- a space-age stock trading game ** ** Copyright (C) 1997 Brian "Beej" Hall ** ** 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 2 ** 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, write to the Free Software ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ** ** For more information, contact "beej@ecst.csuchico.edu". ** ** ** This copy of starlanes was hacked by dmt ** - Added a tax system ** - Added events ** - added !BUGS!: ** Solar Flare/Supernova code not working: ** ! Supernova claims to be at [x,y], nearest star is [x,y+1], nova happens ** at [x+1,y+1]. ** ! Flares do not display when flare announced, then: ** ! Nova replaced by @ and flares are ^@ instead of SPACE chars. ** ! Solar Flare event comes with no string, even though it should default ** to a generic string ** ! Got an "ev_flare: all dirs invalid" error; THIS SHOULD NOT HAPPEN ** ! flare_cleanup() doesn't; No corps die when flared. */ #include #include #include #include #include #include #include #include /* color stuff: */ #define BLUE_ON_BLACK COLOR_PAIR(1) #define RED_ON_BLACK COLOR_PAIR(2) #define GREEN_ON_BLACK COLOR_PAIR(3) #define YELLOW_ON_BLACK COLOR_PAIR(4) #define MAGENTA_ON_BLACK COLOR_PAIR(5) #define CYAN_ON_BLACK COLOR_PAIR(6) #define WHITE_ON_BLACK COLOR_PAIR(7) #define YELLOW_ON_BLUE COLOR_PAIR(8) #define WHITE_ON_BLUE COLOR_PAIR(9) #define BLACK_ON_YELLOW COLOR_PAIR(10) #define BLACK_ON_WHITE COLOR_PAIR(11) #define BLACK_ON_RED COLOR_PAIR(12) #define BLACK_ON_BLUE COLOR_PAIR(13) #define BLACK_ON_GREEN COLOR_PAIR(14) #define MAP_TITLE (color?(BLACK_ON_WHITE):(A_REVERSE)) #define MAP_BORDER (color?(WHITE_ON_BLACK|A_BOLD):(A_BOLD)) #define MAP_SPACE (color?(WHITE_ON_BLACK):(A_NORMAL)) #define MAP_STAR (color?(YELLOW_ON_BLACK|A_BOLD):(A_BOLD)) #define MAP_NEWCO (color?(CYAN_ON_BLACK):(A_NORMAL)) #define MAP_BLACKHOLE (A_NORMAL) #define CO_A (color?(BLUE_ON_BLACK|A_BOLD):(A_NORMAL)) #define CO_B (color?(GREEN_ON_BLACK):(A_NORMAL)) #define CO_C (color?(YELLOW_ON_BLACK):(A_NORMAL)) #define CO_D (color?(RED_ON_BLACK):(A_NORMAL)) #define CO_E (color?(MAGENTA_ON_BLACK):(A_NORMAL)) #define GENERAL_TITLE (color?(WHITE_ON_BLUE|A_BOLD):(A_REVERSE)) #define GENERAL_TITLE_BLINK (color?(WHITE_ON_BLUE|A_BOLD|A_BLINK):(A_REVERSE|A_BLINK)) #define GENERAL_BORDER (color?(BLUE_ON_BLACK):(A_NORMAL)) #define GENERAL_TEXT (A_NORMAL) #define COINFO_TITLE (color?(BLACK_ON_GREEN):(A_REVERSE)) #define COINFO_TEXT (A_BOLD) #define STAND_TITLE (color?(YELLOW_ON_BLUE|A_BOLD):(A_REVERSE)) #define STAND_BORDER (color?(BLUE_ON_BLACK):(A_BOLD)) #define STAND_HEADER (color?(WHITE_ON_BLUE):(A_REVERSE)) #define MORE_COINFO_TITLE (color?(YELLOW_ON_BLUE|A_BOLD):(A_REVERSE)) #define MORE_COINFO_BORDER (color?(BLUE_ON_BLACK):(A_BOLD)) #define MORE_COINFO_HEADER (color?(WHITE_ON_BLUE):(A_REVERSE)) #define QUIT_TITLE (color?(BLACK_ON_RED):(A_REVERSE)) #define QUIT_BORDER (color?(RED_ON_BLACK):(A_BOLD)) #define QUIT_TEXT (color?(YELLOW_ON_BLACK|A_BOLD):(A_NORMAL)) /* misc stuff: */ #define SPACE '.' /* space character */ #define NEWCO '+' /* new company character */ #define STAR '*' /* star character */ #define BLACKHOLE '@' /* black hole character */ #define NUMCO 5 /* number of companies (don't change) */ #define INIT_CO_COST 100 /* initial company start cost */ #define INIT_CASH 6000 /* initial player cash */ #define SPLIT_PRICE 3000 /* when stocks split 2-1 */ #define FOUNDER_BONUS 5 /* founder gets this much stock */ #define STARCOST 500 /* company's price increase near star */ #define BLACKHOLECOST -500 /* price increase near black hole */ #define NEWCOCOST 100 /* company's price increase near new co */ #define DIVIDEND_RATE 0.05 /* stock gives this bonus each turn(float) */ #define NUMMOVES 5 /* number of different moves a player gets */ #define MAXPLAYERS 5 /* total number of players a game can have */ #define END_PERCENT 54 /* end when this much of the map is full */ #define DEF_LINES 25 /* default number of lines on screen */ #define DEF_COLUMNS 80 /* default number of columns on screen */ /* Income tax hack by dmt: Unfinished*/ #define TAX_DIVIDENDS 1 /* Taxes turnly dividends from stock */ #define TAX_SALES 0 /* Unimplemented! Taxes money from sale of stock. */ #define TAXMODE_NONE 0 /* Compiles to no tax like the original */ #define TAXMODE_TIERED 1 /* Tiered tax mode. */ #define TAXMODE_FLAT 2 /* Flat Tax. Malcom Forbes Jr, eat your heart out */ #define TAXMODE_PERCENT 3 /* Percentage of income related to max tax */ #define USE_TAX TAXMODE_TIERED /* Whatever taxmode is being used */ #if (USE_TAX == TAXMODE_TIERED) #define NUM_TIERS 6 /* Number of tax tiers */ int TAX_TIER[NUM_TIERS] ={ /* The divides between different tax tiers */ 0, /* VALUES MUST BE SORTED low-high */ 100, 4000, 10000, 25000, 50000 }; float TAX_TIER_RATE[NUM_TIERS] /* The rate in each tax tier */ ={ 0.00, 0.08, 0.15, 0.25, 0.40, 0.65 }; /* NOTE: There must be at least as many tax tiers and rates as NUM_TIERS. */ /* Otherwise, the program will crash if you try the tiered system. */ #endif /*TAXMODE_TIERED*/ #if (USE_TAX == TAXMODE_FLAT) float TAX_RATE = 0.15; /* Flat Tax */ #endif /*TAXMODE_FLAT*/ #if (USE_TAX == TAXMODE_PERCENT) float MAX_TAX = 0.70; /* Maximum amount someone may be taxed. */ #define MAX_INCOME 50000 /* Income above this has taxrate MAX_TAX */ #endif /*TAXMODE_PERCENT*/ /* End of defines for dmt's income tax hack */ /* Events: hacked in by dmt * The chance of an event happening is (number of events)/EVENT_CHANCE */ #define EVENT_CHANCE 10 /* Higher -- Less events. 0 for no events. */ #if EVENT_CHANCE void event(void); /* Runs events */ #define TAX_INCR_AMT .0075 /* Amount of Tax Increase event */ #define TAX_DECR_AMT .005 /* Amount of Tax Decrease event */ #define MAX_SHOP_DMG 10 /* Maximum percent cost of Shopping event */ #define NO_LOSE_BONUS 0 /* 0 loses bonus when player loses turn */ #define NOVA_CHANCE 4 /* 1 in X chance supernova. */ /* Failed novae become solar flares. */ #define FLARE_UL '\\' /* Flare characters for nova/flare */ #define FLARE_U '|' #define FLARE_UR '/' #define FLARE_R '-' #define FLARE_DR '\\' #define FLARE_D '|' #define FLARE_DL '/' #define FLARE_L '-' #define FLARE_UL '\\' #endif/*EVENT_CHANCE*/ /* End of defines for the events */ #define CR 13 /* various keys */ #define LF 10 #define BS 8 #define DEL 127 #define ESC 27 #define CTRL_L 12 #define CTRL_C 3 #define CTRL_Z 26 /* macros to look at surrounding spaces on the map: */ #define up_obj(move) (((move)-MAPX < 0)?SPACE:map[(move)-MAPX]) #define down_obj(move) (((move)+MAPX >= MAPX*MAPY)?SPACE:map[(move)+MAPX]) #define left_obj(move) (((move)%MAPX)?map[(move)-1]:SPACE) #define right_obj(move) (((move)%MAPX == MAPX-1)?SPACE:map[(move)+1]) #define iscompany(c) ((c)>='A'&&(c)<=('A'+NUMCO-1)) #define ripe(c) ((c)==STAR||(c)==NEWCO) #define co_near(move) (iscompany(up_obj(move))||iscompany(down_obj(move))||iscompany(left_obj(move))||iscompany(right_obj(move))) #define s_or_bh(c) ((c)==SPACE||(c)==BLACKHOLE) /* player and company structures: */ typedef struct { char name[100]; int holdings[NUMCO]; int svalue; /* stock value -- not always accurate!! */ int cash; } PLAYER; typedef struct { char name[100]; int price; int size; } COMPANY; /* function prototypes: */ void initialize(void); void color_setup(void); void get_num_players(void); void showmap(void); void drawmap(int loc, char c); int get_move(void); void show_coinfo(void); void more_coinfo(void); void do_move(int move); void do_merge(int *c1, int *c2, int *o1, int *o2); void holding_bonus(void); void holding_bonus(void); void buy_sell(void); int check_endgame(void); int count_used_sectors(void); void calc_cost(int cnum, int move, int n, int s, int w, int e); void new_co_announce(int newc); void suck_announce(int conum, int grown); void merge_announce(int c1, int c2); void xaction_announce(int c1, int c2); void split_announce(int conum); int co_avail(void); void clear_general(char *s,int blink); void center(WINDOW *win, int width, int row, char *s); int my_mvwgetstr(WINDOW *win, int y, int x, int max, int restrict, char *s); void redraw(void); void show_standings(char *title); int order_compare(const void *v1, const void *v2); void quit_yn(void); void shutdown(void); void usage(void); /* global variables */ char *VERSION = "1.2.2"; char *VERSION_DATE = "29-Mar-1997"; char *ident = "$Id: starlanes.c 1.2.2 29-Mar-1997 beej@ecst.csuchico.edu $"; int MAPX = 12; /* x dimension of map */ int MAPY = 10; /* y dimension of map */ int LINES; /* lines in screen */ int COLUMNS; /* columns in screen */ char *map; /* pointer to the map data */ PLAYER *pl; /* pointer to array of players */ COMPANY *co; /* pointer to array of companies */ int numplayers,turn; /* number of players, whose turn it is */ WINDOW *mapwin,*general,*coinfo; /* pointers to the windows */ int color; /* true if we want color */ int main(int argc, char *argv[]) { int done = 0,move, colorforce=0, monoforce=0; switch(argc) { case 1: break; case 2: if (argv[1][1] == 'v') { fprintf(stderr,"Starlanes for ncurses v%s Copyright (C) by Brian \"Beej\" Hall %s\n",VERSION,VERSION_DATE); fprintf(stderr,"\nStarlanes comes with ABSOLUTELY NO WARRANTY. This is free\n"); fprintf(stderr,"software, and you are welcome to redistribute it under\n"); fprintf(stderr,"certain conditions. See the file COPYING for details.\n"); exit(1); } else if (argv[1][1] == 'c') colorforce = 1; else if (argv[1][1] == 'm') monoforce = 1; else usage(); break; default:usage(); } /* initscr */ initscr(); start_color(); if (colorforce) color = 1; else if (monoforce) color = 0; else color = has_colors(); if (color) color_setup(); raw(); /* init map, stocks */ srand(time(NULL)); initialize(); /* num players */ get_num_players(); clear(); attron(color?(YELLOW_ON_BLUE|A_BOLD):A_REVERSE); mvprintw(0,0," StarLanes "); attroff(color?(YELLOW_ON_BLUE|A_BOLD):A_REVERSE); attron(color?BLUE_ON_BLACK:A_NORMAL); printw("====================================================================="); attroff(color?BLUE_ON_BLACK:A_NORMAL); wnoutrefresh(stdscr); showmap(); show_coinfo(); do { #if EVENT_CHANCE event(); #endif /* EVENT_CHANCE */ move = get_move(); do_move(move); holding_bonus(); if ((done = check_endgame()) != 1) { buy_sell(); turn = (++turn)%numplayers; } } while (!done); shutdown(); return 0; } /* ** initialize() - sets up the map, players, and companies */ void initialize(void) { int i,j; char *lines, *columns; /* get the size of the screen: */ if ((lines=getenv("LINES"))==NULL || (columns=getenv("COLUMNS"))==NULL) { LINES = DEF_LINES; COLUMNS = DEF_COLUMNS; } else { LINES = atoi(lines); COLUMNS = atoi(columns); } /* allocate space for everything: */ if ((map=malloc(MAPX*MAPY)) == NULL) { fprintf(stderr,"starlanes: error mallocing space for map\n"); exit(1); } if ((co=calloc(1,NUMCO * sizeof(COMPANY))) == NULL) { fprintf(stderr,"starlanes: error mallocing space for companies\n"); exit(1); } if ((pl=calloc(1,MAXPLAYERS * sizeof(PLAYER))) == NULL) { fprintf(stderr,"starlanes: error mallocing space for players\n"); exit(1); } /* set up the map: */ for(i=0;i MAXPLAYERS); addch(c+'0'); numplayers = (int)c; srand(getpid()); /* reseed the dumb random number generator */ turn = rand()%numplayers; nl(); for(i=0;iNUMMOVES); echo(); for(i=0;i SPLIT_PRICE) split_announce(newc_type-'A'); else show_coinfo(); } } } /* ** do_merge() -- does all the nasty business behind a merge */ void do_merge(int *c1, int *c2, int *o1, int *o2) { int t,i,cb,cs,doswap=0; cb = *c1 - 'A'; cs = *c2 - 'A'; if (co[cs].size == co[cb].size) { /* if same size, check prices */ int pb=0,ps=0; for(i=0;i pb) /* if smaller co has higher worth, swap 'em */ doswap = 1; else if (ps == pb) /* if same price, choose rand */ doswap = rand()%2; } if (co[cs].size > co[cb].size || doswap) { /* cb = merger, cs = mergee */ t = cs; cs = cb; cb = t; } for(i=0;i SPLIT_PRICE) split_announce(cb); } /* ** calc_cost() -- adds value to a company based on surroundings and converts ** NEWCOs to the company */ void calc_cost(int cnum, int move, int n, int s, int w, int e) { if (n == STAR) co[cnum].price += STARCOST; /* stars */ if (s == STAR) co[cnum].price += STARCOST; if (w == STAR) co[cnum].price += STARCOST; if (e == STAR) co[cnum].price += STARCOST; if (n == BLACKHOLE) co[cnum].price += BLACKHOLECOST; /* black holes */ if (s == BLACKHOLE) co[cnum].price += BLACKHOLECOST; if (w == BLACKHOLE) co[cnum].price += BLACKHOLECOST; if (e == BLACKHOLE) co[cnum].price += BLACKHOLECOST; if (n == NEWCO) { /* starter companies */ map[move-MAPX] = cnum + 'A'; drawmap(move-MAPX,cnum+'A'); co[cnum].size++; co[cnum].price += NEWCOCOST; } if (s == NEWCO) { map[move+MAPX] = cnum + 'A'; drawmap(move+MAPX,cnum+'A'); co[cnum].size++; co[cnum].price += NEWCOCOST; } if (w == NEWCO) { map[move-1] = cnum + 'A'; drawmap(move-1,cnum+'A'); co[cnum].size++; co[cnum].price += NEWCOCOST; } if (e == NEWCO) { map[move+1] = cnum + 'A'; drawmap(move+1,cnum+'A'); co[cnum].size++; co[cnum].price += NEWCOCOST; } wnoutrefresh(mapwin); } /* ** new_co_announce() -- announce the coming of a new company */ void new_co_announce(int newc) { char s[80]; clear_general(" Special Announcement! ",1); wattron(general,A_BOLD); center(general,COLUMNS-2,2,"A new shipping company has been formed!"); sprintf(s,"Its name is %s",co[newc].name); center(general,COLUMNS-2,4,s); wattroff(general,A_BOLD); center(general,COLUMNS-2,7,"Press any key to continue..."); wnoutrefresh(general); noecho();raw(); doupdate(); while (getch() == CTRL_L) redraw(); } /* ** suck_announce() -- when a company gets drawn into a black hole (value < 0) */ void suck_announce(int conum, int grown) { int i; if (conum >= 0) { for(i=0;i= 0 && grown == 1) { /* already existed */ center(general,COLUMNS-2,2,"The company named"); center(general,COLUMNS-2,3,co[conum].name); center(general,COLUMNS-2,4,"has been sucked into a black hole!"); center(general,COLUMNS-2,6,"All players' holdings lost."); show_coinfo(); /* show change */ } else if (conum >= 0 && grown == 0) { /* was trying to start up */ center(general,COLUMNS-2,2,"The company that would have been named"); center(general,COLUMNS-2,3,co[conum].name); center(general,COLUMNS-2,4,"has been sucked into a black hole!"); } else { /* was only a starter company, not a real one yet */ center(general,COLUMNS-2,2,"The new company site just placed"); center(general,COLUMNS-2,3,"has been sucked into a black hole!"); } wattroff(general,A_BOLD); center(general,COLUMNS-2,8,"Press any key to continue..."); wnoutrefresh(general); noecho();raw(); doupdate(); while (getch() == CTRL_L) redraw(); } /* ** merge_announce() -- announce a merger */ void merge_announce(int c1, int c2) { clear_general(" Special Announcement! ",1); wattron(general,A_BOLD); center(general,COLUMNS-2,2,co[c2].name); wattroff(general,A_BOLD); center(general,COLUMNS-2,3,"has just been merged into"); wattron(general,A_BOLD); center(general,COLUMNS-2,4,co[c1].name); wattroff(general,A_BOLD); center(general,COLUMNS-2,7,"Press any key to continue..."); wnoutrefresh(general); noecho();raw(); doupdate(); while (getch() == CTRL_L) redraw(); } /* ** xaction_announce() -- announce transactions after a merger */ void xaction_announce(int c1, int c2) { int i,totalshares=0,newshares,bonus,totalholdings,percentage; clear_general(" Stock Transactions ",0); center(general,COLUMNS-2,2,co[c2].name); wattron(general,color?YELLOW_ON_BLUE|A_BOLD:A_REVERSE); mvwprintw(general,2,4,"=Player===============Old Stock===New Stock===Total Holdings===Bonus="); wattroff(general,color?YELLOW_ON_BLUE|A_BOLD:A_REVERSE); for(i=0;i= 0; i--){ if(earnings > TAX_TIER[i]){ pl[turn].cash += (int)((earnings - TAX_TIER[i]) *(1.0-TAX_TIER_RATE[i])); earnings = TAX_TIER[i]; } } #endif /* TAXMODE_TIERED */ #if USE_TAX == TAXMODE_FLAT pl[turn].cash += (int)(earnings * (1.0-TAX_RATE)); #endif /* TAXMODE_FLAT */ #if USE_TAX == TAXMODE_PERCENT if(earnings > MAX_INCOME){ pl[turn].cash += (int)(earnings *(1.0-MAX_TAX)); } else{ pl[turn].cash += (int)earnings * (1.0-MAX_TAX*(earnings/(float)MAX_INCOME)); } #endif /* TAXMODE_PERCENT */ #endif /* USE_TAX */ #if USE_TAX == TAXMODE_NONE /* Here's Beej's nice code before I screwed it up - dmt */ for(i=0;i= min && amt <= max) { pl[turn].cash += (-amt * co[cos[cursor]].price); pl[turn].holdings[cos[cursor]] += amt; } else { mvwprintw(general,cursor+3,40,"Invalid amount! "); wmove(general,cursor+3,55); wnoutrefresh(general); doupdate(); sleep(1); } mvwprintw(general,cursor+3,40," "); sprintf(s," %s (Cash: $%d) ",pl[turn].name,pl[turn].cash); if (amt) show_coinfo(); pos1 = ((COLUMNS-2)-strlen(s))/2; pos2 = pos1 + strlen(s); wattron(general,GENERAL_TITLE); center(general,COLUMNS-2,0,s); wattroff(general,GENERAL_TITLE); wattron(general,GENERAL_BORDER); mvwaddstr(general,0,pos1-4,"////"); mvwaddstr(general,0,pos2,"////"); wattroff(general,GENERAL_BORDER); wmove(general,cursor+3,strlen(co[cos[cursor]].name)+20); wnoutrefresh(general); break; case ESC: done = 1; } /* switch */ if (cursor != newcur) { /* move cursor */ switch(cos[cursor]) { case 0: attrs = CO_A;break; case 1: attrs = CO_B;break; case 2: attrs = CO_C;break; case 3: attrs = CO_D;break; case 4: attrs = CO_E;break; } wattron(general,attrs); mvwprintw(general,cursor+3,20,co[cos[cursor]].name); wattroff(general,attrs); wattron(general,color?BLACK_ON_WHITE:A_REVERSE); mvwprintw(general, newcur+3, 20, co[cos[newcur]].name); wattroff(general,color?BLACK_ON_WHITE:A_REVERSE); wnoutrefresh(general); cursor = newcur; } } while(!done); } /* ** check_endgame() -- returns true if the game is over */ int check_endgame(void) { int sum=0,i,maptotal; for(i=0;i= END_PERCENT && maptotal <= MAPX*MAPY-4; } /* ** count_used_sectors() -- counts the number of non-empty sectors */ int count_used_sectors(void) { int maptotal, i; for(i=maptotal=0;i=0 && i=0 && i=0 && i=0 && i=0 && i=0 && i=0 && i= 0 && i= 0) { for(i=0;i