The real open source was in the 80s, when magazines would print the game for you to type at home. They even developed typing assistants that could checksum every line for typos.

The real open source was in the 80s, when magazines would print the game for you to type at home. They even developed typing assistants that could checksum every line for typos.

I asked ChatGPT agent to turn program 1 from that mag into an executable python program. Here’s the code. It works:
"""
horse_racing_ti.py
===================
This module implements a command‑line horse racing game inspired by
"Program 1: TI Horse Racing" from the October 1984 issue of *Compute!*
magazine. The original program was written in TI Extended BASIC and used
simple sprite animation to race horses across the screen. Players could
wager on the outcome using win, place, show, exacta (called "exactor" in
the magazine) or quinella bets and watch the race unfold.
The implementation here reimagines those mechanics for a modern Python
environment. It keeps the core ideas and payouts from the original
source while simplifying some of the visual aspects. Each race features
a random number of horses (between 4 and 12) with randomly chosen names
and odds. The player begins with $1000 and participates in up to nine
races, placing bets on their chosen horse(s) each time. After all
horses have finished, the program computes the payout based on the bet
type and odds, updates the player's bankroll and optionally records a
new high score. At the end of the session, the player can choose to
start over or exit.
Key features
------------
* **Random race composition:** Each race has between 4 and 12 horses.
Horse names are drawn from a list (no duplicates) and odds are
randomly generated to fall between 1 and 10 as in the TI code.
* **Flexible betting:** The player may place one of five types of bets:
win (horse must finish first), place (finish in the top two), show
(finish in the top three), exacta (pick the top two finishers in
order) or quinella (pick the top two in any order). Wagers are
validated against the player’s current money.
* **Race simulation:** Instead of animating sprites, the simulation
assigns each horse a random performance time derived from the sum of
several random steps. Faster times finish earlier; ties are broken
randomly. A simple progress indicator prints the name of each
participating horse along with its "distance" after each tick to add
suspense.
* **Authentic payouts:** Payouts mirror the logic in the original
Extended BASIC code. Win bets pay the full odds, place bets pay half
the odds, show bets pay a third of the odds, exacta bets pay the
combined odds of the two chosen horses, and quinella bets pay half of
that combined value.
This script is self‑contained and does not require external modules
outside of the Python standard library. To play the game, run the
module directly with Python 3.
Example::
$ python3 horse_racing_ti.py
Copyright © 2025
Distributed under the MIT license.
"""
import random
import os
import json
class HorseRacingGame:
"""Encapsulates the horse racing game logic."""
# List of horse names (mirrors those from the original DATA lines)
HORSE_NAMES = [
"SUNNY EASTER", "WAY TO GO", "HORSE", "MISS CLEM", "GIVE ME ACTION",
"I'M IN FRONT", "MYSTIC LIGHTNING", "SUPERCHARGED", "INDIAN PRINCESS",
"HOLD ON TIGHT", "SPEED DEMON", "LITTLE DEVIL", "GENESIS", "ACER",
"MR ARNESON", "ON TOP", "SLOWPOKE", "CUREDENT", "JUGGERNAUT",
"GUSTAFU"
]
def __init__(self, bankroll: int = 1000, num_races: int = 9):
self.bankroll = bankroll
self.initial_bankroll = bankroll
self.num_races = num_races
self.current_race = 1
# Record file for high scores (in the same directory as script)
self.record_file = os.path.join(os.path.dirname(__file__), "horse_racing_record.json")
self.high_score, self.high_scorer = self._load_record()
def _load_record(self):
"""Load the high score record from disk.
Returns a tuple (score, name). If no record exists, returns
(0, "")."""
try:
with open(self.record_file, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("score", 0), data.get("name", "")
except FileNotFoundError:
return 0, ""
except Exception:
# If the file is corrupted, ignore it.
return 0, ""
def _save_record(self, score: int, name: str) -> None:
"""Save a new high score to disk."""
data = {"score": score, "name": name[:20]}
with open(self.record_file, "w", encoding="utf-8") as f:
json.dump(data, f)
def _choose_horses(self) -> list:
"""Randomly select a subset of horse names for the current race.
The number of horses is uniformly chosen between 4 and 12
inclusive. Names are chosen without replacement.
Returns a list of selected horse names.
"""
num_horses = random.randint(4, 12)
return random.sample(self.HORSE_NAMES, num_horses)
@staticmethod
def _generate_odds(num_horses: int) -> list:
"""Generate random odds for each horse.
Follows the logic of the TI BASIC program: Each horse starts
with zero, then is incremented by a random number between 0
and 15 inclusive one hundred times. The final odds are
computed by subtracting the tenth of the accumulated total
from 10, with a floor of 1.
"""
# Start with zeros
odd_accum = [0] * num_horses
# Increment one random horse by a random value 0–15 one hundred times
for _ in range(100):
horse_idx = random.randrange(num_horses)
odd_accum[horse_idx] += random.randint(0, 15)
# Convert to odds between 1 and 10
odds = []
for total in odd_accum:
# Equivalent to ODD(A) = 10 - INT(ODD(A)/10); minimum 1
# In the BASIC code ODD accumulators are integers of
# increments; dividing by 10, truncated, then subtract from 10.
odd_value = 10 - (total // 10)
if odd_value < 1:
odd_value = 1
odds.append(odd_value)
return odds
def _simulate_race(self, horses: list) -> list:
"""Simulate a race and return the finishing order.
Each horse is assigned a performance time by summing a number
of random steps. Lower times finish earlier. To add a
dramatic element, progress is printed on screen so players can
watch the race unfold in real time.
Returns a list of indices representing the order of finish.
"""
num_horses = len(horses)
# Each horse is represented by a dict with cumulative distance
positions = [0 for _ in range(num_horses)]
finished = [False] * num_horses
finish_order = []
# Distance required to finish; roughly corresponds to ~224
finish_distance = 100
tick = 0
while len(finish_order) < num_horses:
tick += 1
# Move each horse a random step (0–4) if not finished
for idx in range(num_horses):
if not finished[idx]:
positions[idx] += random.randint(0, 4)
if positions[idx] >= finish_distance:
finished[idx] = True
finish_order.append(idx)
# Print progress every few ticks to avoid flooding
if tick % 5 == 0:
print("\nRace progress:")
for idx, name in enumerate(horses):
bar = "." * (positions[idx] // 4)
marker = "🐎"
print(f" {name:<20} {bar}{marker}")
return finish_order
def _get_bet(self, horses: list, odds: list) -> tuple:
"""Prompt the user for bet type, amount and horses.
Returns a tuple (bet_type, bet_amount, horse1_index, horse2_index)
where horse2_index may be None for win/place/show bets.
"""
print("\nWhat would you like to bet?")
print("(W)in, (P)lace, (S)how, (E)xacta or (Q)uinella.")
bet_type = ""
while bet_type not in {"W", "P", "S", "E", "Q"}:
bet_type = input("Enter choice (W/P/S/E/Q): ").strip().upper()
# Get bet amount
bet = 0
while bet <= 0 or bet > self.bankroll:
try:
bet = int(input(f"You have ${self.bankroll}. Enter bet amount: "))
except ValueError:
bet = 0
if bet <= 0 or bet > self.bankroll:
print("Please enter a valid amount within your bankroll.")
self.bankroll -= bet
# Determine horse(s) to bet on
if bet_type in {"W", "P", "S"}:
horse_idx = -1
while horse_idx < 0 or horse_idx >= len(horses):
try:
horse_idx = int(input(f"Which horse number (1-{len(horses)}): ")) - 1
except ValueError:
horse_idx = -1
if horse_idx < 0 or horse_idx >= len(horses):
print("Not a valid horse.")
return bet_type, bet, horse_idx, None
else:
# Exacta or Quinella: ask for two horse numbers
first_idx = second_idx = -1
while True:
try:
raw = input(f"Enter two horse numbers separated by comma (1-{len(horses)}): ")
parts = raw.replace(" ", "").split(",")
if len(parts) != 2:
raise ValueError
first_idx = int(parts[0]) - 1
second_idx = int(parts[1]) - 1
except ValueError:
print("Please enter two valid numbers separated by a comma.")
continue
# validate
if (first_idx < 0 or first_idx >= len(horses) or
second_idx < 0 or second_idx >= len(horses)):
print("One of those horses is not running.")
continue
if first_idx == second_idx:
print("Please choose two different horses.")
continue
break
return bet_type, bet, first_idx, second_idx
def _settle_bet(
self,
bet_type: str,
bet_amount: int,
horse_idx: int,
horse2_idx: int,
finish_order: list,
odds: list
) -> None:
"""Settle the bet and update bankroll.
Implements the same payout logic as the Extended BASIC code.
Prints a message indicating whether the player won or lost and
their winnings if applicable.
"""
if bet_type in {"W", "P", "S"}:
# Determine place condition
allowed_positions = {
"W": {0}, # must finish first
"P": {0, 1}, # top two
"S": {0, 1, 2}, # top three
}[bet_type]
if finish_order.index(horse_idx) in allowed_positions:
# Payout factor: 1 for Win, 2 for Place, 3 for Show
denominator = {"W": 1, "P": 2, "S": 3}[bet_type]
winnings = bet_amount * odds[horse_idx] // denominator
print("You win!")
print(f"You get ${winnings}.")
self.bankroll += winnings
else:
print("You lost. Too bad.")
elif bet_type == "E":
# Exacta: must pick first and second in exact order
if finish_order[0] == horse_idx and finish_order[1] == horse2_idx:
winnings = bet_amount * (odds[horse_idx] + odds[horse2_idx])
print("You won the exacta!")
print(f"Payoff: ${winnings}")
self.bankroll += winnings
else:
print("You lost the exacta. Too bad.")
if self.bankroll == 0:
print("You have no money left.")
else: # Quinella
# Either horse can place first or second
first_two = {finish_order[0], finish_order[1]}
if {horse_idx, horse2_idx} == first_two:
winnings = (bet_amount * (odds[horse_idx] + odds[horse2_idx])) // 2
print("You win the quinella!")
print(f"Winnings: ${winnings}")
self.bankroll += winnings
else:
print("You lost the quinella. Too bad.")
if self.bankroll == 0:
print("You have no money left.")
def play(self) -> None:
"""Run the horse racing game session."""
print("At the Races!")
if self.high_score > 0:
print(f"Record: ${self.high_score} by {self.high_scorer}")
input("Press Enter to begin...")
while self.current_race <= self.num_races:
print("\n" + "=" * 40)
print(f"Race #{self.current_race}")
# Select horses and generate odds
horses = self._choose_horses()
odds = self._generate_odds(len(horses))
# Display horses and odds
for idx, (name, odd) in enumerate(zip(horses, odds), start=1):
print(f"{idx}. {name} \t {odd}:1")
# Get bet information
bet_type, bet_amount, horse_idx, horse2_idx = self._get_bet(horses, odds)
# Simulate the race
print("\nThere they go!")
finish_order = self._simulate_race(horses)
# Display official results
print("\nOfficial results")
print(f"{'Place':<6}{'Horse #':<8}{'Name'}")
for pos, idx in enumerate(finish_order, start=1):
print(f"{pos:<6}{idx+1:<8}{horses[idx]}")
# Settle bet
self._settle_bet(bet_type, bet_amount, horse_idx, horse2_idx, finish_order, odds)
# Next race
self.current_race += 1
# Early termination if bankroll is gone
if self.bankroll <= 0:
print("\nYou've run out of money!")
break
# End of session
print("\nThat's all.")
print(f"You ended with ${self.bankroll}.")
# Check record
if self.bankroll > self.high_score:
print("You set a new record!")
name = input("Please enter your name: ")
self._save_record(self.bankroll, name)
self.high_score, self.high_scorer = self.bankroll, name
elif self.bankroll == self.high_score and self.high_score > 0:
print("You tied the record!")
# Ask to play again
again = ""
while again not in {"Y", "N"}:
again = input("Would you like to try again? (Y/N): ").strip().upper()
if again == "Y":
# Reset state and replay
self.bankroll = self.initial_bankroll
self.current_race = 1
self.play()
if __name__ == "__main__":
# Seed the RNG to vary outcomes
random.seed()
game = HorseRacingGame()
game.play()
Thanks!
I just got the first return issue of Compute!'s Gazette. It has some of these.
I still remember me and my dad taking turns typing for hours, sometimes days, in order to get a new game! Then we would play the same game for days because there was no easy way to save them. That was quality family time!
nostr:nprofile1qqsrmd72g25ws0qyh4fvaqm2nqvtd6g9960r42zaytpt83c47ysljmcpz4mhxue69uhhyetvv9ujuerpd46hxtnfduhsz9mhwden5te0wfjkccte9ehx7um5wghxyctwvshsz9mhwden5te0wfjkccte9ec8y6tdv9kzumn9wshs540lpz
you know what's crazy? You can still use "rem" for comments in windows batch files 😀
This is so cool!!
Now that was coding.
I remember this. We saved our programs on cassette tape.

Oh, you had an ATARI. I also had a Datasette for my VIC-20 and a screwdriver to adjust the sound head in case I got hold of a cassette that couldn't be read without errors. At some point I came up with the trick of setting up the Datasette at different angles, because then the tape of the cassette ran past the tape at different heights.
My first computer was an Atari 400. Later I had a Commodore 64. I finally got a Mac in my second year of college.
Based
I hated those stupid magazines. They never explained how the program worked. Basically useless information
Disagree. The Internet was a long way off. Libraries didn't yet have computer books, especially not about my model. Typing out listings and finding mistakes was another way of learning. Even if it was typed letter by letter, I wasn't sure if the typesetter hadn't made a mistake, and when the program finally ran, I tried to figure out why it did what it did. A magical time, a new spell every day.
I was too young to try and figure it out. I had no way of knowing what "peek and poke" meant, or what it meant to write to the "frame buffer". None of that was explained in the Tandy books. Even if it was, it was over my head. I probably needed someone to explain it to me.
My post above was describing my frustration with the magazines not explaining what the code was doing. I would have loved to know, but I could see I had no path to understanding it on my own. I pleaded with the librarians to show me books on computers and electronics, but they were ancient government employees and ignored me as an annoyance. Once someone told me I needed to go to the university library to find those books. So... I got on the bus, went to the next town and found the university library. (Back then you just had to go corner to corner and ask people which way to go, which was never reliable. You had to look for people that looked honest). I went through the doors and found a guard blocking my way. He said I couldn't go in without a student ID. Naturally I asked how I could get one. He said I needed to be a student at the university. I asked how to be a student at the university. He pointed me to admissions. So, I asked admissions how to get a student ID so I could use the library. They said I needed to finish high school. Well, as a 6th grader, that wouldn't be for some time. At that point I gave up on the university, and went to different library systems. They pointed me at the card index. Again... government employees. It wasn't until 3 years later when I had a student job at a large corporation that I finally found a library for the engineers. It had ALL the books I wanted... accept anything about Tandy computers or anything to do with writing video games.
Did you see that Commodore was bought by retro fans and some of the old time creators, and are relaunching the company with a new vision? Exciting times ahead
Nice!
I remember typing a game or two this way as a little kid, on a Commodore 64. Those were the days!
10 So true
20 goto 10
Yeah I remember that, sitting at a friends house behind their MSX computer, spending Saturday mornings typing and not sure if you'd be able to play the game that afternoon. Because of typos and not the skills to debug 🔙📺📼😌
A book I have from back then.
First tried this on a zx81 I'd borrowed from the local library, then on the c64. I dont think I ever got one to work properly :) just seeing that page gives me nightmares.