Super Sim Bros.

June 10, 2025

Every Thursday at 8 pm, my friends and I have movie night.

The system we use is simple: Each person puts up a movie, then we randomly choose one and watch it. As for the rules, there’s really only two: You can’t put up a movie that we already watched, and if you win, you can’t put up a movie the following week.

This means you could technically double up with someone and the odds of the movie winning increase. But if it wins, both of you are barred from submitting a movie the following week. In the event that everyone chooses the same movie, rule 2 is ignored.

There are many ways you could randomly select the movie here, and at first we were just doing it in the most basic way. We would write down all the movies, use the random number generator (RNG) on Google, and pick the movie with that index.

But where is the fun in that?


The Google RNG method was quick, boring, and anticlimactic. I was like wait a minute, I should just write a program to make this more fun. So that’s what I did.

The first iteration was a race using Pygame. The movies would start at the bottom of the screen and work their way towards the top. Every frame, each movie would gain a random amount of progress. There would be a back and forth for about a minute or two until one of them crossed the finish line and won.

This was fine for a while, but then I decided it could be improved.

I eventually came up with the idea to combine the action of SUPER SMASH BROS. with the turn-based combat of DUNGEONS & DRAGONS.

All of the movies would be on a small platform and fight to the death, but they would do so one at a time. We first take the list of movies and shuffle them to create an initiative order. When a movie starts its turn, it randomly selects a target from the remaining movies. It then moves over to to it, rolls a d20, and that amount of damage is subtracted from the target’s hit points (HP). Once the attack is over, it moves back to its original position, and then the next movie goes. Every movie starts with 100 HP and is eliminated once its HP reaches 0.

So I got to work writing this in Python. I wanted it to be better than the last version, so I used TMDB to get the movie posters, Fooocus-API to generate the arena background, and GPT to create the prompt for the arena background image based on a random movie in the list. I even added music and sound effects for the attacks.

All in all, I was pretty happy with how it turned out, but representing it in this way led to an interesting question that I still don’t have an answer to.


Let’s say four people attend movie night. Person one puts up PARASITE, person two puts up CITY OF GOD, person three puts up EX MACHINA, and person four puts up IT’S A WONDERFUL LIFE.

With everyone putting up their own movie, each movie has a ¼ chance of winning.

But now let’s say person four says actually, I don’t feel like watching an old movie tonight. I’m also gonna put up PARASITE.

So now we have four people, but only three movies.

The simple solution here is to physically double the movie.

If we have our list:

parasite
parasite
city of god
ex machina

Now the odds of PARASITE winning are ½ while the other two remain at ¼, just as it should be.

Note that in the way we’re doing it here with the attacking mechanic, even though the two instances of PARASITE can attack each other, the odds still work out to be correct.

But what if we didn’t want to duplicate the movie?

If our list is now:

parasite
city of god
ex machina

The question becomes, how much starting HP does PARASITE need to guarantee a two-in-four chance of winning?

(Does it need to be doubled since its odds are doubling? 200HP?, 150HP? 141HP? Take a moment to choose a number and I’ll let you know what it is by the end.)

And not just this specifically, but also in the general case. What if three people choose PARASITE? What if there were n movies with k people grouping up?

The full question is, is there some function that takes the number of people, the number of movies, and the number of groupings, and returns the amount of HP each movie would need in order to match the odds of the duplication method?


Similar to my other post, this is once again a math problem that I just don’t have the mathematical knowledge or tools to figure out.

I asked about this on the math stack exchange among other places, and I got the ol’ stack overflow why would you want to do this? response. They told me to just use the duplication method.

However, one person did respond saying a function like that would be quite complicated, and he wasn’t sure what it would be.

I then asked GPT 4o, o1, and o3, and the best I got was a really poor approximation function. One of the responses also mentioned how there isn’t really a clean way to do this, matching what people were saying online. So maybe there is a way to do it, maybe there isn’t, who knows.

The cool thing is that we can still figure out what the values would need to be by simulating the game. We can modify the starting HP and simulate thousands of matches and record the percentage of wins. Based on that, we can raise or lower the value and repeat.


We’ll start by defining a Movie class so we can keep track of each movie and its HP:

