How was randomness achieved in older video games?

From Crayola’s “wheat” brown plasticine. :slight_smile:

According to the data sheet, all of the bits in the counter are initialized to 1.

I made games on the TRS 80 color computer in the early 1980’s. For randomness I used a pseudorandom number generator algorithm that I found somewhere (don’t remember where).

To seed it I used memory locations that were not consistently initialized, some were connected to IO (D/A converter, etc.). Basically it was trial and error to find good sources for the seed.

The behaviour that you describe is rather specific to the Linux implementation. The Wikipedia article on /dev/random describes a number of *nix implementations; FreeBSD, OpenBSD, OSX and AIX all implement a non-blocking /dev/random.

The vast, vast majority of older video games use player input at least somewhere in the system. That’s why they will play back exactly the same if you watch tool assisted speed runs. The only thing they record are the button presses and their exact timing.

In fact, there’s a specific process called RNG manipulation that you are encouraged to use. The TASvideos website has a primer on it.

And, yes, it’s also a part of speedruns made by real people, too, though it’s much harder to initiate.

I see what you’re doing there!

Obviously different games are different, but I’ll throw out the process for Doom: the game contained a file listing 256 numbers between 0 and 255 in a scrambled, but fixed, order. It’s also worth noting that the list didn’t quite contain every number once, 145 appears 5 times, 1 doesn’t appear at all. The game would always call for random events at the start of the list and go through it, wrapping around when it was done.

Thus, the game wasn’t truly “random” - while there’s usually enough happening at once that the player usually won’t notice, sometimes they will. For example, if you load a saved game where nothing else is happening, you’ll notice the first shot of your weapon always does the same amount of “randomized” damage (since it always calls from the same spot on the list). It also means that certain value ranges are less than they might be - for example, the BFG 9000 works by shooting out rays that each individually deal 16d8 damage (16 rolls of 1-8 damage), so theoretically the weapon could do between 16 and 128 damage, but in reality due to the list it can only do between 49 and 87 per ray.

Most computers, even the early 8-bit ones (ATARI, PET, APPLE ][, TRS-80) function to generate a random number in basic. It could get a random number from things like reading the milliseconds of the system clock (or as others mention, screen refresh, or any other internal counter).It’s pretty much impossible to time a keypress to the same 60th of a second, or millisecond of a seconds, every time - and by using that as a seed, the RND() function would give you a very different sequence of random for even very close seeds.

I vaguely recall reading some discussions back then that sme random generators had biases for certain numbers over others, i.e. something like .881 was far more likely to occur than .634 and such, so writing long programs like Monte Carlo simulations risked getting warped results.

We know this because id Software released the source code to classic DOOM in 1997, and we can read it online.

id Software actually makes a habit of open-sourcing their old game engines. Here’s their official Github page. They’ve open-sourced up through the engine used for Doom 3.

Here’s what happens when you replace every value in that array with a single number:

More things become a bit wonky, as you can read in the rest of the blog post.

I learned a bit of programming back in computing’s cretaceous, and was taught a little trick for getting randomer random numbers.

First the problem: when generating random numbers in Basic, you needed a “random number seed”. If you put in the same seed, you would get the same sequence of random numbers. So you needed a way to randomize the seed.

The simple solution was to put a message on the screen at the start that said “press any key to continue”, then follow that with a FOR NEXT loop. After the loop, a command that would tell it to start the loop again, inside the loop is an INKEY command that tells it to leave the loop if any key is pressed.

Then a command to set the Random Number Seed to whatever value the loop was at.

So, the computer is counting from 1 to 10,000 (say) while waiting for a key to be pressed. If no key is pressed and it reaches 10,000, it will start over at 1. Since the computer counts VERY fast, it is unlikely that someone will hit a key at the exact same time twice.

It does mean that, in a game dependent on random numbers, there would only be 10,000 possible games. But most people only played a given game a few hundred times anyway (and we usually set the loop to count from -32,000 to +32,000, so there were 64,000 possible outcomes).

