From 7ac76fb4e4117887f6fd80ca7d6659be88209313 Mon Sep 17 00:00:00 2001 From: "B. Watson" Date: Wed, 6 Jan 2016 17:38:48 -0500 Subject: finishing touches of compression, attempt to fix damage calc issue --- README.txt | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ comptitle.s.in | 1 + taipan.c | 50 +++++++++++++++++++++++++++++++++++--------- titlecompression.txt | 21 +++++++++++++++++++ 4 files changed, 120 insertions(+), 10 deletions(-) diff --git a/README.txt b/README.txt index 9c8f1f7..bf03aab 100644 --- a/README.txt +++ b/README.txt @@ -215,6 +215,64 @@ I've made a few changes to the UI, compared to the Apple version: Other things that need doing to the code: +- Decide what to do about integer overflow. Possibilities: + + - Use a "bigint" library (e.g. 64-bit ints). Would still be + possible to overflow, but it would take a really determined + player. Disadvantage: slow. Maxes out at: + 40-bit: 1,099,511,627,776 (1 trillion) + 48-bit: 281,474,976,710,656 (281 trillion) + 64-bit: 1.84467440737096e+19 (beaucoup!) + + - Use the ROM floating point routines. Nobody's likely to ever + overflow them. But, would have to write wrapper code to call + them from C and convert longs to floats and back. And it'll + be slower than bigints even. Maxes out at 1.0e+97 + + - Use packed BCD (base 100) like the FP ROM, but as an integer (no + magnitude). Maxes out at: + 6 bytes: 1,000,000,000,000 (1 trillion) + 7 bytes: 100,000,000,000,000 (100 trillion) + 8 bytes: 10,000,000,000,000,000 (10 quadrillion) + Advantage: as above, but more so: "1.2 million" gets even easier + to calculate. Math would be faster than FP, slower than 64-bit ints, + but conversion to/from longs would be slower. + + - Use a hybrid data format: one long for the bottom 5 decimal digits, + range 0 to 99,9999 (value % 100000) and the other for the high part + (value / 100000). Advantage: the game prints strings like "1.2 + million", this would be faster/easier than a regular bigint. + Maxes out at 429,496,729,600,000 (429 trillion). + + - Leave it as-is. Obviously the easiest option, but not very satisfying. + Maxes out at a measly 4.2 billion. + + Whatever format we pick, we'll run into limitations. The "Cash" area + of the display is only so wide, we can only fill it with so many + characters. At some point, we need artificial limits: + + - If your debt maxes out: "Taipan, you have been assassinated!" and + the game is over. + + - If the bank maxes out, stop calculating interest. On deposit, + "Taipan, the bank's coffers are full!" + + - If cash maxes out, forcibly retire the player. "Taipan, you are now + so rich that you own the entire continent of Asia!" or maybe "Taipan, + your ship has sunk under the weight of your massive fortune!" + + Tricky part about these limits is checking for overflow without + actually overflowing. E.g. instead of "cash += amount" we have to + write "if(cash + amount < cash) overflow(); else cash += amount". + Also, backing out of the call tree will be a PITA. longjmp() anyone? + cc65's implementation seems to work OK. If I use ROM floats, I + won't worry about this at all. + + For display, "million" can become "billion" if needed... then "trillion" + but without a space in front ("1.2trillion"). We have enough room for + 4 digits there, 9999trillion would be the max. Or, abbreviate "billion" + as "bil", allowing 4 more digits. "99999999 bil" would be 99 quadrillion. + - Size optimization. Right now, the executable is almost 27K of code. I'd like it to at least fit on a 16K cartridge. A lot of the C code is redundant, and some things can be rewritten in asm if need be. I've diff --git a/comptitle.s.in b/comptitle.s.in index b8ed688..824cded 100644 --- a/comptitle.s.in +++ b/comptitle.s.in @@ -39,6 +39,7 @@ table: ; decompression code starts here init: lda #0 + sta SDMCTL ; turn off the screen. newtitle.s turns it back on. ;;; for benchmarking only, remove! ;sta RTCLOK diff --git a/taipan.c b/taipan.c index af755e4..f3f0c87 100644 --- a/taipan.c +++ b/taipan.c @@ -18,6 +18,10 @@ damage and capacity numbers directly */ // #define MCHENRY_TEST +/* define this to start the game in the year 1869, with + 1000 capacity, 20 guns, and 1 billion cash and bank. */ +// #define TIMEWARP + /**** atari-specific stuff */ /* values returned by cgetc() for backspace & enter keys */ @@ -223,10 +227,10 @@ int hkw_[4], hold_[4]; int hold = 0, - capacity = 60, + // capacity = 60, guns = 0, bp = 0, - damage = 0, + // damage = 0, month = 1, year = 1860, li = 0, @@ -234,13 +238,15 @@ int hold = 0, wu_warn = 0, wu_bailout = 0; -int newdamage; +// these need to be longs to avoid int overflow when +// displaying ship status. +long damage = 0, capacity = 60, newdamage; // fancy_numbers() will get replaced sooner or later. // void cprintfancy(unsigned long ul) { // } -/* print an int or long as a string, conio-style */ +/* print an int or long as a string, conio-style */ void cprintulong(unsigned long ul) { cputs(ultoa(ul, fancy_num, 10)); } @@ -514,7 +520,7 @@ int sea_battle(int id, int num_ships) { fight_stats(num_ships, orders); while(num_ships > 0) { - status = 100 - ((damage * 100 / capacity)); + status = 100L - ((damage * 100L / capacity)); if(status <= 0) { return 4; } @@ -932,8 +938,8 @@ int sea_battle(int id, int num_ships) { // if ((guns > 0) && ((randi()%100 < (((float) damage / capacity) * 100)) || // ((((float) damage / capacity) * 100) > 80))) - if((guns > 0) && ((randi()%100 < ((damage * 100) / capacity)) || - (((damage * 100) / capacity)) > 80)) + if((guns > 0) && ((randi()%100 < ((damage * 100L) / capacity)) || + (((damage * 100L) / capacity)) > 80)) { i = 1; guns--; @@ -966,8 +972,18 @@ int sea_battle(int id, int num_ships) { 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! */ + #ifdef DAMAGE_TEST gotoxy(0, 23); + clrtoeol(); cprintulong(ed); cputc(' '); cprintulong(i); @@ -1169,12 +1185,26 @@ void cash_or_guns(void) li = 0; bp = 10; } else { +#ifdef TIMEWARP + year = 1869; + cash = 1000000000L; + bank = 1000000000L; + 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 } return; @@ -1194,7 +1224,7 @@ void port_stats(void) { static int firmpos = 0; int i, in_use, - status = 100 - ((damage * 100) / capacity); + 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 @@ -1381,7 +1411,7 @@ void mchenry(void) static int percent, time; static long br, repair_price, amount; // int percent = ((float) damage / capacity) * 100, - percent = (damage * 100 / capacity); + percent = (damage * 100L / capacity); time = ((year - 1860) * 12) + month; /* @@ -1836,7 +1866,7 @@ void quit(void) // 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 * 100 / capacity; + damagepct = damage * 100L / capacity; if(damagepct < 34) sunk = 0; else if(damagepct < 67) diff --git a/titlecompression.txt b/titlecompression.txt index 5f6bf3f..9c30232 100644 --- a/titlecompression.txt +++ b/titlecompression.txt @@ -22,6 +22,27 @@ likely to have large areas of all 0 bytes, but e.g. it can't compress an ASCII or ATASCII text file at all (because there are pretty much never any null bytes in a human-readable text file). +Apologia: +--------- + +I'm aware that cc65 has a zlib implementation, but it doesn't quite meet +the requirements: it's slow, large (linking it adds 781 bytes to the +executable size), and the best DEFLATE compression I could manage for +my title screen was 2806 bytes (using 7z). Add to that the 781 bytes of +zlib code and the other required libs for a C program, and I end up with +something like a 65% compression ratio (since I'm counting the code size +as part of the file size), and it takes 2/3 of a second to decompress. So, +it meets one of my requirements, but not all of them. Which should not be +taken as a criticism of cc65's zlib implementation: It's amazingly small, +efficient, and easy to use. It's just that deflate is a general-purpose +compression algorithm, and I thought I could do a better job with a +purpose-built one... which I think I've done. + +I didn't know exomizer existed, before I started on my compression scheme. +If I'd known, and if I'd tried it out, I probably would have just used +it instead. It has a better compression ratio (55% for my title screen, +including decomp code) and decompresses fast enough. + Theory of operation: -------------------- -- cgit v1.2.3