Ok, so I spent a couple days reverse engineering Utopia. I now know pretty much how it all works, and now you can find out too.
Here's some highlights, in case you don't want to wade through the code:
- Do protect a 1 unit radius against rebels
- Will protect parked ships that have the same owner as the fort. Radius of 1 card.
- Will protect both players ships when they're under active control. Radius of 1 card.
- Do not otherwise contribute to your score
- Scoring will overflow, if you have more than 65 crops + fishing boats combined.
- It will also overflow if you earn more than 255 gold bars in a single turn. (Is that possible?)
- The "float off bottom of screen" bug is likely due to island/status collision detection interacting poorly with EXEC's boundary detection. The game can "nudge" a boat across the boundary the EXEC checks for. There is a check for this, but it only has been applied properly to the left/right edges.
- Delay loop in scoring display appear to want to update random number generator, but they don't actually.
- Code quality:
- Let's just say it's likely the priority was to have a compelling game, and have it out quickly, as opposed to writing the tightest, cleanest possible code.
- Pirates will never sail toward a parked PT boat on purpose. The "nudge" code, though, can nudge them through a tangle of PT boats.
- Hurricanes are 5 times more destructive than tropical storms. All three forms of weather water crops at the same rate, though.
- There's two copies of the "sinking fishing boat" animation in the ROM, due to how the EXEC distinguishes between background cards and MOB animations
- Each island has precisely 29 squares.
- When weather takes out something on your island, it will incur 0 to 101 casualties.
If you want more details, you'll have to dig through the code unless it's covered in the slightly more detailed review below. :-) Code attached.
The bulk of the main game code can be put into one of three categories -- The Timer Tick Task, Dispatches, Scoring Logic. The rest of the code is either initialization code, or support code for those three categories.
Timer Tick Task
The bulk of the game logic hangs off the the Timer Tick Task. This task runs 20 times a second, and it handles everything from spawning weather to collision detection to animating certain sinking ships. It's quite a lot of code and is fairly linear. The rough order of execution:
- Update the weather, possibly creating new weather
- Update the fish, possibly creating new schools of fish
- Update the pirates, possibly spawning new pirates
- Island vs. boat collision detection
- Update the game clock
- If not end of round / end of game:
- Update the status line at bottom of screen
- Update parked sinking ships
- At end-of-round, fire off the scoring code and then show the scores
- At end-of-game, show final score and halt
The next big set of code is the set of Dispatches. The EXEC mostly works by calling various functions in response to various events. Dispatches fall into a few categories:
- Controller input dispatches. The action-button and disc handlers are pretty simple. The keypad dispatch is amazingly convoluted, but that makes sense when you consider everything that it has to account for.
- Object vs. object dispatches. These handle pirates vs. PT boats and similar such things.
- Object vs. land dispatches. This is what handles rain on crops, pirates sinking parked ships, or parked ships going fishing.
The final big piece of code is the scoring logic. Scoring happens in four phases: Income Computation, Population Update, Round Score Calculation, Rebellion.
- Income Computation
- During the round, every gold bar you earn (say due to fishing, rain on crops, etc.) gets tallied in this round's Gross Domestic Product (Round GDP), separate from your actual gold bar balance. That is, spending does not subtract from Round GDP even though it lowers your treasury balance.
- At the end of the round, you get awarded additional gold as follows. Each of these contributes to the Round GDP except for the "baseline 10 bars."
- 4 gold bars per factory
- 1 gold bar per fishing boat
- Productivity bonus: ((Schools + Hospitals) * Factories) + Hospitals, clamped to a maximum of 30 gold bars.
- 10 gold bars of baseline income (does not contribute to the Round GDP).
- Population computation -- expressed as a growth rate, resulting in exponential growth.
- Fertility computation
- Start with a baseline fertility rate of 5.0%
- Increase fertility by 0.3% for every crop
- Increase fertility by 0.3% for every hospital
- Increase fertility by 0.1% for every house
- Decrease fertility by 0.3% for every school
- Clamp fertility to a minimum of 4.0%. You can't have fertility below 4% even if you filled the island with schools.
- Mortality computation
- Start with a baseline mortality rate of 1.1%
- Decrease mortality by 0.3% for every hospital, but not below 0.2%. (This limit is applied before the next step.)
- Increase mortality by 0.1% for every factory. If you fill the island with factories, your mortality rate will be 4.0%, matching the minimum allowed fertility.
- New population: Population + Population * Fertility - Population * Mortality. Maximum allowed population is limited to 9999.
- Fertility computation
- Round Score Calculation -- roughly, "approval rating", 0-100%
- First compute the following four subscores:
- Housing score: ((Houses * 500) / (Population / 100)) / 3. If larger than 30, clamp it to 30.
- Per-capita GDP score: ((Round GDP * 100) / (Population / 100)) / 12. If larger than 30, clamp it at 30.
- Food supply score: (((Fishing boats + Crops) * 500) / (Population / 100)) / 3. If this value is larger than 30, clamp it to 30.
- Note: 65 * 500 = 32500. So, if you have more than 65 fishing boats + crops, this score can go negative. This is fixable by changing the BLE at $5B1E to a BNC, I think, so it treats the overflow case as a case that needs to clamp to 30.
- General welfare score: 1 point for every school or hospital
- Add up the four subscores, limiting the total to 100 or less. That's the per-round score.
- First compute the following four subscores:
- Compare this round's score to the previous round, and consider it in absolute terms as well
- If it dropped by more than 10 points or is below 30 points, add a rebel
- If it increased by more than 10 points or is above 70 points, remove a rebel
If you want to see the exact details of how these pieces work, look at the code. For example, in the scoring section, some divides are rounding divides, and some are truncating divides. The population computation is actually carried out with scaled arithmetic (ie. fertility/mortality rates are multiplied by 10, and population divided by 10 when computing numbers of births and deaths.)
For anything else... see the code!
[Edited to fix some formatting issues in the nested bulleted lists.]
Edited by intvnut, Fri Aug 3, 2018 3:04 AM.