class Movie:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp

    def __repr__(self):
        return self.name

    def attack(self, target):
        target.hp -= random.randrange(1, 21)

Now we need a function that can act as the main game. First, we’ll create the three movies, add them to a list, and then shuffle them so they’re in a random order.

We could use a regular list and update the index at each turn, but doing it that way complicates things when there’s a death. If it were movies[1]’s turn and movies[0] gets eliminated, we would have to shift everything over. movies[1] would now be movies[0], and we would have to add a case saying not to increase the index if a movie with a lower index was removed.

Using a deque allows us to keep the attacker as movies[0] and we can rotate the list every time regardless of the outcome:

import random
from collections import deque


def game():
    movies = collections.deque()

    movies.append(Movie("parasite", 100))
    movies.append(Movie("city of god", 100))
    movies.append(Movie("ex machina", 100))

    random.shuffle(movies)

Alternatively, I could have written a rotate function that accepts a list, but collections.deque already exists.

For the rest of the function, we need to cycle through the movies and have them attack until only one remains:

def game():
    movies = collections.deque()

    movies.append(Movie("parasite", 100))
    movies.append(Movie("city of god", 100))
    movies.append(Movie("ex machina", 100))

    random.shuffle(movies)

    while len(movies) > 1:
        attacker = movies[0]

        targets = [i for i in movies if i != attacker]

        victim = random.choice(targets)

        attacker.attack(victim)

        if victim.hp <= 0:
            movies.remove(victim)

        movies.rotate(-1)

    return str(attacker)

The last thing we need is a main function that will run the game thousands of times and keep track of the wins and losses:

def main():
    winners = {}
    sims = 900000

    for i in range(sims):
        winner = game()

        winners.setdefault(winner, 0)
        winners[winner] += 1

    for key, value in winners.items():
        print(key, value / sims)

Once we run main() we get (drum roll…):

ex machina 0.33388444444444443
parasite 0.33350555555555556
city of god 0.33261

Okay cool, but this isn’t what we want. We need CITY OF GOD and EX MACHINA to be 0.25 and PARASITE to be 0.50. So we’ll raise the starting HP of PARASITE and see what happens.

Let’s try doubling it:

movies.append(Movie("parasite", 200))
movies.append(Movie("city of god", 100))
movies.append(Movie("ex machina", 100))
parasite 0.9577944444444444
city of god 0.020965555555555557
ex machina 0.02124

Oh, well that’s not right. The answer has to be somewhere between 100 and 200 though, so let’s cut it in half until we find it.

movies.append(Movie("parasite", 150))
movies.append(Movie("city of god", 100))
movies.append(Movie("ex machina", 100))
parasite 0.7626744444444444
ex machina 0.11860333333333334
city of god 0.11872222222222223

Closer, but PARASITE is still winning way too much. Let’s go down to 120.

movies.append(Movie("parasite", 120))
movies.append(Movie("city of god", 100))
movies.append(Movie("ex machina", 100))
parasite 0.5192955555555555
ex machina 0.24025777777777776
city of god 0.24044666666666667

Very close. If we spend some time checking each value between 115 and 120, we’ll find that the closest number ends up being 118.

movies.append(Movie("parasite", 118))
movies.append(Movie("city of god", 100))
movies.append(Movie("ex machina", 100))
parasite 0.49992
city of god 0.24978444444444445
ex machina 0.25029555555555555

Before I wrote this simulation, my intuition was that this problem would be fairly simple. I thought, if we want to double the odds of a movie winning, then we probably need to double its health. Or if the answer wasn’t to double it, then surely it would be some nice value like 150 (normal health + half the normal health), 141 (normal health * sqrt(2)), etc.

And this is just the four people with three movies case. You could have any number of people with some number of movies and some number of groupings, and the HP values will fluctuate between about 116 and 155 depending.

So that’s where things stand. A fun Thursday-night tradition accidentally turned into a mini research project, and the best answer I’ve got right now is run the sim and see where it lands. If you know the neat closed-form formula that the math stack exchange swore was either ugly or impossible, feel free to enlighten me. Until then, I’m happy letting Python crunch a few hundred thousand brawls while I go and grab the popcorn.