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. image

Replies (23)

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!
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()
lkraider's avatar
lkraider null 5 months ago
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
Donutpanic's avatar
Donutpanic 5 months ago
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.
Donutpanic's avatar
Donutpanic 5 months ago
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.
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 🔙📺📼😌
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.
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.