4
\$\begingroup\$

What is Buckshot Roulette?

Buckshot Roulette is an indie game, released by Mike Klubnika to itch.io in 2023.

Okay, but how do you play?

To quote from the wiki:

"At its core, Buckshot Roulette is simply Russian roulette played with a shotgun. The Shotgun has a random number of "shells" (or rounds, see Terminology) inserted into it. Each shell is either a live or blank. A live shell shoots and does damage, it is alike to a loaded chamber in Russian roulette. A blank shell does not shoot at all, it is alike to an empty chamber in Russian roulette. When you shoot yourself or someone else, a charge is deducted from the entity shot. Charges are equivalent to the traditional lives in gaming.

There are also items that you can use to your advantage. There are a random amount and variety drawn at the beginning of every round, and you choose what item and when to use it. For example, you might have a Hand Saw that makes the Shotgun deal two charges of damage. You would want to use this when you know for sure that the round currently chambered in the Shotgun is live. If you had a Cigarette Pack which gives you an extra charge, you would want to use this after getting shot to regain your health."

Okay, that's great and all, but why are you making a KOTH challenge?

A. Because I can.

B. It's fun to try and optimize luck-based games algorithmically.

Why Python only?

Because it's really tricky to have multiple languages compete at once (unless I'm stupid and I've missed something simple), and I already have the interpreter on hand.

How does a tournament work?

Your "dealer"/bot function is run against (or will be) the 10 most upvoted bots 1000 times each, to determine how good it is. The higher the number of wins, the better

Can I have the tournament code so I can develop my bot?

Sure! Comes with 2 example dealers, to test your bot.

from random import randint, shuffle from math import factorial dealers = ["alwaysother","alwaysself"] def alwaysother(livecount, blankcount, myitems, theiritems, mylives, theirlives, dnumber): if "cigr" in myitems: useitem(myitems.index("cigr"),dnumber) if "cigr" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("cigr")) if blankcount/livecount < 0.25 and "saws" in myitems: useitem(myitems.index("saws"),dnumber) if blankcount/livecount < 0.25 and "saws" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("saws")) if "cuff" in myitems: useitem(myitems.index("cuff"),dnumber) if "cuff" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("cuff")) return 1 def alwaysself(livecount, blankcount, myitems, theiritems, mylives, theirlives, dnumber): if "cigr" in myitems: useitem(myitems.index("cigr"),dnumber) if "cigr" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("cigr")) if blankcount/livecount < 0.25 and "saws" in myitems: useitem(myitems.index("saws"),dnumber) if blankcount/livecount < 0.25 and "saws" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("saws")) if "cuff" in myitems: useitem(myitems.index("cuff"),dnumber) if "cuff" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"),dnumber,theiritems.index("cuff")) return 0 shotgun = [] D1items = [] D2items = [] D1lives = 5 D2lives = 5 damage = 1 cuffsused = False def useitem(itemindx, D1orD2, choice=0): global shotgun, D1items, D1lives, D2items, D2lives, damage, cuffsused items = D1items if D1orD2 == 1 else D2items item = items.pop(itemindx) match item: case "adrn": target = D2items if D1orD2 == 1 else D1items if 0 <= choice < len(target): return target.pop(choice) case "beer": if shotgun: return shotgun.pop() case "burn": if shotgun: idx = randint(0, len(shotgun)-1) return shotgun[idx], idx case "cigr": if D1orD2 == 1: D1lives += 1 return D1lives else: D2lives += 1 return D2lives case "medc": heal = randint(0,1) if D1orD2 == 1: D1lives += 2 if heal else -1 return D1lives else: D2lives += 2 if heal else -1 return D2lives case "saws": damage += 1 return damage case "cuff": cuffsused = True case "invr": if shotgun: s = shotgun.pop() shotgun.append("blank" if s=="live" else "live") case "mgnf": if shotgun: return shotgun[-1] def calldealer(name, *args): return globals()[name](*args) dealer_totals = {} def tournement(): global dealers, shotgun, D1items, D1lives, D2items, D2lives, damage, cuffsused, dealer_totals, total for d in dealers: dealer_totals[d] = 0 for i in range(len(dealers)): D1 = dealers[i] total = 0 for j in range(i, len(dealers)): D2 = dealers[j] print(D1, "v", D2) D1wins = D2wins = 0 for g in range(1000): D1lives = randint(3,7) D2lives = randint(3,7) shotgun = [] D1items = [] D2items = [] damage = 1 cuffsused = False turn = 1 while D1lives > 0 and D2lives > 0: if not shotgun: livecount = randint(1,5) blankcount = randint(1,5) shotgun = ["live"]*livecount + ["blank"]*blankcount shuffle(shotgun) if len(D1items)==0 or len(D2items)==0: pool = ["adrn","beer","burn","cigr","medc","saws","cuff","invr","mgnf"] for g in range(randint(2,4)): D1items.append(pool[randint(0,8)]) D2items.append(pool[randint(0,8)]) shell = shotgun.pop() if turn == 1: c = calldealer(D1, livecount, blankcount, D1items, D2items, D1lives, D2lives, 1) shootSelf = (c == 0) if shootSelf: if shell=="live": D1lives -= damage turn = 2 else: turn = 2 if not cuffsused else 1 cuffsused = False else: if shell=="live": D2lives -= damage turn = 1 else: turn = 2 if not cuffsused else 1 cuffsused = False damage = 1 else: c = calldealer(D2, livecount, blankcount, D2items, D1items, D2lives, D1lives, 2) shootSelf = (c == 0) if shootSelf: if shell=="live": D2lives -= damage turn = 1 if not cuffsused else 2 cuffsused = False else: turn = 2 else: if shell=="live": D1lives -= damage turn = 2 else: turn = 1 if not cuffsused else 2 cuffsused = False damage = 1 if D1lives <= 0 < D2lives: D2wins += 1 elif D2lives <= 0 < D1lives: D1wins += 1 print(f"{D1} wins: {D1wins} | {D2} wins: {D2wins}") dealer_totals[D1] += D1wins dealer_totals[D2] += D2wins print("\nTotal wins per dealer:") for k,v in dealer_totals.items(): print(f"{k} - {v}/{1000*factorial(len(dealers))}") tournement() 