Oddly, the first computer I learned Basic on (a TRS-80 Model I) didn’t require a random number seed, so I learned a bad habit there and was quite surprised when a program I wrote in GW-Basic (on a PC-compatible) generate the same “random” numbers every time.

I learned a lot of programming on the TRS-80 model 1. I always found it kinda funny that an el-cheapo cassette recorder and el-cheapo tapes worked much better than the special computer cassette deck and computer tapes that they sold at the time.

The TRS-80 was based on the z80 processor, and that processor had a register in it called the DRAM Refresh Register (often just referred to as the refresh register, or the R register). The refresh register incremented every time the z80 fetched an opcode. Since the computer would be sitting and spinning in a loop waiting for a user to enter a command to start up a program, by the time you got a program running, the R register was basically a random number.

Tandy Basic used the R register to generate the seed for its pseudorandom sequence, and a lot of other z80 based systems used the R register as well. Depending on what you were doing, you didn’t necessarily need a pseudorandom sequence. If you fetched a random value fairly infrequently, often you could just use whatever was in the R register at the time as your random number.

So there’s yet another different method used to generate random numbers on old 8 bit machines.

I’ve heard that the original Diablo seeded its random numbers with the seconds (not milliseconds) of the system clock at the time it started up. This led to speedrunners “mining” for particularly favorable random map layouts, and then setting the system clock to those values to do their runs.

Another use for pseudorandom number generators is for when you want things to be repeatable. For instance, in Windows Freecell, if you bring up a particular puzzle number, it’ll always give you the exact same deal. They didn’t store 64k different deals somewhere to look up; rather, they just use the puzzle number as the seed to shuffle the deck.

Why sdmb is so awesome: I thought the op was a stupid question but kept reading for the sure-to-come snark or pun festivals. Instead we have this delightfully educational and fascinating discussion!

I’ve played a lot of computer games in my life but the only one that really made me feel the existence of the rng was nethack. I wasn’t alone. In discussion boards players got into the habit of referring to it as a god. Like rng= random number God. It felt that there would almost always come a time in your adventure when something terrific happened to you and you thought you could skate home, but then a cascade of bad luck would descend, leaving you limping home in shreds - if you survived at all. I never could get far in the game so spent over a decade playing it in wizard (I.e. Cheat) mode and still never could win. It seemed that the game knew I was in wiz mode so would throw the full temper of the rng at me until I died.

Anybody here dig into the nethack code enough to figure out how it’s rng worked? Was the bad luck and temper tantrum effect just pure random number generator behavior or was it skewed to lean hard to one range of numbers when the user did certain things?

It hasn’t really occurred to me in a long time how strange and counter-intuitive it is that generating good random numbers is a non-trivial problem.

I think it’s purely random. I know once upon a time someone ascended 20 times in a row or something just to show it was possible. I think he estimated that there are only 2 truly impossible games you can generate.

I first programmed BASIC on a TRS-80. As I recall in Level I BASIC you only had the RND(0) command which returned a random non-interger number between 0.0 and 1 (0.2342 etc.) so you had to combine it into:


**X=INT(RND(0)*A)+1**

to get a random number X in the range between zero and A. Level II BASIC simplified it to just:


**X=RND(A)**


But I think Level II also required you to first use the RANDOM command to seed the generator whereas Level I did not.

Minor nitpick: That would generate an integer value from 1 to A, inclusive.

The Commodore 64 had the same syntax for generating random numbers, but the number in between the parenthesis in RND(), whether it was a positive value, a zero, or a negative value, would have different effects on how the random number was generated.

When RND(0) = 1, wouldn’t X = A+1?

Why isn’t the interval from 1 to A+1?

The values generated from the RND function are [0.0,1.0). Or 0.0, inclusive, to 1.0, exclusive, so you cannot get a value of 1. ETA: At least this is the case with the C64 and I seem to recall it holding true with other early 80s computers in BASIC.

Minor point, it looks like in TRS-80 BASIC, RND is not inclusive at either bound, so the floating point number, r, generated is 0 < r < 1.

Is that actual proof of the actual results of RND () ?
Perhaps they were just talking as accurate as needed, but not totally accurately ?