Inspired by Aaron A. Reed's wonderful project exploring 50 years of text games I set out to make a port of the the educational game The Oregon Trail. Initially written in late 1971 by Don Rawitsch, Bill Heinemann, and Paul Dillenberg in only two weeks, the program would go on to become "probably the best-known piece of educational software ever written", in Reed's words.
The text based game simulates the struggles of early settlers to journey from Independence, Missouri to Oregon City, Oregon in 1847, a trip of over 2,000 miles. Players, students in this case, would have to make decisions about how much money to budget for oxen to pull a wagon, food, clothing, ammunition, and supplies. A series of random events provided challenges of inclement weather, broken wagons, getting lost, or being attacked by raiders – all of which reduced the dwindling inventory that the students had to manage.
If the players traversed the entire journey of 2,040 miles they would be rewarded with a short note from President James K. Polk who congratulated them on the perseverance and wished them well in their new life.
The program was initially written in the BASIC language for the HP 2100 minicomputer. The earliest known copy of the source code, which Reed links to in his article, is from 1975. This was the version I used to make my port to Python 3.9. My goal was to create an accurate representation of the logic and display for the game. You can view my code on github or repl.it.
I made two changes:
- I used the term Native American, where the original code contains text using a derogatory term for the indigenous people of North America.
- I added a dashed line to show a visual break for each new turn.
Porting a program, any program, forces the developer to get intimately familiar with the original source code and the originating language, just as translating a book, say from Japanese to English, requires the translator to have a deep understanding of the original material and the initial language. I thought it would be fun to read through the BASIC code and reminisce about the many times I played this game as an elementary school student myself and the numerous hours I spent writing BASIC code in my youth. And while I had fun with this project, I didn't expect to be surprised by a clever implementation for how random events occur along the journey.
Below is the block of BASIC code from the 1975 version that is the logic for picking a random event.
This block of code randomly selects one of 16 events that happen each turn to present to the player. Given the rather basic use of the pseudo-random function used throughout the game, I expected to see a simple call to pick a number between 1 and 16 and then display the corresponding event and apply the benefits or side effect of the event to the player's inventory.
Instead, the developers choose to use the exact minute of the hour as the base of their random number selection and pick a number from that base up to and including 100. This number was then compared to a set list of numbers, one at a time until the randomly selected number was lower of the two. The effect of this approach means that the later into the hour a player played the game, resulting in a higher minute of the hour, the higher the event number selected "at random" would be. The sixteen events tended to be harder the higher the event number. Therefore, playing The Oregon Trail at 3:05pm could be easier than playing the game at 3:45pm.
I can't know the reason for sure that the developers implemented this logic, however, I suspect they were aiming for increased variability in the game from play to play. Below I will step through the code line by line, explaining the logic of this code block.
Each line of code begins with a line number, written in increasing valued sequence, as is common practice for the BASIC language. The first line of the block we are looking at begins at line number 2500.
The function of this code is to simply set a variable, D1, to the value of 0. D1 acts as a counter and will determine which event is presented to the player. In this version of BASIC variables are limited to a letter followed by a $ if it represents a string, for example N$. Numbers can be represented by a single letter variable or a letter followed by a single number, as we see here, D1.
RESTORE is the BASIC command that resets an internal pointer that keeps track of which element in a list of DATA elements is currently being pointed to. When executing the RESTORE command the result is to ensure that the next call to the DATA command will point to the first element in the list. We see the companion DATA line a few lines into the code at line number 2535.
Here the code is assigning a value to the variable R1, the result of choosing a random number, RND, between the current minutes of the hour, TIM(0), and 100. As an example, if this line of code is run at 8:37PM, R1 will be assigned a random value between 37-100.
This next line simply adds the value of 1 to the current value of D1, acting as an incriminator. Remember, D1 will be which event of the 16 possible options is selected.
Line 2520 is a conditional check to see if the event counter, D1, now equals 16. If it does, the program jumps to line 3020 and continues to execute from there. You'll recall that there are 16 unique events that can occur as part of a turn and item number 16 is triggered if our counter reaches the value of 16. In this case, it is a benefit to the settlers, who have encountered some helpful Native Americans who provide them with some additional food resources. If D1 does not equal 16, our next line is executed.
The READ statement interacts with our DATA line located at 2535, just as the RESTORE command did previously. In this case READ assigns a value to the variable D based on which element from the DATA list the internal pointer is pointing to. Because the RESTORE command was run just moments ago, we are assured the first time we encounter this line of code, D will be assigned the first value in the list, which is 6. Then the READ command advances the internal pointer to point to the next element in the DATA list.
Now we encounter another conditional check, this time testing the randomly selected value assigned to the variable R1 to see if it is greater in value than that which was just assigned to the variable D in the last line. If R1 is indeed greater, the program now returns to a line we have already discussed, line 2515. The effect is that we loop back to the place in the code that increments our event counter, D1.
This sequence will repeat itself until one of the two conditional checks we reviewed become true. Either the counter D1 increments until it equals 16 or we keep selecting new values from the DATA line until we finally have one that is greater than the value of R1
This line of code defines the list of numbers to step through as we call the READ function. When we encounter this line of code during execution, nothing happens, as this line is used by the RESTORE and READ functions, as we've covered.
I'm going to cover these next three lines together, the last in the block we are examining. The first line, line 2537, is a conditional check that evaluates to either TRUE or FALSE based on the value of D1. If D1 is less than or equal to 10 it will evaluate to FALSE and the program will execute the next line in the sequence, line 2540. If D1 is greater than 10 it will evaluate to TRUE and the program, directed by the THEN command, will jump to line 2545.
Line 2540 is a conditional statement of a different form. Based on the value of the variable D, the program will jump, GOTO, the corresponding line number represented in the list. For example, if D equals 3, the program would jump to the third element in the list following the keyword OF, which is line 2590. Another way to describe this is that the program will GOTO the Dth element in the list provided.
Line 2545 is similar to line 2540, simply evaluating the equation of D-10. When using GOTO in this fashion, BASIC limits the value of the variable from 1 to 9. Here we are subtracting 10 from the variable D1 to determine which element in the list the program should GOTO, because we already handled the cases where D was less than 10 previously.
The results of lines 2540 or line 2545 correspond to the 16 events that can happen during a player's turn. Factually, we are covering events from 1-15, since event 16 was handled by the conditional check on line 2520.
After this, the code continues on to check if the players has travelled far enough in the journey to hit the Rocky Mountains, where some other events can happen.
Let's look at the effects of this code versus the simple approach of picking a random event from 1-16. In the simple approach, each event would have an equally random chance of being selected, 1 divided by 16, or 6.25% of selecting any of the 16 numbers.
The code we just outlined greatly varies this depending on the current number of minutes past the hour. Below is a chart showing 4 samples: the top of the hour (0 mins), and then three points past the hour, 30 minutes, 45 minutes, and 59 minutes.
I've listed the number range for each event that R1 would need to be within to trigger that event in column 2. Columns 3-7 show the total number of values that R1 could equal, for example to trigger event 6 at the top of the hour (0 minutes) R1 would need to equal any of 5 values: 18, 19, 20, 21, or 22.
Columns 8-12 show the corresponding percentages for how likely an event is to occur given the column heading number of minutes past the hour. Compared to the simple approach of selecting one event with an even 6.25% occurrence this charts shows how the developers increased variability and how the likelihood increases for higher number events the later into the hour the player plays.
The Oregon Trail will continue to be remembered for its groundbreaking educational impact and showcasing the immersive power of storytelling and text based games. I will also remember it for reminding me of the value of looking deeper into the code for hidden surprises like this short function to pick random events. I hope this may also be inspiration for you, too, to take a peek at some source code and see what surprises you might encounter. Let me hear what you discover.