Lorye Go! Hong Kong Edition - News

Lorye Go! Hong Kong Edition gameplay showcasing randomization in action.

Vibe Coding Journey:
Solving Randomization Issues in "Lorye Go! 香港版"

Published on March 27, 2025 by Entz Yeung

A New Challenge: Predictable Randomness

As the developer of Lorye Go! 香港版, I’ve been vibing with AI to bring this train-themed board game to life for 3 weeks now. I hit a new snag: the randomization of questions and events wasn’t as random as it should’ve been. With 440 trivia questions and seasonal events tied to yellow stations, I wanted every game to feel fresh. Instead, players kept seeing the same questions — like QIDs 1, 100, 233, 301 — popping up way too often. Events felt off too, though less obvious with fewer options. This log dives into how I tackled this with AI.

The Culprit: PRNG and Predictable Patterns

The issue stemmed from Python’s random module, a pseudorandom number generator (PRNG) that’s deterministic by nature. Without a varied seed, it spits out similar sequences. In my game, first I shuffled the question list twice — once at startup and once per session — but the selection still used question_count % len(all_questions). If question_count resets to 0 at the start of each game, I always picking from the start of the shuffled list. If the shuffle doesn’t vary much between sessions due to a similar PRNG seed, the early elements of all_questions — say, indices 0, 1, 2 — stay roughly the same. So, players see the same questions (e.g., IDs 1, 100, 233, 301) repeatedly. Yellow station events, picked with random.choice(), risked the same fate.

First Try: Double Shuffle

My initial fix was straightforward: shuffle twice. Here’s how it looked:

all_questions = [(cat, q) for cat in questions for q in questions[cat]]
random.shuffle(all_questions)
custom_print(f"Shuffled all_questions one time...")
        

And at the session start:

random.shuffle(all_questions)
custom_print(f"Shuffled {len(all_questions)} questions for new game session")
        

I figured more shuffling equals more chaos, right? Nope. The same questions kept showing up. The PRNG seed, tied to the system clock by default, wasn’t varying enough, and my selection method locked me into the list’s front end.

Second Try: Seeding with Nanoseconds

Next, I jazzed up the seed using time.time_ns() for nanosecond precision:

random.seed(time.time_ns())
random.shuffle(all_questions)
custom_print(f"Shuffled {len(all_questions)} questions with seed {time.time_ns()}")
        

This helped a bit—seeds were more unique—but the selection still leaned on question_count. Early list items dominated, and the massive 440-question pool didn’t shuffle wildly enough to break the pattern.

Breakthrough: Random Index Picking

Enter Grok 3, my vibe coding buddy. It pointed out the real flaw: why shuffle at all if I’m cycling predictably? Instead, I switched to random.randint() for question picks:

question_idx = random.randint(0, len(all_questions) - 1)
category, question_data = all_questions[question_idx]
        

Boom—every question now had a 1/440 shot each time, no matter the list order. The nanosecond seed kept the PRNG fresh across sessions, and repetition vanished.

Human Twist: Custom Seed Vibes

Grok suggested the full time.time_ns(), but I wanted to vibe it my way. I cooked up this:

random.seed((time.time_ns() % 10**9) / 1000)
random.shuffle(all_questions)
custom_print(f"Shuffled {len(all_questions)} questions with seed {(time.time_ns() % 10**9) / 1000}")
        

Taking the last 9 digits of the nanosecond timestamp (e.g., 823074000) and dividing by 1000 (to 823074.0) gave me a funky, floating-point seed that still rocked with random.seed(). It felt dynamic, and in testing, it kept things unpredictable.

Fixing Events Too

Yellow station events needed love too. Originally:

event_list = seasonal_events[current_season]
event_tuple = random.choice(event_list)
        

I aligned it with the question fix:

event_idx = random.randint(0, len(event_list) - 1)
event_tuple = event_list[event_idx]
custom_print(f"Selected event index: {event_idx} for season {current_season}")
        

Using the same custom seed, events now popped up randomly, no favoritism.

The Final Recipe

The solution clicked with three ingredients: a custom seed ((time.time_ns() % 10**9) / 1000), random.randint() for picking, and logs to confirm it worked. No more patterns—just pure, vibey randomness.

Vibe Coding in Action

I spotted the repetition playing the game — AI can’t “feel” that. I tinkered with shuffles and seeds changes, then Grok swooped in with random.randint(), nailing the tech fix. Together, we solved the randomization issue.

Why Humans Still Rule

AI’s slick, but it won’t catch a game feeling “off” without me playing it. And because of my data science background, I have faced the randomization issue before, so I kind of know which is the right direction we should head to next. So I drove the vision; Grok refined it. I believe this could be the magic of vibe coding — human's experience and vision meets AI muscle.

Wrapping Up

Fixing randomization in Lorye Go! was a blast. From double shuffles to a custom-seeded, index-picking system, it’s now a wild ride every time. If you’re coding something, trust your instincts, vibe with AI, and ship it. Enjoy the game, Hong Kongers—randomness awaits! 🚂