Install Steam
login
|
language
简体中文 (Simplified Chinese)
繁體中文 (Traditional Chinese)
日本語 (Japanese)
한국어 (Korean)
ไทย (Thai)
Български (Bulgarian)
Čeština (Czech)
Dansk (Danish)
Deutsch (German)
Español - España (Spanish - Spain)
Español - Latinoamérica (Spanish - Latin America)
Ελληνικά (Greek)
Français (French)
Italiano (Italian)
Bahasa Indonesia (Indonesian)
Magyar (Hungarian)
Nederlands (Dutch)
Norsk (Norwegian)
Polski (Polish)
Português (Portuguese - Portugal)
Português - Brasil (Portuguese - Brazil)
Română (Romanian)
Русский (Russian)
Suomi (Finnish)
Svenska (Swedish)
Türkçe (Turkish)
Tiếng Việt (Vietnamese)
Українська (Ukrainian)
Report a translation problem
RNG comes from the term pseudo-random number generation. No game is actually random, it only pretends to be - it is pseudo-random. All programmatic RNG is fully deterministic. Given the same inputs, it produces the same outputs. Typically you generate some seed, then feed the RNG's own output back into the RNG as the next seed, and so on. If the RNG isn't trash, it will produce a sequence that looks random, is plausible as a random sequence, but it's not random given it's 100% reproducible. Given any particular seed, the RNG will produce exactly the same sequence of numbers as an output. This is how Binding of Isaac seeds let you replay the same dungeon, and how Minecraft seeds let you play on someone else's world without having to send a massive save file.
Though admittedly some get fancy and use the microsecond(s) of your system time as one of its inputs, which, combined with quantum jitter in the human nervous system, amounts to true randomness.
Later Civilization games use seeding, to discourage the player from savescumming until they always win their fights. In CIV (and also DQ3) you can burn bad rolls on unimportant actions, though. E.g. if you're sieging a city, you can let the bad rolls kill some cheap, obsolete unit.
DD may even use the modern timestamp-dependent RNG, but it rolls in advance and saves a set of them. Probably takes less than a second to roll ten times as much as you need in a quest. Alternatively, each mission has a seed which is saved. Copying and replacing save files every mission is even less fun than tedious stall tactics, and Red Hook decided to make sure nobody practices it.
The wagon is even less random, BTW. I believe there's a pre-determined deck of classes, and it simply shuffles the deck and deals it to you through the wagon. Alternatively there's some randomness in the proportion of classes in the deck, but it checks in advance that you're not getting too screwed with any of the classes.
In other words if it seems like the wagon likes to give you houndmasters, that's because it does. The deck has many houndmasters in it.
certainly not predetermined. Seeded ? maybe, haven't tried it.
Obviously recreational games don't need this and you'd generally only notice the "pseudo" in the PRNG if you're trying some stunt like scumming.
Good point but as with most things in this game, when you avoid one specific problem you often create others. "Speed teams" are a valid strategy, but you're almost always buying into a situation where your guys go first and "clump up" and if you somehow whiff, the enemy all goes at the same time. This can be problematic as the faster characters (like Grave Robber) are very susceptible to one hit-DD, two hit kills. (At the same time those very characters like GR are very good at single-handedly eliminating an enemy before it can even do anything.)
So I generally think it's preferable to have a range of speeds. Healers are an obvious example of a character that can usually cope with being slow. Them acting last means you can finish off a round with either another attack or a heal. It's often a good strategy to have the backup healer at the opposite end of the speed spectrum.
I had this epiphany that I could parse a log file for that seed, launch my own local game, and replay the entire game up to any point, exactly as both A and B saw it happen. But my copy is running in the VC++ debugger, and I can single-step through the source and examine variables and the call stack, right up to the point of a crash or error.
Inevitably, when a bug in the code causes one game copy to consume 1 more random number than the other, that's the dreaded Out of Sync (OOS) error. I even found ways to kill off 1 tainted guy without generating any random numbers (stand him in lava), which "cures" certain OOSes. Probably every other network game that complains about OOS-type errors has the same root flaw of mismatched consumption of pseudo-RNGs.
~~~~~~~~
Later, I even codified this experience into some Laws of Game Dev:
0. Use a pseudo-random number generator, so that you can duplicate the sequence of pseudo-RNG calls. Reproducibility enables post-mortem debugging.
1. Write the Log Viewer First.
Don't make me rip out your 2-lane I-90 from Seattle to Boston to put in a 6-lane Log Viewer after the fact. Easier to just start with it in the first place. (Deep Corollary: the game dev company that does start with it is likely to be the one that persists.)
2. Promote your log-round-tripping data into first-class values.
2.a) Every datum is a form of serialization, i.e. saving a piece of game state to an external persistent format (in this case, a text file, but in principle it could be binary) for the purpose of reading it back in. Serialization is a write-(years pass)-read cycle.
One datum could be 1 attack swing, which consumes 1 random number.
It gets logged as a string such as "2 of 5: Alice attacks Bob but misses [96]".
Thus, you need to both write that string, and read it back in.
2.b) For each datum, create 1 (sub)class D of a master LogDatum class.
D implements both the writer (as operator<<(ostream &, D const &)),
and the reader (as operator>>(istream &, D &)).
Textually, put these two adjacent, so that you can single-screen them both.
This alone will save you hours and hours of effort. It sucks beyond words to have two highly correlated (in fact, lockstep-mirror-imaged) pieces of code be widely scattered in 2 (or more) different source files. It's infinitely cleaner to make them be part of the same code, in the same place.
Also, the code itself gets unbelievably beautiful, because it's so terse.
The game code now prints a D like this: ostream o; o << d;
And the log viewer reads it like this: istream i; D d; i >> d;
This reduces every piece of log data to the same mental footprint as an integer.
It doesn't get simpler than that.
~~~~~~~~
Log Viewer worked so well that it basically put itself out of business. Within ~1 year, I caught a couple hundred crash bugs and fixed them all, including some mythical legendary bugs spoken only in whispers since the dawn of the game. Thereafter, there were no more crash bugs. The entire category of solvable-by-Log Viewer bugs basically collapsed to near 0.
Now imagine a game that has Log Viewer from day 1, and sidesteps all of those bugs in the first place, because they're caught in-house during dev. That's actually the normal case we all expect, right? And so must it ever be.
All of this rests on the foundation that pseudo-random number generators are deterministic. Determinism enables debugging.