What do all the items do?

adrn lets you steal the other dealer's items beer deletes the next shell, and tells you what it was burn tells you a random future shell cigr are just a +1 to health medc are a 50% chance to gain 2 health, 50% to lose 1 saws add one damage for the next shot cuff lets you take 2 turns in a row invr inverts the next shell in the chamber mgnf tells you the next shot in the chamber 

Are there any bonuses?

+20% score if it is under 250 bytes, and has a 60% or greater win rate (if you are doing neural networks, the size of the network counts). The function definition header is not counted in this.

Rules

  1. No directly modifying the lists!

  2. Must be yielding

  3. You can use any imports you like

  4. Neural networks are allowed, PyTorch or whatever works for you

  5. Must be a function (sorry about this one, just makes it easier to run tournaments)

\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Example Bot

def optimised(livecount, blankcount, myitems, theiritems, mylives, theirlives, dnumber): def get_prob(blank, live): return blank / live if live > 0 else 1 probablilty = get_prob(blankcount, livecount) if "cigr" in myitems: useitem(myitems.index("cigr"), dnumber) elif "cigr" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"), dnumber, theiritems.index("cigr")) if "cuff" in myitems: useitem(myitems.index("cuff"), dnumber) elif "cuff" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"), dnumber, theiritems.index("cuff")) if "mgnf" in myitems: result = useitem(myitems.index("mgnf"), dnumber) probablilty = 0 if result == "live" else 1 elif "mgnf" in theiritems and "adrn" in myitems: result = useitem(myitems.index("adrn"), dnumber, theiritems.index("mgnf")) probablilty = 0 if result == "live" else 1 if probablilty > 0.75: if "beer" in myitems: result = useitem(myitems.index("beer"), dnumber) if result == "blank": blankcount -= 1 else: livecount -= 1 elif "beer" in theiritems and "adrn" in myitems: result = useitem(myitems.index("adrn"), dnumber, theiritems.index("beer")) if result == "blank": blankcount -= 1 else: livecount -= 1 probablilty = get_prob(blankcount, livecount) if probablilty > 0.75: if "invr" in myitems: useitem(myitems.index("invr"), dnumber) probablilty = get_prob(blankcount - 1, livecount + 1) elif "invr" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"), dnumber, theiritems.index("invr")) probablilty = get_prob(blankcount - 1, livecount + 1) if probablilty < 0.25: if "saws" in myitems: useitem(myitems.index("saws"), dnumber) elif "saws" in theiritems and "adrn" in myitems: useitem(myitems.index("adrn"), dnumber, theiritems.index("saws")) return 1 if probablilty >= 0.1 else 0 
\$\endgroup\$

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.