#include #include #include // #include // finally got rid of it! #include #include /* only for memcpy() */ #include #include #include "sounds.h" #ifdef BIGNUM #include "bignum.h" #endif /* define this to use POKEY's RANDOM register instead of cc65's rand(). Disabled for now, maybe forever (pending testing) */ // #define POKEY_RANDOM /**** These defines should be disabled for normal gameplay. Don't leave any of them enabled for a release or a normal test build. */ /* define this for testing sea_battle(). it causes a pirate attack every time you leave port. */ // #define COMBAT_TEST /* define this to show internals of damage calculation */ // #define DAMAGE_TEST /* define this to start the game in the year 1869, with 1000 capacity, 20 guns, and 1 billion cash and bank. */ // #define TIMEWARP /* define this to start the game in a 99% damaged ship */ // #define ALMOST_DEAD /* define this to test the mchenry() routine by entering damage and capacity numbers directly */ // #define MCHENRY_TEST /* define this to test the cprintfancy_big() routine */ // #define BIGNUM_TEST /**** atari-specific stuff */ /* values returned by cgetc() for backspace/enter/delete keys */ #define BKSP 0x7e #define ENTER 0x9b #define DEL 0x9c /* wait up to 5 sec for a keypress. returns 0 if no key pressed */ extern char __fastcall__ timed_getch(void); /* custom Atari-aware cgetc() wrapper. returns only non-inverse plain ASCII characters, except EOL and BS. Unlike the real cgetc(), it's an unsigned char, and can't return -1 for failure (but, it will never fail. real cgetc() never fails either, even if user hits Break) */ extern unsigned char agetc(void); /* wrapper for agetc(): lowercases letters */ extern unsigned char lcgetc(void); /* wrapper for agetc(): returns only numbers, a, enter, backspace */ extern unsigned char numgetc(void); /* wrapper for agetc(): returns only y or n. dflt is 'y' or 'n' to set the default answer if the user presses Enter, or 0 for no default (waits until user presses either y or n) */ extern unsigned char __fastcall__ yngetc(char dflt); /* sleep for j jiffies (no PAL adjustment at the moment) */ extern void __fastcall__ jsleep(unsigned int j); /* flash screen when we're hit in combat */ extern void explosion(void); /* Atari-specific random number functions from rand.s. Non-Atari platforms can probably just: #define initrand() _randomize() #define randi() ((unsigned int)rand()) #define randl() (unsigned long)((randi() << 16) | randi()) */ #ifdef POKEY_RANDOM #define initrand() /* no-op on Atari */ /* random positive int, 0 to 32767 */ extern unsigned int __fastcall__ randi(void); #else #define initrand() _randomize() #define randi() ((unsigned int)rand()) #endif /* random long, 0 to 2**32-1 */ extern unsigned long __fastcall__ randl(void); /* defined in portstat.s, this is the contents of PORTSTAT.DAT. used to quickly redraw the port stats screen. If ever PORTSTAT.DAT needs to be regenerated, use mkportstats.c */ extern const char *port_stat_screen; /* boolean, whether or not port_stats() needs to redraw the static parts of the port stats screen (by copying port_stat_screen into screen RAM) */ char port_stat_dirty = 1; /* asm curses/conio funcs from console.s. Old C versions moved to oldcurses.c for reference. */ extern void clrtobot(void); extern void clrtoeol(void); /* print 'count' spaces: */ extern void __fastcall__ cspaces(unsigned char count); /* asm funcs from draw_lorcha.s for drawing/animating enemy ships. used by sea_battle() */ extern void __fastcall__ draw_lorcha(int which); extern void __fastcall__ flash_lorcha(int which); extern void __fastcall__ damage_lorcha(int which); extern void __fastcall__ sink_lorcha(int which); extern void __fastcall__ clear_lorcha(int which); /* used to set the background/text colors here, but now the title screen does it (newtitle.s) */ void atari_text_setup() { jsleep(1); POKE(560, PEEK(212)); // restore the POKE(561, PEEK(213)); // display list jsleep(1); POKE(559, 34); // turn on the screen (normal playfield) jsleep(1); POKE(756, FONT_ADDR / 256); // use our custom font POKE(731, 1); // disable keyclick on XL/XE (does nothing on 400/800) } /* this didn't work out, bummer. */ // extern void __fastcall__ waitvcount(unsigned char c); /**** End of atari-specific stuff. Supposed to be, anyway. */ /* old version of this used to just 'return randl()%clamp'. If clamp were 0, the return value would be the unclamped result from randl() (x % 0 == x, in cc65). If it were 1, the return value would always be 1 (no randomness there). */ unsigned long randclamp(unsigned long clamp) { unsigned long r = randl(); if(clamp == 0) return clamp; if(clamp == 1) return r & 0x01; return r % clamp; } unsigned char one_chance_in(unsigned char odds) { return ( (randi() % odds) == 0); } /* print 1 space */ void cspace(void) { cputc(' '); } /* print the letter s (for pluralization) */ void cputc_s(void) { cputc('s'); } /* print 'count' spaces, but leave the cursor where it was. TODO: rewrite in asm. */ void cblank(unsigned char count) { char oldx = wherex(); char oldy = wherey(); cspaces(count); gotoxy(oldx, oldy); } /* conio doesn't back up the cursor if you cputc(BKSP), it prints the graphics character instead. Could use putchar(), but using stdio links a bunch of extra support code. So: */ /* TODO: rewrite in asm */ void backspace() { gotox(wherex()-1); cblank(1); } /* get an inventory item, return its index into items[]. if allow_all is true, allows '*', which is used for 'throw cargo' in sea_battle. */ unsigned char get_item(unsigned char allow_all) { for(;;) { switch(lcgetc()) { case 'o': return 0; case 's': return 1; case 'a': return 2; case 'g': return 3; case '*': if(allow_all) return 4; // else fall thru default: break; } } } /* modified ultoa() with hardcoded radix */ extern char *ultostr(unsigned long value, char* s); /* if your debt goes above this, Elder Brother Wu has you assassinated, game over. Value is: int((2**31 - 1) / 1.1) */ #define DEBT_MAX 1952257860L char wu_assassin = 0; /* taipan functions (modified as little as possible) */ #define GENERIC 1 #define LI_YUEN 2 /* title screen now a separate xex segment (see Makefile for details) */ // void splash_intro(void); unsigned long get_num(void); void name_firm(void); void cash_or_guns(void); void set_prices(void); void port_stats(void); int port_choices(void); void new_ship(void); void new_gun(void); void li_yuen_extortion(void); void elder_brother_wu(void); void good_prices(void); void buy(void); void sell(void); void visit_bank(void); void transfer(void); void quit(void); void overload(void); int sea_battle(int id, int num_ships); void fight_stats(int ships, int orders); void mchenry(void); void retire(void); void final_stats(void); void you_only_have(unsigned char in_bank); void cprintulong(unsigned long ul); void cprintfancy(unsigned long num); void cprintfancy_centered(unsigned long num); void too_much_cash(void); char would_overflow(unsigned long a, unsigned long b); int get_time(void); void cprint_taipan_comma(void); void cprint_taipan_bang(void); void cprint_taipan_bangbang(void); void cprint_taipan_period(void); void cprint_taipan_prompt(void); unsigned char firmpos; /* use page 6 for these buffers */ char *firm = (char *) 0x680; char *num_buf = (char *) 0x600; char *item[] = { "Opium", "Silk", "Arms", "General Cargo" }; char *location[] = { "At sea", "Hong Kong", "Shanghai", "Nagasaki", "Saigon", "Manila", "Singapore", "Batavia" }; // inverse "Jan\0Feb\0Mar\0Apr\0May\0Jun\0Jul\0Aug\0Sep\0Oct\0Nov\0Dec\0"; char *months = "\xca\xe1\xee\0" "\xc6\xe5\xe2\0" "\xcd\xe1\xf2\0" "\xc1\xf0\xf2\0" "\xcd\xe1\xf9\0" "\xca\xf5\xee\0" "\xca\xf5\xec\0" "\xc1\xf5\xe7\0" "\xd3\xe5\xf0\0" "\xcf\xe3\xf4\0" "\xce\xef\xf6\0" "\xc4\xe5\xe3\0"; char *st[] = { "\xc3\xf2\xe9\xf4\xe9\xe3\xe1\xec", // inverse "Critical" "\xa0\xa0\xd0\xef\xef\xf2", // inverse " Poor" " Fair", " Good", " Prime", "Perfect" }; /* char *st[] = { "Critical", " Poor", " Fair", " Good", " Prime", "Perfect" }; */ #ifdef BIGNUM bignum(bank) = BIG_0; bignum(interest_denom) = BIG_200; bignum(big_max_ulong) = BIG_MAX_ULONG; #else unsigned long bank = 0; #endif unsigned long cash = 0, debt = 0, booty = 0; // ec = 20, // ed = 1; // used to be a float, 0.5 unsigned long price[4]; int base_price[4][8] = { {1000, 11, 16, 15, 14, 12, 10, 13}, {100, 11, 14, 15, 16, 10, 13, 12}, {10, 12, 16, 10, 11, 13, 14, 15}, {1, 10, 11, 12, 13, 14, 15, 16} }; /* hkw_ is the warehouse, hold_ is how much of each item is in your hold. both need to be unsigned (makes no sense to have negative amounts in warehouse or hold). hold_ needs to be a long because it's entirely possible to buy e.g. over 32768 of some item later in the game. You won't be able to leave port, so you'll have to get rid of it on the same turn, but we still have to stop it overflowing a 16-bit int. hkw_ doesn't need to be long, since you can never have more than 10,000 of any one item. */ unsigned int hkw_[4]; unsigned long hold_[4]; /* this really can go negative (meaning, your ship is overloaded). It needs to be a long though. At some point, it *still* might overflow, e.g. if general cargo drops to 1, you have over 2**31-1 in cash, and you buy that many general cargo... Probably should limit the amount of cargo the player can buy per transaction, but even then, they can do multiple transactions on the same turn. Need a signed version of would_overflow() to do this right. */ long hold = 0; /* these being negative would be a Bad Thing */ unsigned int guns = 0, month = 1, year = 1860, ec = 20, ed = 1; /* ec+=20, ed++ every game-year (12 turns). player would have to play until 15 Jan 5168 to overflow ec. */ unsigned char port = 1, bp = 0, li = 0, wu_warn = 0, wu_bailout = 0; // these need to be longs to avoid int overflow when // displaying ship status. long damage = 0, capacity = 60, newdamage; /* a bunch of text strings that occur multiple times in the prompts. Each of these actually does save a few bytes, but there are diminishing returns. Anything that only occurs twice might or might not be worth turning into a function. */ void how_much(void) { cputs("How much "); } void how_much_will_you(void) { how_much(); cputs("will you "); } void cprint_bad_joss() { cputs("Bad Joss!!\r\n"); } void crlf(void) { cputs("\r\n"); } void cprint_taipan(void) { cputs("Taipan"); } void comma_space(void) { cputs(", "); } void cprint_taipan_comma(void) { cprint_taipan(); comma_space(); } void cprint_bang(void) { cputc('!'); } void cprint_pipe(void) { cputc('|'); } void cprint_taipan_bang(void) { comma_space(); cprint_taipan(); cprint_bang(); } void cprint_taipan_bangbang(void) { cprint_taipan_bang(); cprint_bang(); } void cprint_taipan_period(void) { comma_space(); cprint_taipan(); cputc('.'); } void cprint_taipan_prompt(void) { comma_space(); cprint_taipan(); cputs("? "); } void do_you_wish(void) { cputs("do you wish "); } #ifdef BIGNUM bignum(big1T) = BIG_1T; bignum(big1B) = BIG_1B; bignum(big1M) = BIG_1M; bignum(big1K) = BIG_1K; bignum(big0) = BIG_0; /* what we should have: For Million/Billion, 3 significant digits. range printed as 0..999999 stet 1M..10M-1 1.23 Million 10M..100M-1 10.2 Million, 100 Million 100M..1B-1 100 Million, 999 Billion 1B..10B-1 1.23 Billion 10B..100B-1 10.2 Billion, 100 Billion 100B..1T-1 100 Billion 1T..inf 1 Trillion+! */ void cprintfancy_big(bignump b) { bignum(tmp); unsigned long leftdigits = 0L; unsigned char rightdigits = 0, letter = 'M', leading0 = 0; big_copy(tmp, b); if(big_cmp(tmp, big0) < 0) { cputc('-'); big_negate(tmp); } if(big_cmp(tmp, big1T) >= 0) { // inverse "1 Trillion+!": cputs("\xb1\xa0\xd4\xf2\xe9\xec\xec\xe9\xef\xee\xab\xa1"); return; } /* for >= 1B, divide by 1M */ if(big_cmp(tmp, big1B) >= 0) { big_div(tmp, tmp, big1K); letter = 'B'; } big_to_ulong(tmp, &leftdigits); if(big_cmp(tmp, big1M) < 0) { /* 0..999999 */ letter = 0; } else if(leftdigits < 10000000L) { /* 1M..10M-1 */ leftdigits /= 10000L; rightdigits = (unsigned char)(leftdigits % 100L); leftdigits /= 100L; if(rightdigits < 10) leading0 = 1; } else if(leftdigits < 100000000L) { /* 10M..100M-1 */ leftdigits /= 100000L; rightdigits = (unsigned char)(leftdigits % 10L); leftdigits /= 10L; } else { leftdigits /= 1000000L; } cprintulong(leftdigits); if(rightdigits) { cputc('.'); if(leading0) cputc('0'); cprintulong((unsigned long)rightdigits); } if(letter) { cspace(); cputc(letter); cputs("illion"); } } #endif int get_time(void) { return ((year - 1860) * 12) + month; } /* print an int or long as a string, conio-style */ void cprintulong(unsigned long ul) { cputs(ultostr(ul, num_buf)); } void at_sea(void) { gotoxy(30, 6); cspace(); revers(1); cputs(location[0]); revers(0); cspaces(3); } /* this bit of code was duplicated a *bunch* of times, making it a function makes the binary 2K smaller. */ void prepare_report(void) { gotoxy(0, 16); clrtobot(); } void compradores_report(void) { prepare_report(); cputs("Comprador's Report\r\n\n"); } void captains_report(void) { prepare_report(); cputs(" Captain's Report\r\n\n"); } void clear_msg_window(void) { gotoxy(0, 18); clrtobot(); } void overload(void) { compradores_report(); cputs("Your ship is overloaded"); cprint_taipan_bangbang(); good_joss_sound(); timed_getch(); return; } void new_ship(void) { int choice = 0, time; unsigned long amount; // time = ((year - 1860) * 12) + month; time = get_time(); amount = randi()%(1000 * (time + 5) / 6) * (capacity / 50) + 1000; if(cash < amount) { return; } compradores_report(); cputs("Do you wish to trade in your "); if(damage > 0) { cputs("\xe4\xe1\xed\xe1\xe7\xe5\xe4"); // inverse "damaged" } else { cputs("fine"); } cputs("\r\nship for one with 50 more capacity by\r\npaying an additional "); cprintfancy(amount); cprint_taipan_prompt(); choice = yngetc(0); if(choice == 'y') { cash -= amount; hold += 50; capacity += 50; damage = 0; } if (one_chance_in(2) && (guns < 1000)) { port_stats(); new_gun(); } port_stats(); return; } void new_gun(void) { int choice = 0, time; unsigned long amount; // time = ((year - 1860) * 12) + month; time = get_time(); amount = randi()%(1000 * (time + 5) / 6) + 500; if((cash < amount) || (hold < 10)) { return; } // fancy_numbers(amount, fancy_num); compradores_report(); cputs("Do you wish to buy a ship's gun\r\nfor "); // cputs(fancy_num); cprintfancy(amount); cprint_taipan_prompt(); choice = yngetc(0); if(choice == 'y') { cash -= amount; hold -= 10; guns += 1; } port_stats(); return; } void cprintfancy_centered(unsigned long num) { if(num < 1000000L) { /* 0 to 999999: | 999999 | | 99999 | | 9999 | | 999 | | 99 | | 9 | */ cspaces(3); if(num < 100L) cspace(); if(num < 10000L) cspace(); revers(1); cprintulong(num); } else { revers(1); cprintfancy(num); } revers(0); } /* if BIGNUM, cprintfancy() just converts to bignum and uses cprintfancy_big() to do the work. A bit slower, but fast enough, and avoids duplicate logic. */ #ifdef BIGNUM void cprintfancy(unsigned long num) { bignum(b); ulong_to_big(num, b); cprintfancy_big(b); } #else /* replaces old fancy_numbers. same logic, but stuff is just printed on the screen rather than being kept in a buffer. One minor difference between this and fancy_numbers() is that we print "1.10 Million" rather than "1.1 Million" (extra zero). I don't think anyone's going to complain. */ void cprintfancy(unsigned long num) { unsigned char tmp; if(num >= 100000000L) { /* 100 million and up: |1000 Million| |100 Million | */ cprintulong(num / 1000000L); } else if (num >= 10000000L) { /* 10 million to 99 million: | 10 Million | |10.1 Million|*/ tmp = (num % 1000000L) / 100000L; cprintulong(num / 1000000L); if(tmp) { cputc('.'); cprintulong(tmp); } } else if (num >= 1000000L) { /* 1 million to 9 million: | 1 Million | |1.10 Million| // always has 0, never 1.1 |1.23 Million| */ tmp = (num % 1000000L) / 10000L; cprintulong(num / 1000000L); if(tmp) { cputc('.'); if(tmp < 10L) cputc('0'); cprintulong(tmp); } } else { /* 0 to 999999: | 999999 | | 99999 | | 9999 | | 999 | | 99 | | 9 | */ cprintulong(num); return; } cputs(" Million"); } #endif void justify_int(unsigned int num) { if(num < 1000) cspace(); if(num < 100) cspace(); if(num < 10) cspace(); cprintulong(num); } void hide_cursor() { gotoxy(0,23); cspace(); } void update_guns() { revers(1); gotoxy(31, 1); justify_int(guns); revers(0); hide_cursor(); } void fight_stats(int ships, int orders) { // cursor(0); gotoxy(0, 0); justify_int(ships); cputs(" ship"); if(ships != 1) cputc_s(); // cputs(" attacking, Taipan! \r\n"); cputs(" attacking"); cprint_taipan_bang(); cspace(); crlf(); cputs("Your orders are: "); if(orders == 1) cputs("Fight "); else if(orders == 2) cputs("Run "); else if(orders == 3) cputs("Throw Cargo"); hide_cursor(); } /* print an inverse video plus if there are offscreen ships, or clear it to a space if not. */ void plus_or_space(unsigned char b) { gotoxy(39, 15); cputc(b ? 0xab : ' '); hide_cursor(); } char set_orders(char orders) { switch((char)timed_getch()) { case 'f': return 1; case 'r': return 2; case 't': return 3; default: return orders; } } int sea_battle(int id, int num_ships) { /* These locals seem to eat too much stack and cause weird behaviour, so they're static now. */ static int ships_on_screen[10]; static int orders, num_on_screen, time, s0, ok, ik, i, // input, status; char choice, flashctr; unsigned long amount, total; port_stat_dirty = 1; orders = 0; num_on_screen = 0; // time = ((year - 1860) * 12) + month; time = get_time(); s0 = num_ships; ok = 0; ik = 1; booty = (time / 4 * 1000 * num_ships) + randi()%1000 + 250; if(would_overflow(cash, booty)) { booty = 0L; } for(i = 0; i <= 9; i++) { ships_on_screen[i] = 0; } clrscr(); // cursor(0); /* the static part of "we have N guns" display, gets printed only once per battle. Bloats the code by 30-odd bytes, but updates are smoother-looking. Maybe. */ revers(1); gotoxy(30, 0); cputs(" We have"); gotoxy(30, 1); cspaces(6); cputs("guns"); revers(0); update_guns(); fight_stats(num_ships, orders); while(num_ships > 0) { if(damage >= capacity) return 4; status = 100L - ((damage * 100L / capacity)); /* // I think this is a problem: if(status <= 0) { return 4; } */ // flushinp(); gotoxy(0, 3); clrtoeol(); cputs("Current seaworthiness: "); cputs(st[status / 20]); cputs(" ("); cprintulong(status); cputs("%)"); gotoxy(0, 4); for(i = 0; i <= 9; i++) { if (num_ships > num_on_screen) { if (ships_on_screen[i] == 0) { jsleep(5); ships_on_screen[i] = (randi() % ec) + 20; draw_lorcha(i); num_on_screen++; } } } plus_or_space(num_ships > num_on_screen); gotoxy(0, 16); // cputs("\r\n"); crlf(); orders = set_orders(orders); if(orders == 0) { orders = set_orders(orders); if(!orders) { gotoxy(0, 3); clrtoeol(); cprint_taipan_comma(); cputs("what shall we do??\r\n(Fight, Run, Throw cargo)"); under_attack_sound(); while(! (orders = set_orders(orders)) ) ; gotoxy(0, 3); clrtoeol(); gotoxy(0, 4); clrtoeol(); /* while ((input != 'f') && (input != 'r') && (input != 't')) { input = lcgetc(); } gotoxy(0, 3); clrtoeol(); gotoxy(0, 4); clrtoeol(); if(input == 'f') { orders = 1; } else if(input == 'r') { orders = 2; } else { orders = 3; } */ } } fight_stats(num_ships, orders); if((orders == 1) && (guns > 0)) { static int targeted, sk; sk = 0; ok = 3; ik = 1; gotoxy(0, 3); clrtoeol(); cputs("Aye, we'll fight 'em"); cprint_taipan_period(); timed_getch(); gotoxy(0, 3); clrtoeol(); cputs("We're firing on 'em"); cprint_taipan_bang(); timed_getch(); for(i = 1; i <= guns; i++) { if ((ships_on_screen[0] == 0) && (ships_on_screen[1] == 0) && (ships_on_screen[2] == 0) && (ships_on_screen[3] == 0) && (ships_on_screen[4] == 0) && (ships_on_screen[5] == 0) && (ships_on_screen[6] == 0) && (ships_on_screen[7] == 0) && (ships_on_screen[8] == 0) && (ships_on_screen[9] == 0)) { static int j; for (j = 0; j <= 9; j++) { if (num_ships > num_on_screen) { if(ships_on_screen[j] == 0) { ships_on_screen[j] = randclamp(ec) + 20; draw_lorcha(j); num_on_screen++; } } } } plus_or_space(num_ships > num_on_screen); gotoxy(0, 16); crlf(); do { targeted = randi()%10; } while(ships_on_screen[targeted] == 0); /* flash_lorcha must be called an even number of times to leave the lorcha in an unflashed state after. */ for(flashctr = 0; flashctr < 6; flashctr++) { flash_lorcha(targeted); jsleep(2); } damage_lorcha(targeted); ships_on_screen[targeted] -= randi()%30 + 10; if(ships_on_screen[targeted] <= 0) { num_on_screen--; num_ships--; sk++; ships_on_screen[targeted] = 0; bad_joss_sound(); /* not sure this should be here */ sink_lorcha(targeted); plus_or_space(num_ships > num_on_screen); fight_stats(num_ships, orders); } if(num_ships == 0) { i += guns; } else { jsleep(10); } } gotoxy(0, 3); clrtoeol(); if(sk > 0) { cputs("Sunk "); cprintulong(sk); cputs(" of the buggers"); cprint_taipan_bang(); bad_joss_sound(); } else { cputs("Hit 'em, but didn't sink 'em"); cprint_taipan_bang(); } timed_getch(); // if ((randi()%s0 > (num_ships * .6 / id)) && (num_ships > 2)) if((randi()%s0 > ((num_ships / 2) / id)) && (num_ships > 2)) { static int ran; // ran = randi()%(num_ships / 3 / id) + 1; ran = randclamp(num_ships / 3 / id) + 1; num_ships -= ran; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cprintulong(ran); cputs(" ran away"); cprint_taipan_bang(); bad_joss_sound(); if(num_ships <= 10) { for(i = 9; i >= 0; i--) { if ((num_on_screen > num_ships) && (ships_on_screen[i] > 0)) { ships_on_screen[i] = 0; num_on_screen--; clear_lorcha(i); jsleep(5); } } if(num_ships == num_on_screen) { plus_or_space(0); } } gotoxy(0, 16); orders = set_orders(orders); } } else if ((orders == 1) && (guns == 0)) { gotoxy(0, 3); clrtoeol(); cputs("We have no guns"); cprint_taipan_bangbang(); timed_getch(); } else if (orders == 3) { choice = 0; amount = 0; total = 0; gotoxy(0, 3); cputs("You have the following on board, Taipan:"); gotoxy(4, 4); cputs(item[0]); cputs(": "); cprintulong(hold_[0]); gotoxy(24, 4); cputs(item[1]); cputs(": "); cprintulong(hold_[1]); gotoxy(5, 5); cputs(item[2]); cputs(": "); cprintulong(hold_[2]); gotoxy(21, 5); cputs(item[3]); cputs(": "); cprintulong(hold_[3]); gotoxy(0, 6); clrtoeol(); cputs("What shall I throw overboard"); cprint_taipan_prompt(); choice = get_item(1); if(choice < 4) { gotoxy(0, 6); clrtoeol(); cputs("How much"); cprint_taipan_prompt(); amount = get_num(); if((hold_[choice] > 0) && ((amount == UINT32_MAX) || (amount > hold_[choice]))) { amount = hold_[choice]; } total = hold_[choice]; } else { total = hold_[0] + hold_[1] + hold_[2] + hold_[3]; } gotoxy(0, 4); clrtoeol(); gotoxy(0, 5); clrtoeol(); gotoxy(0, 6); clrtoeol(); if(total > 0) { gotoxy(0, 3); clrtoeol(); cputs("Let's hope we lose 'em"); cprint_taipan_bang(); bad_joss_sound(); if (choice < 4) { hold_[choice] -= amount; hold += amount; ok += (amount / 10); } else { hold_[0] = 0; hold_[1] = 0; hold_[2] = 0; hold_[3] = 0; hold += total; ok += (total / 10); } timed_getch(); } else { gotoxy(0, 3); clrtoeol(); cputs("There's nothing there"); cprint_taipan_bang(); good_joss_sound(); timed_getch(); } } if((orders == 2) || (orders == 3)) { if(orders == 2) { gotoxy(0, 3); clrtoeol(); cputs("Aye, we'll run"); cprint_taipan_period(); timed_getch(); } ok += ik++; if(randi()%ok > randi()%num_ships) { gotoxy(0, 3); clrtoeol(); cputs("We got away from 'em"); cprint_taipan_bang(); good_joss_sound(); timed_getch(); num_ships = 0; } else { gotoxy(0, 3); clrtoeol(); cputs("Couldn't lose 'em."); timed_getch(); if((num_ships > 2) && (one_chance_in(5))) { static int lost; lost = (randi()%num_ships / 2) + 1; num_ships -= lost; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("But we escaped from "); cprintulong(lost); cputs(" of 'em!"); if(num_ships <= 10) { for(i = 9; i >= 0; i--) { if((num_on_screen > num_ships) && (ships_on_screen[i] > 0)) { ships_on_screen[i] = 0; num_on_screen--; clear_lorcha(i); jsleep(5); } } plus_or_space(num_ships > num_on_screen); } gotoxy(0, 16); orders = set_orders(orders); } } } if(num_ships > 0) { gotoxy(0, 3); clrtoeol(); cputs("They're firing on us"); cprint_taipan_bang(); timed_getch(); hide_cursor(); explosion(); fight_stats(num_ships, orders); plus_or_space(num_ships > num_on_screen); gotoxy(0, 3); clrtoeol(); cputs("We've been hit"); cprint_taipan_bangbang(); under_attack_sound(); timed_getch(); i = (num_ships > 15) ? 15 : num_ships; // is this really correct? // if ((guns > 0) && ((randi()%100 < (((float) damage / capacity) * 100)) || // ((((float) damage / capacity) * 100) > 80))) if((guns > 0) && ((randi()%100 < ((damage * 100L) / capacity)) || (((damage * 100L) / capacity)) > 80)) { i = 1; guns--; hold += 10; fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("The buggers hit a gun"); cprint_taipan_bangbang(); under_attack_sound(); fight_stats(num_ships, orders); update_guns(); timed_getch(); } // damage = damage + ((ed * i * id) * ((float) randi() / RAND_MAX)) + (i / 2); // remember, ed is now scaled by 2 (used to be 0.5, now 1) // broken because sometimes works out to 0 or 1. If it's 0, // randi()%0 is just randi() (ouch)... on a modern platform, // this would trigger a floating point exception or similar. // cc65 runtime can't detect it... // If ((ed * i * id)/2)) works out to 1, anything%1 is 0. // damage = damage + (randi() % ((ed * i * id)/2)) + (i / 2); // The answer is to avoid to % operator if the 2nd arg would be // 0 or 1: the intended result would just be 0 or 1 anyway. newdamage = ((ed * i * id)/2) + (i / 2); if(newdamage <= 0) newdamage = 1; // how the hell could this happen? if(newdamage > 1) newdamage = randi() % newdamage; damage += newdamage; if(damage > capacity) damage = capacity; /* just in case */ /* the above is still somehow broken. When fighting lots of ships, late in the game, we still get ship status over 100% in the fight screen, and mchenry says we're 4 billion percent damaged (and memory gets all kinds of corrupted after that). I do NOT understand what's going on here. It looks like damage is still somehow going negative, but that shouldn't be possible. */ if(damage < 0) damage = capacity; /* band-aid! */ if(damage == capacity) return 4; #ifdef DAMAGE_TEST gotoxy(0, 23); clrtoeol(); cprintulong(ed); cspace(); cprintulong(i); cspace(); cprintulong(id); cspace(); cprintulong(damage); cspace(); cprintulong(newdamage); cgetc(); #endif if((id == GENERIC) && (one_chance_in(20))) { return 2; } } } if(orders == 1) { // clrscr(); fight_stats(num_ships, orders); gotoxy(0, 3); clrtoeol(); cputs("We got 'em all"); cprint_taipan_bang(); bad_joss_sound(); timed_getch(); return 1; } else { return 3; } } /* TODO: rewrite in asm. Maybe. */ unsigned long get_num(void) { unsigned char count = 0; char input; // cursor(1); // cblank(1); while((input = numgetc()) != '\n') { if(count >= 10) continue; if(input == BKSP) { if(!count) continue; backspace(); num_buf[count] = '\0'; count--; } else if(input == 'a') { if(!count) { // cursor(0); return UINT32_MAX; } } else if(input == 'k' || input == 'm') { char i; for(i = 0; i < (input == 'k' ? 3 : 6); i++) { cputc('0'); num_buf[count++] = '0'; if(count >= 10) break; } } else if(input == DEL) { while(count) { backspace(); count--; } POKEW(1024, count); } else { cputc(input); num_buf[count++] = input; } } // cursor(0); num_buf[count] = '\0'; return strtoul(num_buf, (char **)NULL, 10); } /* TODO: rewrite in asm */ void cash_or_guns(void) { int choice = 0; clrscr(); cputs("Do you want to start . . .\r\n\n"); cputs(" 1) With cash (and a debt)\r\n\n"); // cputs(" -- or --\r\n\n"); cspaces(16); cputs("-- or --\r\n\n"); cputs(" 2) With five guns and no cash\r\n"); // cputs(" (But no debt!)\r\n"); cspaces(16); cputs("(But no debt!)\r\n"); while ((choice != '1') && (choice != '2')) { gotoxy(10, 10); cputc('?'); choice = agetc(); } cputc(choice); capacity = 60; damage = 0; if (choice == '1') { cash = 400; debt = 5000; hold = 60; guns = 0; li = 0; bp = 10; } else { #ifdef TIMEWARP year = 1869; cash = 1000000000L; // cash = 4294000000L; // cash = 3500000L; #ifdef BIGNUM big_copy(bank, big1M); big_mul(bank, bank, big1M); #else bank = 1000000000L; #endif debt = 0; capacity = 1000; hold = 800; guns = 20; li = 1; bp = 7; ed = 9; ec = 90; #else cash = 0; debt = 0; hold = 10; guns = 5; li = 1; bp = 7; #endif #ifdef ALMOST_DEAD damage = capacity - 1; #endif } return; } void set_prices(void) { char i; for(i = 0; i < 4; ++i) price[i] = base_price[i][port] / 2 * (randi()%3 + 1) * base_price[i][0]; } unsigned int warehouse_in_use() { return hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]; } void port_stats(void) { int i, in_use, status = 100 - ((damage * 100L) / capacity); /* all the static text that used to be in port_stats() has been moved to mkportstats.c, which creates a .xex file which will get prepended to taipan.xex and loaded into a chunk of memory cc65 won't use. When it's time to print it, it'll get memcpy()ed into *SAVMSC. */ #if 0 chlinexy(1, 1, 26); chlinexy(1, 7, 26); chlinexy(1, 13, 26); cvlinexy(0, 2, 5); cvlinexy(27, 2, 5); cvlinexy(0, 8, 5); cvlinexy(27, 8, 5); chlinexy(0, 15, 40); cputcxy(0, 1, 17); // upper left corner cputcxy(0, 7, 1); // |- cputcxy(0, 13, 26); // lower left corner cputcxy(27, 1, 5); // upper right corner cputcxy(27, 7, 4); // -| cputcxy(27, 13, 3); // lower right corner cputsxy(1, 2, "Hong Kong Warehouse"); cputsxy(4, 3, "Opium In use"); cputsxy(4, 4, "Silk "); cputsxy(4, 5, "Arms Vacant"); cputsxy(4, 6, "General "); cputsxy(1, 8, "Hold "); cputsxy(16, 8, "Guns "); cputsxy(4, 9, "Opium "); cputsxy(4, 10, "Silk "); cputsxy(4, 11, "Arms "); cputsxy(4, 12, "General "); cputsxy(32, 2, "Date"); cputsxy(29, 3, "15 "); cputsxy(30, 5, "Location"); cputsxy(32, 8, "Debt"); cputsxy(29, 11, "Ship Status"); cputsxy(0, 14, "Cash: "); cputsxy(20, 14, "Bank: "); #else if(port_stat_dirty) { void *p = (void *)(PEEK(88) + 256 * PEEK(89)); memcpy(p, &port_stat_screen, 640); port_stat_dirty = 0; } #endif /* dynamic stuff: */ gotoxy(firmpos, 0); cputs("Firm: "); cputs(firm); cputs(", Hong Kong"); gotoxy(21, 4); in_use = warehouse_in_use(); cblank(5); cprintulong(in_use); gotoxy(21, 6); cblank(5); cprintulong(10000 - in_use); for(i = 0; i < 4; ++i) { gotoxy(12, i + 3); cblank(5); cprintulong(hkw_[i]); } /* gotoxy(12, 3); cblank(5); cprintulong(hkw_[0]); gotoxy(12, 4); cblank(5); cprintulong(hkw_[1]); gotoxy(12, 5); cblank(5); cprintulong(hkw_[2]); gotoxy(12, 6); cblank(5); cprintulong(hkw_[3]); */ gotoxy(7, 8); if(hold >= 0) { cblank(8); cprintulong(hold); } else { cputs("\xcf\xf6\xe5\xf2\xec\xef\xe1\xe4"); // inverse "Overload" } gotoxy(22, 8); cblank(5); cprintulong(guns); for(i = 0; i < 4; ++i) { gotoxy(12, i + 9); cblank(9); cprintulong(hold_[i]); } /* gotoxy(12, 9); cblank(9); cprintulong(hold_[0]); gotoxy(12, 10); cblank(9); cprintulong(hold_[1]); gotoxy(12, 11); cblank(9); cprintulong(hold_[2]); gotoxy(12, 12); cblank(9); cprintulong(hold_[3]); */ gotoxy(32, 3); // revers(1); cputs(months + 4 * (month - 1)); // revers(0); cspace(); cprintulong(year); gotoxy(30, 6); cblank(10); revers(1); if(port == 4 || port == 5) cspace(); cputs(location[port]); revers(0); gotoxy(28, 9); clrtoeol(); cprintfancy_centered(debt); gotoxy(29, 12); clrtoeol(); i = status / 20; // if(i < 2) revers(1); cputs(st[i]); cputc(':'); cprintulong(status); // revers(0); gotoxy(6, 14); cblank(14); cprintfancy(cash); gotoxy(26, 14); cblank(13); #ifdef BIGNUM cprintfancy_big(bank); #else cprintfancy(bank); #endif } void mchenry(void) { static int choice; compradores_report(); cprint_taipan_comma(); cputs("Mc Henry from the Hong Kong\r\n"); cputs("Shipyards has arrived!! He says, 'I see\r\n"); cputs("ye've a wee bit of damage to yer ship.'\r\n"); cputs("Will ye be wanting repairs? "); choice = yngetc('y'); if(choice == 'y') { static int percent, time; static unsigned long br, repair_price, amount; // int percent = ((float) damage / capacity) * 100, percent = (damage * 100L / capacity); // time = ((year - 1860) * 12) + month; time = get_time(); /* long br = ((((60 * (time + 3) / 4) * (float) randi() / RAND_MAX) + 25 * (time + 3) / 4) * capacity / 50), repair_price = (br * damage) + 1, amount; */ br = ((randclamp(60 * (time + 3) / 4) + 25 * (time + 3) / 4) * capacity / 50); repair_price = (br * damage) + 1; clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Och, 'tis a pity to be "); cprintulong(percent); cputs("% damaged.\r\nWe can fix yer whole ship for "); cprintulong(repair_price); cputs(",\r\nor make partial repairs if you wish.\r\n"); how_much(); cputs("will ye spend? "); for (;;) { gotoxy(24, 21); amount = get_num(); if(amount == UINT32_MAX) { if(cash > repair_price) amount = repair_price; else amount = cash; } if(amount <= cash) { cash -= amount; // damage -= (int)((amount / br) + .5); damage -= (int)(amount / br); damage = (damage < 0) ? 0 : damage; port_stats(); break; } } } return; } /* void retire_blanks(void) { char i; for(i = 0; i < 29; ++i) cspace(); crlf(); // above loop saves a measly 6 bytes over this: // cputs(" \r\n"); } */ void retire_blanks(void) { cspaces(29); crlf(); } #ifdef BIGNUM void aire(void) { char endspace = 1; bignum(networth); bignum(big1T) = BIG_1T; ulong_to_big(cash, networth); big_add(networth, networth, bank); // cputs(" "); cspaces(4); if(big_cmp(networth, big1B) < 0) { cputc('M'); } else if(big_cmp(networth, big1T) < 0) { cputc('B'); } else { cputs("T R"); endspace = 0; } cputs(" I L L I O N A I R E !"); if(endspace) cspaces(2); crlf(); } #endif void retire(void) { compradores_report(); revers(1); // cspaces(29); // crlf(); retire_blanks(); // cputs(" Y o u ' r e a \r\n"); cputs(" Y o u ' r e a"); cspaces(9); crlf(); // cspaces(29); // crlf(); retire_blanks(); #ifdef BIGNUM aire(); #else cputs(" M I L L I O N A I R E ! \r\n"); #endif // cspaces(29); // crlf(); retire_blanks(); revers(0); timed_getch(); final_stats(); } void final_stats(void) { int years = year - 1860, // time = ((year - 1860) * 12) + month, time = get_time(), choice = 0; #ifdef BIGNUM long score; bignum(finalcash); bignum(big_100) = BIG_100; bignum(bigscore); bignum(bigtmp); ulong_to_big(cash, finalcash); ulong_to_big(debt, bigtmp); big_add(finalcash, finalcash, bank); big_sub(finalcash, finalcash, bigtmp); big_div(bigscore, finalcash, big_100); ulong_to_big((unsigned long)time, bigtmp); big_div(bigscore, bigscore, bigtmp); if(big_cmp(bigscore, big1M) > 0) { score = 1000000L; } else if(big_cmp(bigscore, big0) < 0) { score = -1; } else { big_to_ulong(bigscore, (unsigned long*)&score); } #else /* TODO: write cprintlong() to print signed value */ long finalcash = cash + bank - debt; long score = finalcash / 100 / time; #endif port_stat_dirty = 1; clrscr(); cputs("Your final status:\r\n\n" "Net cash: "); #ifdef BIGNUM cprintfancy_big(finalcash); #else cprintfancy(finalcash); #endif cputs("\r\nShip size: "); cprintulong(capacity); cputs(" units with "); cprintulong(guns); cputs(" guns\r\n\n" "You traded for "); cprintulong(years); cputs(" year"); if (years != 1) { cputc_s(); } cputs(" and "); cprintulong(month); cputs(" month"); if (month > 1) { cputc_s(); } cputs("\r\n\n"); revers(1); cputs("Your score is "); #ifdef BIGNUM cprintfancy_big(bigscore); #else cprintulong(score); #endif cputs(".\r\n"); revers(0); if ((score < 100) && (score >= 0)) { cputs("Have you considered a land based job?\r\n\n\n"); } else if (score < 0) { cputs("The crew has requested that you stay on\r\n" "shore for their safety!!\r\n\n"); } else { cputs("\r\n\n\n"); } cputs("Your Rating:\r\n"); cputc(17); // upper left corner chline(31); cputc(5); // upper right corner // cputs("\r\n"); crlf(); cprint_pipe(); // cputc('|'); if (score > 49999L) { revers(1); } cputs("Ma Tsu"); revers(0); cspaces(9); cputs("50,000 and over |\r\n"); cprint_pipe(); // cputc('|'); if ((score < 50000L) && (score > 7999L)) { revers(1); } cputs("Master Taipan"); revers(0); cputs(" 8,000 to 49,999|\r\n"); cprint_pipe(); // cputc('|'); if ((score < 8000L) && (score > 999L)) { revers(1); } // cputs("Taipan"); cprint_taipan(); revers(0); cspaces(10); cputs("1,000 to 7,999|\r\n"); cprint_pipe(); // cputc('|'); if ((score < 1000) && (score > 499)) { revers(1); } cputs("Compradore"); revers(0); cspaces(8); cputs("500 to 999|\r\n"); cprint_pipe(); // cputc('|'); if (score < 500) { revers(1); } cputs("Galley Hand"); revers(0); cspaces(7); cputs("less than 500|\r\n"); cputc(26); // lower left corner chline(31); cputc(3); // lower right corner gotoxy(0, 22); cputs("Play again? "); choice = yngetc(0); if(choice == 'y') { #ifdef BIGNUM big_copy(bank, big0); #else bank = 0; #endif hkw_[0] = 0; hkw_[1] = 0; hkw_[3] = 0; hkw_[4] = 0; hold_[0] = 0; hold_[1] = 0; hold_[2] = 0; hold_[3] = 0; hold = 0; capacity = 60; damage = 0; month = 1; year = 1860; port = 1; name_firm(); cash_or_guns(); set_prices(); return; } /* exit(0) works by itself in DOS 2.0S or 2.5, or any DUP.SYS style DOS that reopens the E: device when entering the menu. However, command-line DOSes (XL and Sparta) don't do this, which results in garbage on screen and wrong colors. So: */ /* restore CHBAS to its original value, generally the ROM font. This is called fontsave in newtitle.s. */ POKE(756, PEEK(0x6fc)); /* restore COLOR1 and COLOR2. These locations are called color1save and color2save in newtitle.s. */ POKE(709, PEEK(0x6fd)); POKE(710, PEEK(0x6fe)); exit(0); } char have_no_cargo(void) { char i; for(i = 0; i < 4; ++i) if(hkw_[i] || hold_[i]) return 0; return 1; } void you_have_only(void) { cputs("You have only "); } void transfer(void) { int i, in_use; unsigned long amount = 0; if(have_no_cargo()) { gotoxy(0, 22); clrtobot(); cputs("You have no cargo"); cprint_taipan_period(); crlf(); good_joss_sound(); timed_getch(); return; } for (i = 0; i < 4; i++) { if (hold_[i] > 0) { for (;;) { compradores_report(); how_much(); cputs(item[i]); cputs(" shall I move\r\nto the warehouse"); cprint_taipan_prompt(); amount = get_num(); if (amount == UINT32_MAX) { amount = hold_[i]; } if (amount <= hold_[i]) { // in_use = hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]; in_use = warehouse_in_use(); if ((in_use + amount) <= 10000) { hold_[i] -= amount; hkw_[i] += amount; hold += amount; break; } else if (in_use == 10000) { gotoxy(0, 21); cputs("Your warehouse is full"); cprint_taipan_bang(); good_joss_sound(); } else { gotoxy(0, 21); cputs("Your warehouse will only hold an\r\nadditional "); cprintulong(10000 - in_use); cprint_taipan_bang(); good_joss_sound(); timed_getch(); } } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); you_have_only(); cprintulong(hold_[i]); // cputs(", Taipan.\r\n"); cprint_taipan_period(); good_joss_sound(); timed_getch(); } } port_stats(); } if (hkw_[i] > 0) { for (;;) { compradores_report(); how_much(); cputs(item[i]); cputs(" shall I move\r\naboard ship"); cprint_taipan_prompt(); amount = get_num(); if (amount == UINT32_MAX) { amount = hkw_[i]; } if (amount <= hkw_[i]) { hold_[i] += amount; hkw_[i] -= amount; hold -= amount; break; } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); you_have_only(); cprintulong(hkw_[i]); cprint_taipan_period(); // cputs("\r\n"); crlf(); timed_getch(); } } port_stats(); } } return; } void quit(void) { #ifdef BIGNUM bignum(banktmp); #endif unsigned char choice, sunk; int result = 0, damagepct; compradores_report(); cprint_taipan_comma(); do_you_wish(); cputs("me to go to:\r\n"); for(choice = 1; choice < 8; ++choice) { if(choice == 7) crlf(); cputc(choice + '0'); cputs(") "); cputs(location[choice]); if(choice != 7) comma_space(); } cputs(" ? "); /* cputs("1) Hong Kong, 2) Shanghai, 3) Nagasaki,\r\n" "4) Saigon, 5) Manila, 6) Singapore, or\r\n" "7) Batavia ? "); */ for (;;) { gotoxy(13, 21); clrtobot(); choice = numgetc() - '0'; if (choice == port) { cputs("\r\n\nYou're already here"); cprint_taipan_period(); good_joss_sound(); timed_getch(); } else if ((choice >= 1) && (choice <= 7)) { port = choice; break; } } at_sea(); captains_report(); #ifdef COMBAT_TEST if(1) #else if (randi()%bp == 0) #endif { int num_ships = randi()%((capacity / 10) + guns) + 1; if (num_ships > 9999) { num_ships = 9999; } cprintulong(num_ships); cputs(" hostile ship"); if(num_ships != 1) cputc_s(); // cputs(" approaching, Taipan!\r\n"); cputs(" approaching"); cprint_taipan_bang(); crlf(); under_attack_sound(); timed_getch(); result = sea_battle(GENERIC, num_ships); gotoxy(0,23); /* to avoid disappearing U in "In use" */ } if (result == 2) { port_stats(); at_sea(); captains_report(); cputs("Li Yuen's fleet drove them off!"); timed_getch(); } if (((result == 0) && (randi()%(4 + (8 * li))) == 0) || (result == 2)) { clear_msg_window(); // cputs("Li Yuen's pirates, Taipan!!\r\n"); cputs("Li Yuen's pirates"); cprint_taipan_bangbang(); // cputs("\r\n"); crlf(); crlf(); bad_joss_sound(); timed_getch(); if (li > 0) { cputs("Good joss!! They let us be!!\r\n"); bad_joss_sound(); timed_getch(); // return; // original code, results in prices not changing. result = 0; } else { static int num_ships; num_ships = randi()%((capacity / 5) + guns) + 5; cprintulong(num_ships); cputs(" ships of Li Yuen's pirate\r\nfleet"); cprint_taipan_bangbang(); // cputs(" ships of Li Yuen's pirate\r\n"); // cputs("fleet, Taipan!!\r\n"); under_attack_sound(); timed_getch(); /* WTF, the original code reads: sea_battle(LI_YUEN, num_ships); ...which seems to mean you die if you succeed in running away from li yuen's pirates... */ result = sea_battle(LI_YUEN, num_ships); gotoxy(0,23); /* to avoid disappearing U in "In use" */ } } if (result > 0) { port_stats(); at_sea(); captains_report(); if (result == 1) { // fancy_numbers(booty, fancy_num); cputs("We captured some booty.\r\n" "It's worth "); // cputs(fancy_num); cprintfancy(booty); cprint_bang(); cash += booty; good_joss_sound(); } else if (result == 3) { cputs("We made it!"); good_joss_sound(); } else { cputs("The buggers got us"); cprint_taipan_bangbang(); cputs("!\r\nIt's all over, now!!!"); timed_getch(); final_stats(); return; } timed_getch(); } if(one_chance_in(10)) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Storm"); cprint_taipan_bangbang(); // cputs("\r\n"); crlf(); crlf(); bad_joss_sound(); timed_getch(); if(one_chance_in(30)) { cputs(" I think we're going down!!\r\n\n"); timed_getch(); // if (((damage / capacity * 3) * ((float) randi() / RAND_MAX)) >= 1) // in the float version, damage/capacity*3 is your damage percentage, // scaled 0 (0%) to 3 (100%). So if you're less than 34% damaged, // you have no chance of sinking. If you're 34%-66% damaged, you // have a 1 in 3 chance. If you're over 66%, you have a 2 in // 3 chance. damagepct = damage * 100L / capacity; if(damagepct < 34) sunk = 0; else if(damagepct < 67) sunk = randclamp(3) == 0; else sunk = randclamp(3) != 0; if(sunk) { cputs("We're going down"); cprint_taipan_bangbang(); // cputs("\r\n"); crlf(); under_attack_sound(); timed_getch(); final_stats(); } } cputs(" We made it!!\r\n\n"); bad_joss_sound(); timed_getch(); if(one_chance_in(3)) { int orig = port; while(port == orig) port = randi()%7 + 1; clear_msg_window(); cputs("We've been blown off course\r\nto "); cputs(location[port]); timed_getch(); } } month++; if(month == 13) { month = 1; year++; ec += 10; ed += 1; } /* debt calculation original formula was: debt = debt + (debt * .1); int-based formula is the same, except it would never increase if debt is <= 10, so we fudge it with debt++ in that case. Which means small debts accrue interest *much* faster, but that shouldn't affect gameplay much. There needs to be some overflow detection though... or maybe we let the overflow through, and the player can think of it as Wu forgiving the debt after enough years go by (or, he lost the paperwork?). Most likely though, the player gets his throat cut long before the amount overflows. */ if(debt) { if(debt > 10) debt += (debt / 10); else debt++; } if(debt >= DEBT_MAX) wu_assassin = 1; #ifdef BIGNUM // no good, assumes a bignum can handle a fraction // big_mul(bank, bank, interest_rate); // bank = bank + (bank / 200); big_div(banktmp, bank, interest_denom); big_add(bank, bank, banktmp); #else /* bank calculation original formula was: bank = bank + (bank * .005); int-based formula is the same, except when bank <= 200, it's linear. */ if(bank) { if(bank > 200) bank += (bank / 200); else bank++; } #endif set_prices(); clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cputs("Arriving at "); cputs(location[port]); cputs("..."); timed_getch(); return; } void li_yuen_extortion(void) { // int time = ((year - 1860) * 12) + month; int time = get_time(); unsigned char choice; /* float i = 1.8, j = 0, amount = 0; */ unsigned long amount = 0; unsigned int i = 2, j = 0; if(time > 12) { j = randi() % (1000 * time) + (1000 * time); i = 1; } // amount = ((cash / i) * ((float) randi() / RAND_MAX)) + j; amount = randclamp((cash >> (i - 1))) + j; if(!amount) return; /* asking for 0 is dumb */ compradores_report(); cputs("Li Yuen asks "); cprintfancy(amount); cputs(" in donation\r\nto the temple of Tin Hau, the Sea\r\nGoddess. Will you pay? "); choice = yngetc(0); if(choice == 'y') { if(amount <= cash) { cash -= amount; li = 1; } else { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cprint_taipan_comma(); cputs("you do not have enough cash!!\r\n\n"); timed_getch(); cputs("Do you want Elder Brother Wu to make up\r\nthe difference for you? "); choice = yngetc(0);; clear_msg_window(); // gotoxy(0, 18); // clrtobot(); if(choice == 'y') { amount -= cash; debt += amount; cash = 0; li = 1; cputs("Elder Brother has given Li Yuen the\r\n" "difference between what he wanted and\r\n" "your cash on hand and added the same\r\n" "amount to your debt.\r\n"); } else { cash = 0; cputs("Very well. Elder Brother Wu will not pay\r\n" "Li Yuen the difference. I would be very\r\n" "wary of pirates if I were you"); cprint_taipan_period(); // cputs("\r\n"); crlf(); } timed_getch(); } } port_stats(); } #ifdef BIGNUM void you_only_have(unsigned char in_bank) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cprint_taipan_comma(); cputs("you only have "); if(in_bank) cprintfancy_big(bank); else cprintfancy(cash); cputs("\r\nin "); cputs(in_bank ? "the bank" : "cash"); cputs(".\r\n"); good_joss_sound(); timed_getch(); } #else void you_only_have(unsigned char in_bank) { clear_msg_window(); // gotoxy(0, 18); // clrtobot(); cprint_taipan_comma(); cputs("you only have "); cprintfancy(in_bank ? bank : cash); cputs("\r\nin "); cputs(in_bank ? "the bank" : "cash"); cputs(".\r\n"); good_joss_sound(); timed_getch(); } #endif void elder_brother_wu(void) { int choice = 0; unsigned long wu = 0; compradores_report(); cputs("Do you have business with Elder Brother\r\nWu, the moneylender? "); for (;;) { gotoxy(21, 19); // choice = agetc(); choice = yngetc('n'); if ((choice == 'n') || choice == 0) break; if ((cash == 0) && #ifdef BIGNUM (big_cmp(bank, big0) == 0) #else (bank == 0) #endif && (guns == 0) && have_no_cargo()) { int i = randi()%1500 + 500, j; wu_bailout++; j = randi()%2000 * wu_bailout + 1500; for (;;) { compradores_report(); cputs("Elder Brother is aware of your plight,\r\n" "Taipan. He is willing to loan you an\r\n" "additional "); cprintulong(i); cputs(" if you will pay back\r\n"); cprintulong(j); cputs(". Are you willing"); cprint_taipan_prompt(); choice = agetc(); if(choice != 'y') { compradores_report(); cputs("Very well, Taipan, the game is over!\r\n"); under_attack_sound(); timed_getch(); final_stats(); } else { cash += i; debt += j; port_stats(); compradores_report(); cputs("Very well, Taipan. Good joss!!\r\n"); bad_joss_sound(); timed_getch(); return; } } } else if ((cash > 0) && (debt != 0)) { for (;;) { compradores_report(); how_much(); do_you_wish(); cputs("to repay\r\nhim? "); wu = get_num(); if(wu == UINT32_MAX) wu = cash; if(wu <= cash) { if(wu > debt) wu = debt; cash -= wu; debt -= wu; /* // currently debt is unsigned so the negative debt // bug (or feature) is unimplemented. if ((wu > debt) && (debt > 0)) debt -= (wu + 1); else debt -= wu; */ break; } else { you_only_have(0); } } } port_stats(); for (;;) { compradores_report(); how_much(); do_you_wish(); cputs("to \r\nborrow? "); wu = get_num(); // TODO: handle case where (cash * 2) would overflow! if(wu == UINT32_MAX) { wu = (cash * 2); } if((wu <= (cash * 2)) && !would_overflow(cash, wu)) { cash += wu; debt += wu; break; } else { cputs("\r\n\nHe won't loan you so much"); cprint_taipan_bang(); good_joss_sound(); timed_getch(); } } port_stats(); // break; /* do NOT let him steal the money back on the SAME TURN he loans it to you! */ return; } if((debt > 20000) && (cash > 0) && (one_chance_in(5))) { int num = randi()%3 + 1; cash = 0; port_stats(); compradores_report(); cprint_bad_joss(); cprintulong(num); cputs(" of your bodyguards have been killed\r\n" "by cutthroats and you have been robbed\r\n" "of all of your cash"); cprint_taipan_bangbang(); crlf(); under_attack_sound(); timed_getch(); } return; } void good_prices(void) { unsigned char i = randi()%4; compradores_report(); cprint_taipan(); cputs("!! The price of "); cputs(item[i]); cputs("\r\n has "); if(randi()&1) { price[i] *= (randi()%5 + 5); cputs("risen"); } else { price[i] /= 5; /* somehow general cargo dropped to 0 once. stop it. */ if(price[i] < 1) price[i] = 1; cputs("dropped"); } cputs(" to "); cprintulong(price[i]); cputs("!!\r\n"); good_joss_sound(); timed_getch(); } int port_choices(void) { char choice; char retire_ok = 0; compradores_report(); cprint_taipan_comma(); cputs("present prices per unit here are"); /* NB: exactly 40 cols */ // ===> free code space $0f7b (3936, 3.9K) // saves 46 bytes: for(choice = 0; choice < 4; ++choice) { gotox(3 + ((choice & 1) * 16)); if(choice == 3) cputs("General"); else cputs(item[choice]); cputc(':'); gotox(11 + ((choice & 1) * 18)); cprintulong(price[choice]); if(choice == 1) crlf(); } /* // original version: // ===> free code space $0f32 (3890, 3.8K) cputs(" Opium: Silk:\r\n"); cputs(" Arms: General:\r\n"); gotoxy(11, 19); cprintulong(price[0]); gotoxy(29, 19); cprintulong(price[1]); gotoxy(11, 20); cprintulong(price[2]); gotoxy(29, 20); cprintulong(price[3]); */ gotoxy(0, 22); clrtobot(); #ifdef BIGNUM if(port == 1) { /* // this speeds things up ever so slightly when cash is // low, but costs 68 bytes of code. Leave out for now. if(cash > 1000000L) { retire_ok = 1; } else if(big_cmp(bank, big1M) >= 0) { retire_ok = 1; } else { */ bignum(tmp); ulong_to_big(cash, tmp); big_add(tmp, tmp, bank); retire_ok = (big_cmp(tmp, big1M) >= 0); // if(big_cmp(tmp, big1M) >= 0) // retire_ok = 1; /* } */ } #else retire_ok = (port == 1 && ((cash + bank) >= 1000000L)); #endif cputs("Shall I Buy, Sell, "); if(port == 1) cputs("Visit bank, Transfer\r\ncargo, "); if(!retire_ok) cputs("or "); cputs("Quit trading"); if(retire_ok) cputs(", or Retire"); cputs("? "); for(;;) { choice = lcgetc(); if(choice == 'b' || choice == 's' || choice == 'q') break; if(port == 1) { if(retire_ok && choice == 'r') break; if(choice == 't' || choice == 'v') break; } } return choice; } /* TODO: rewrite in asm, or at least better C */ void name_firm(void) { unsigned char input, firmlen = 0; clrscr(); chlinexy(1, 7, 38); chlinexy(1, 16, 38); cvlinexy(0, 8, 8); cvlinexy(39, 8, 8); cputcxy(0, 7, 17); // upper left corner cputcxy(0, 16, 26); // lower left corner cputcxy(39, 7, 5); // upper right corner cputcxy(39, 16, 3); // lower right corner gotoxy(6, 9); cprint_taipan_comma(); gotoxy(2, 11); cputs("What will you name your"); gotoxy(6, 13); cputs("Firm:"); chlinexy(12, 14, 22); gotoxy(12, 13); while((input = agetc()) && (firmlen < 22)) { if(input == ENTER) { if(firmlen) break; else bad_joss_sound(); } else if(input == BKSP) { if(firmlen) { backspace(); firm[firmlen--] = '\0'; } } else { cputc(input); firm[firmlen++] = input; } } firm[firmlen] = '\0'; firmpos = 12 - firmlen / 2; return; } char what_do_you_wish_me_to(char buy_or_sell) { gotoxy(0, 22); clrtobot(); cputs("What "); do_you_wish(); cputs("me to "); cputs(buy_or_sell ? "buy" : "sell"); cprint_taipan_prompt(); return get_item(0); } void buy(void) { int choice; unsigned long afford, amount; choice = what_do_you_wish_me_to(1); for (;;) { gotoxy(31, 21); clrtobot(); afford = cash / price[choice]; revers(1); cputs(" You can "); revers(0); gotoxy(0, 22); how_much(); cputs(item[choice]); cputs(" shall"); gotoxy(31, 22); revers(1); cputs(" afford "); gotoxy(31, 23); cspaces(9); gotoxy(31, 23); if(afford < 100) cspace(); if(afford < 10000) cspace(); if(afford < 1000000) cspace(); if(afford < 100000000) cspace(); cprintulong(afford); revers(0); gotoxy(0, 23); cputs("I buy, Taipan: "); amount = get_num(); if(amount == UINT32_MAX) { amount = afford; } if(amount <= afford) { break; } } cash -= (amount * price[choice]); hold_[choice] += amount; hold -= amount; return; } void sell(void) { int choice; unsigned long amount; choice = what_do_you_wish_me_to(0); for (;;) { gotoxy(0, 22); clrtobot(); how_much(); cputs(item[choice]); cputs(" shall\r\nI sell, Taipan: "); amount = get_num(); if (amount == UINT32_MAX) { amount = hold_[choice]; } if(would_overflow(cash, amount * price[choice])) { too_much_cash(); continue; } if (hold_[choice] >= amount) { hold_[choice] -= amount; break; } } cash += (amount * price[choice]); hold += amount; return; } char would_overflow(unsigned long a, unsigned long b) { return ((UINT32_MAX - b) <= a); } void too_much_cash(void) { clear_msg_window(); cputs("\r\nYou cannot carry so much cash"); cprint_taipan_bang(); cputs("\r\nYour ship would sink under the weight\r\nof your riches.\r\n"); bad_joss_sound(); timed_getch(); } void visit_bank(void) { unsigned long amount = 0; #ifdef BIGNUM bignum(bigamt); bignum(biglimit); bignum(bigcash); #endif for (;;) { compradores_report(); how_much_will_you(); cputs("deposit? "); amount = get_num(); if (amount == UINT32_MAX) { amount = cash; } if (amount <= cash) { cash -= amount; #ifdef BIGNUM ulong_to_big(amount, bigamt); big_add(bank, bank, bigamt); #else bank += amount; #endif break; } else { you_only_have(0); } } port_stats(); for (;;) { compradores_report(); how_much_will_you(); cputs("withdraw? "); amount = get_num(); #ifdef BIGNUM if(amount == UINT32_MAX) { // memcpy(bigamt, bank, 6); big_copy(bigamt, bank); } else { ulong_to_big(amount, bigamt); } ulong_to_big(cash, bigcash); big_sub(biglimit, big_max_ulong, bigcash); if(big_cmp(bigamt, biglimit) >= 0) { too_much_cash(); continue; } if(big_cmp(bank, bigamt) < 0) { you_only_have(1); } else { big_sub(bank, bank, bigamt); big_add(bigcash, bigcash, bigamt); big_to_ulong(bigcash, &cash); break; } #else if (amount == UINT32_MAX) { amount = bank; } if (amount <= bank) { cash += amount; bank -= amount; break; } else { you_only_have(1); } #endif } port_stats(); return; } #ifdef BIGNUM_TEST void bignum_test(void) { int i; bignum(n); bignum(o); ulong_to_big(1L, n); ulong_to_big(11L, o); for(i = 0; i < 14; i++) { cprintfancy_big(n); cspace(); big_negate(n); cprintfancy_big(n); crlf(); big_mul(n, n, o); } agetc(); ulong_to_big(1100000L, n); cprintfancy_big(n); ulong_to_big(1010000L, n); cprintfancy_big(n); ulong_to_big(1001000L, n); cprintfancy_big(n); hangx: goto hangx; } #endif /* N.B. cc65 is perfectly OK with main(void), and it avoids warnings about argv/argc unused. */ int main(void) { int choice; #ifdef MCHENRY_TEST { while(1) { clrscr(); cputs("dmg? "); damage = get_num(); // cputs("\r\n"); crlf(); cputs("cap? "); capacity = get_num(); mchenry(); } } #endif atari_text_setup(); cursor(0); #ifdef BIGNUM_TEST bignum_test(); #endif name_firm(); initrand(); cash_or_guns(); set_prices(); for (;;) { choice = 0; port_stats(); if(wu_assassin) { wu_assassin = 0; compradores_report(); cprint_taipan_comma(); cputs("you have been assassinated!"); under_attack_sound(); timed_getch(); compradores_report(); cputs("As the masked figure plunges the blade\r\n" "into your heart, he says:\r\n"); timed_getch(); compradores_report(); cputs("Elder Brother Wu regrets to inform you\r\n" "that your account has been terminated\r\n" "with extreme prejudice."); timed_getch(); final_stats(); } if ((port == 1) && (li == 0) && (cash > 0)) li_yuen_extortion(); if ((port == 1) && (damage > 0)) mchenry(); if ((port == 1) && (debt >= 10000) && (wu_warn == 0)) { int braves = randi()%100 + 50; compradores_report(); cputs("Elder Brother Wu has sent "); cprintulong(braves); cputs(" braves\r\nto escort you to the Wu mansion"); cprint_taipan_period(); crlf(); timed_getch(); clear_msg_window(); cputs("Elder Brother Wu reminds you of the\r\n" "Confucian ideal of personal worthiness,\r\n" "and how this applies to paying one's\r\ndebts.\r\n"); timed_getch(); clear_msg_window(); cputs("He is reminded of a fabled barbarian\r\n" "who came to a bad end, after not caring\r\n" "for his obligations.\r\n\n" "He hopes no such fate awaits you, his\r\nfriend"); cprint_taipan_period(); crlf(); timed_getch(); wu_warn = 1; } if(port == 1) elder_brother_wu(); if(one_chance_in(4)) { if(one_chance_in(2)) new_ship(); else if (guns < 1000) new_gun(); } if((port != 1) && (one_chance_in(18)) && (hold_[0] > 0)) { // float fine = ((cash / 1.8) * ((float) randi() / RAND_MAX)) + 1; // the 1.8 is now a 2 unsigned long fine = 0; if(cash > 0) fine = randclamp(cash >> 1) + 1; hold += hold_[0]; hold_[0] = 0; cash -= fine; port_stats(); // fancy_numbers(fine, fancy_num); compradores_report(); // cputs("Bad Joss!!\r\n"); cprint_bad_joss(); cputs("The local authorities have seized your\r\n" "Opium cargo and have also fined you\r\n"); // cputs(fancy_num); cprintfancy(fine); cprint_taipan_bang(); // cputs("\r\n"); crlf(); timed_getch(); } if ((one_chance_in(50)) && ((hkw_[0] + hkw_[1] + hkw_[2] + hkw_[3]) > 0)) { int i; for (i = 0; i < 4; i++) { // hkw_[i] = ((hkw_[i] / 1.8) * ((float) randi() / RAND_MAX)); // the 1.8 is now a 2 hkw_[i] = randclamp(hkw_[i] >> 1); } port_stats(); compradores_report(); cputs("Messenger reports large theft\r\nfrom warehouse"); cprint_taipan_period(); // cputs("\r\n"); crlf(); timed_getch(); } if(one_chance_in(20)) { if (li > 0) li++; if (li == 4) li = 0; } if((port != 1) && (li == 0) && (!one_chance_in(4))) { compradores_report(); cputs("Li Yuen has sent a Lieutenant,\r\n" "Taipan. He says his admiral wishes\r\n" "to see you in Hong Kong, posthaste!\r\n"); bad_joss_sound(); timed_getch(); } if(one_chance_in(9)) good_prices(); if((cash > 25000) && (one_chance_in(20))) { // float robbed = ((cash / 1.4) * ((float) randi() / RAND_MAX)); // line below changes the 1.4 to 1.5 unsigned long robbed = randclamp((cash >> 2) + (cash >> 1)); cash -= robbed; port_stats(); compradores_report(); cprint_bad_joss(); cputs("You've been beaten up and\r\nrobbed of "); cprintfancy(robbed); cputs(" in cash"); cprint_taipan_bangbang(); crlf(); under_attack_sound(); timed_getch(); } for (;;) { while (choice != 'q') { switch (choice = port_choices()) { case 'b': buy(); break; case 's': sell(); break; case 'v': visit_bank(); break; case 't': transfer(); break; case 'r': retire(); } port_stats(); } choice = 0; if (hold >= 0) { quit(); break; } else { overload(); } } } return 0; }