Skip to content

Instantly share code, notes, and snippets.

@mmdts
Last active February 13, 2021 05:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mmdts/6888be6060aea45f4cec738e2ed31a50 to your computer and use it in GitHub Desktop.
Save mmdts/6888be6060aea45f4cec738e2ed31a50 to your computer and use it in GitHub Desktop.
Betting system analysis and rework for custom hero clash dota custom game.

Old system

Betting currently has the following aspects:

Expected winner = W > L
Underdog = L > W

Winner gain (Gw_aspects):
1. The winner could win at least half the total pot if the algorithm did work --> Discourages
   overbetting
2. The algorithm always gives an amount equatable to 2.5 times the base pot if the winner was an
   underdog --> This is broken
3. The expected winner also gets the same broken 2.5 times the base pot stated in point 2 if he
   is overbet on by a large margin (four times the base pot) --> This is broken
4. The expected winner always gets exactly half the total pot if he isn't overbet on --> This is
   the only balanced part of the algorithm

Better gain (Gb_aspects):
1. Explained below, betting little on the underdog doesn't give a lot of money. This fixes jackpot
   stealing.
2. Overbetting gains are capped at seizing all of the losers' bets and the base pot to yourself
   You only compete with other betters and not with the duel winner, who gets insanely rich anyway

This is how the old system worked in detail. Notice that the quirky functions it had are explained in a more rigorous way that is easier to analyze mathematically.

Basic values

W  # Amount bet on winner
L  # Amount bet on loser
X  # The amount bet by a specific person

B = 1550 * 1.021**(level - 1)  # Base pot in FFA, for Duos: B = 1800 * 1.021 ** (level - 1)

Calculation primitives

Q is a fraction (coefficient that is < 1)

Starts at 1.0 at 0% bet on winner

Breaks even at 20% bet on winner, and floors at 0.5 when that amount or more is bet on winner

Q = max(0.5, 1 - W/(W+L) * 2.5)

R is a fraction (coefficient that is < 1)

Starts at 0.0 when less than 26.7% is bet on the winner, at which value it is 1.0, and caps there.

R = min(1, W/(W+L) * 3.75)

Winner gain

This can be comprehended as an execution on the condition of W - L < 4B:

  • Less than 50% on winner:
    • 2.5*B + L --> Reason for Jackpot-like structure, keeping in mind that L (a large value for the underdog) is large, and that 2.5*B is ridiculous
  • Over 50% on winner:
    • W - L < 4*B ? 0.5*(W+L+B) : 2.5*B + L ---> Imbalanced if W - L is indeed more, since 2.5*B is a ridiculous amount. This is the part where overbetting becomes broken.
    • If W - L < 4*B, it gives exactly half the total amount if no overbetting took place, which is the only balanced situation.
  • The situation in which Q*(W+L+B) is less than 2.5*B + L and Q is more than 0.5 can not happen!!
Gw = W - L < 4*B ? 0.5*(W+L+B) : 2.5*B + L

Better gain

Basically put, this is a modified "better's fraction", where the modifying coefficient is based on the percent bet on the winner.

Better gain is DIRECTLY PROPORTIONAL to (a linear function, with no intercept of) better amount. This is easy to comprehend but implications will be described below.

Gb = R*(W+L+B)*(X/W)

When you're the only better on the underdog, R is likely going to be very low, which means you won't make as much gold.

This discourages betting small amounts on the underdog, as you get significantly much higher returns if the bet on the underdog is a higher portion of the total bet.

For overbetting, the implication is: (~ means almost)

  • X/W ~= 1, R is definitely 1
  • You gain W+L+B, and since you invested ~W, your net gain is L+B, which is a "cap"

for your returns on overbetting.

New System

From AIMC code:

  if player.betOnWinner:
      player.gold += math.floor(
          totalPot * min(1, 0.4 + winnerBet / totalPot * 2.5) * player.bet / winnerBet)
  if player.winner:
      share_fraction = max(0.5,1 - winnerBet / totalPot * 2.5)
      player.gold += min(totalPot * share_fraction, 2.2 * basePot + totalBet - winnerBet)

This system still exhibits many of the issues in the old system, particularly:

  1. Not providing gains for overbetting.
  2. Not providing balanced aid to low-gold players.
  3. Not relying on proper mathematical foundation.
  4. The amount of gold generated by the system is not a function of anything, and cannot be determined before the duel ends.

mmdts System

Section 1. The system basis

A new system is constructed in this section to fix the issues described at the start of the previous section.

Fixing Gb_aspects.2 is simply implemented by providing less-than-linear gain. Roots and logarithms quickly come to mind.

I choose roots over logarithms because logarithms discourage overbetting over a certain optimization point (between risk and gain), while roots provide gains that scale well beyond any given optimization.

For now, we'll go with a fractional exponent that is very similar to (but more refinable than) a root function.

Jackpot stealing, Gb_aspects.1, was a significant part of the game, since you do lose your bet for the round if you risk betting on the significantly-less-popular underdog that no one else bets on. You want to be able to cash money back on that great risk of gaining no money at all.

Bringing jackpot stealing back would be unfair to the winner, so I settle on the middle ground of jackpot sharing, where the share fraction's denominator is a function of level:

  1. Share fractions that are a function of level force you to bet more at higher levels to get a decent share of the underdog's jackpot.
  2. Share fractions could be basically thought of as "the amount I'd earn from the otherwise-would've-been-jackpot is based on how much I bet as compared to a fixed amount" Rather than the current "as compared to the total bet" signified by R.

All 3 Gw_aspects are all fixable by introducing a new, simpler algorithm for deciding winner gain, which has to have the following properties:

  1. Overbetting resistance: The winner should get returns on overbetting, but ones that are significantly less than the overbetter, as to not make overbetting futile.
  2. Jackpot-like behavior: The more of an underdog you are, the more you should feel rewarded by the system.
  3. Balanced functionality: The winner should never get an insane amount of gold under any circumstance other than a true jackpot.

How it works

We start by redefining the basics:

W  # Amount bet on winner
L  # Amount bet on loser
X  # The amount bet by a specific person

We then calculate the variable pot V, and the share fraction, J.

# The variable pot, or gold generated, is given by the following rule:
V = (W+L)**0.25 * 500 * 1.021**(level - 1)

# For Duos: V = (W+L)**0.25 * 592 * 1.021**(level - 1)
# The share fraction, or better-winner split, is a function of total winner
# bet and level (given through variable pot), and is given by the following rule:
J = W == 0 ? 0 : atan(10 * (W/V - 0.2) ) / (1.6 * pi) + 0.4

# where atan is the arctan function
# The W == 0 condition is set so that J = 0 if the duel winner has a jackpot.

V is identical to the previous B at a certain bet amount, increases with an exponent identical to the fifth root of the total bet amount.

We finally calculate the amount the betters make and the winner makes on the bet.

  1. The amount you gain from the winners' pot scales linearly with your bet contribution. This is necessary to prevent you from "losing" gold on the bet. Note that W*(X / W) is simply just X, but it is written the way it is written to illustrate that sum(Gb) + Gw is equal to W + V + L.
  2. The amount you gain from the variable pot scales almost linearly with your bet contribution. Since the variable pot scales (non-linearly) with your bet contribution, this is the part that encourages you to bet more to earn more without a hard-cap like in the previous system.
  3. You'll be giving each other winning better a little more gold, but ultimately, you'd get the biggest share of the pie.

The amount you gain from the losers' pot is designed to be more fair to small-number betters. It scales at a significantly lower value exponent than V.

Finally, the value J gets to decide the split of the pot the winner gets. Note that the winner does not get any share of the winners' pot, since this means that they'd lose money on the bet. The betters (altogether) get a proportion J*(V + L) of the variable pot and the losers' pot.

Gb = W * (X / W)
   + V * (J * X**0.8 / sum(Xi**0.8))
   + L * (J * X**0.5 / sum(Xi**0.5))

# where the sums on Xi sum only on the bet winners' bets.

# The duel winner gets the rest of V + L, which is the following:

Gw = (1 - J) * (V + L)

A quick proof

Summing to demonstrate that the total pot is indeed W + V + L:

sum(Gb) = W * (sum(Xi)/W)
        + V * (J * sum(Xi**0.8) / sum(Xi**0.8))
        + L * (J * sum(Xi**0.5) / sum(Xi**0.5))

sum(Gb) = W + J * (V + L)

Gw = (1 - J) * (V + L)

T = sum(Gb) + Gw = W + V + L

Section 2. The system definition

The rules set above have "hard-coded" numbers, that are much more tunable than the original formula (for better balance mechanics over testing time). They are given those values to aid understanding, but once the hang of it is gotten, one could refer to the abstract rules shown below:

W  # Amount bet on winner
L  # Amount bet on loser
X  # The amount bet by a specific person

Cv = solo ? 500 : 592    # Variable pot gold constant. This variable preserves the balance
                         # between the old system and the new one. Tweaking this variable
                         # changes the amount of "legacy base pot" present in duels.

Lv = 1.021               # Variable pot level gain. Identical to the one of the old betting
                         # system.

Ev = 0.25                # Variable pot exponent (total bet amplification). Tweaking this
                         # system adjusts how much new-gold is brought into the game by the
                         # system based on bet-hype (or the total amount bet on both sides).
                         # Lower values mean that the game will be significantly resistant to
                         # bet-hype, which is desired to preserve economy (so that inflation
                         # will not happen). Keeping in mind that bet hype is necessary
                         # to make overbetting viable (so this value can't be zero),
                         # therefore the default value of 0.25

Egv = 0.8                # Variable pot attentuation for better gain fraction. This variable
                         # decides how much less-than-linearly the variable pot is split
                         # between betters. A value of 1 means that the variable pot is
                         # CAPITALIST, or that you get linear returns on your investment
                         # (allowing you to hog the entire pot for yourself).
                         # A value of 0 means that the variable pot is COMMUNIST, or that
                         # the pot will always give equal amounts to everyone regardless of
                         # how much they bet. A value in the middle is a result between both.
                         # 0.8, the default value, means that the variable pot is almost
                         # capitalist, which is desired, so that betting is rewarding, yet
                         # not 100% capitalist, so that overbetting does not deny other
                         # people their reward.

Egl = 0.5                # Losers' pot attentuation for better gain fraction. This is a
                         # capitalist-communist trade like the one above. Losers' pot (the
                         # gold inside the pot from the people who lost the bet) is something
                         # I'd like to think of as "fair gold". I'd like to believe this
                         # should be distributed more equally, to help poor people who can't
                         # bet much grow, therefore the default value of 0.5.

# Warning: The explanations for Jn, Js, Ja, Jp will make very little sense. Consider playing
# around with an arctan function in a graphing tool like fooplot.com to gain a better
# intuition on how they work.

Jn = 0.4                 # Normal earning. The "normal" fraction of the gold that someone
                         # who bets a proportionate amount on the winner should receive.
                         # Hard to explain in words, since it is very sensitive to the rest
                         # of the variables below.
                         # Note that Jn should always be close to 0.5. Graph the function of
                         # J for more information.

Js = 0.2                 # Skew factor. A number between  0 and 1 that decides when J would
                         # equal to the normal earning.
                         # Lower values of Js means that the system favor the betters, higher
                         # values of Js means that the system favor the duel winner.

Ja = 10                  # Avalanche factor. Decides the almost-linear part of the atan
                         # domain. Higher numbers mean that the atan will be linear for
                         # a shorter interval of input gold ratio.

Jp = 1.6                 # Peak-to-peak difference attentuation, or rather,
                         # asymptote-to-asymptote. Jp needs to always be higher than 1.
                         # Higher values mean that the betters (altogether) and the duel
                         # winner will each get closer amounts of money, irrelevant of their
                         # bet amount. If you need more dramatic peak-to-peak performance
                         # than can be exhibited by the minimum value of Jp = 1, consider
                         # employing avalanche behavior by changing the avalanche factor.

# Finally, our formulas, but abstracted:

V = (W+L)**Ev * Cv * Lv**(level - 1)

J = W == 0 ? 0 : atan(Ja * (W/V - Js) ) / ( Jp * pi) + Jn

Gb = W * (X / W)
   + V * (J * X**Egv / sum(Xi**Egv))
   + L * (J * X**Egl / sum(Xi**Egl))

Gw = (1 - J) * (V + L)

Section 3. The system demonstration

Implemented as actual code (keep in mind it isn't tested for type conversion errors, unlike the code in the simulation):

from typing import List
from math import atan, pi

# We start by a list of the winners' bets, and a list of the losers' bets
def transact(Xw: List[int], Xl: List[int], level: int, solo: bool = True):
  L = sum(Xl)
  W = sum(Xw)

  # Bet gains list to be returned. Has the same size as the winners' bets list.
  G = [None] * len(Xw)

  Cv = solo and 500 or 592
  Lv = 1.021
  Ev = 0.25
  Egv = 0.8
  Egl = 0.5
  Jn = 0.4
  Js = 0.2
  Ja = 10
  Jp = 1.6

  V = (W+L)**Ev * Cv * Lv**(level - 1)

  if W == 0:
    J = 0
  else:
    J = atan(Ja * (W/V - Js) ) / ( Jp * pi) + Jn

  Xgv = list(Xi**Egv for Xi in Xw)
  Xgl = list(Xi**Egl for Xi in Xw)

  for i, X in enumerate(Xw, 0): # for each winner
    G[i] = X + J * (V * X**Egv / sum(Xgv) + L * X**Egl / sum(Xgl))
  
  Gw = (1 - J) * (V + L)
  
  return G, Gw

FAQ

This is too complex! I feel like I'm reading someone's thesis! The original code is simpler and better.

This is elementary mathematics. We've been through this argument before with the duel system, and the final conclusion was that code written to be "mathematically tractable and coherent" is much more superior to code that is simple yet acts in mathematically unpredictable ways and needs "patches" and "quick fixes".

The code that is based on mathematics will ultimately always scale to all your use-cases, and if issue is found with it, it could be corrected based on mathematical principles instead of based on "oh, I need to set a minimum to this so lets use max(x, 0.5)"

On the original argument in the duel system, I refered to the analogy: Imagine if time lock procs were random with a uniform distribution instead of a pseudorandom distribution.. They would be much simpler, and still be random, but honestly, the game would be unplayable. Mathematics is a vital part of any game that needs to be balanced, because balance without mathematics is hard to achieve, and when achieved, is very brittle.

All the variable names are killing me. Can't you use better variable naming?

Here you go (keep in mind it isn't tested for type conversion errors, unlike the code in the simulation):

from typing import List
from math import atan, pi

# We start by a list of the winners' bets, and a list of the losers' bets
def transact(bets_on_winner: List[int], bets_on_loser: List[int],
             level: int, solo: bool = True):

  total_bet_on_loser = sum(bets_on_loser)
  total_bet_on_winner = sum(bets_on_winner)

  # Bet gains list to be returned. Has the same size as the winners' bets list.
  betters_earnings = [None] * len(bets_on_winner)

  vpot_gold_constant = solo and 500 or 592
  vpot_level_gain = 1.021
  vpot_exponent = 0.25
  earnings_vpot_attentuation = 0.8
  earnings_bet_on_loser_attentuation = 0.5
  atan_normal_earning = 0.4
  atan_scew_factor = 0.2
  atan_avalanche_factor = 10
  atan_attentuation = 1.6

  variable_pot = (total_bet_on_winner+total_bet_on_loser)**vpot_exponent * vpot_gold_constant * vpot_level_gain**(level - 1)

  if total_bet_on_winner == 0:
    betters_winner_split = 0
  else:
    ritsu = atan_avalanche_factor * (total_bet_on_winner/variable_pot - atan_scew_factor)
    betters_winner_split = atan(ritsu) / ( atan_attentuation * pi) + atan_normal_earning

  attenuated_variable_pot = list(Xi**earnings_vpot_attentuation for Xi in bets_on_winner)
  attenuated_total_bet_on_loser = list(Xi**earnings_bet_on_loser_attentuation for Xi in bets_on_winner)

  for i, X in enumerate(bets_on_winner, 0): # for each winner
    vpot_portion = variable_pot * X**earnings_vpot_attentuation / sum(attenuated_variable_pot)
    losers_bet_portion = total_bet_on_loser * X**earnings_bet_on_loser_attentuation / sum(attenuated_total_bet_on_loser)
    betters_earnings[i] = X + betters_winner_split * (vpot_portion + losers_bet_portion)
  
  winner_earnings = (1 - betters_winner_split) * (variable_pot + total_bet_on_loser)
  
  return betters_earnings, winner_earnings

I don't use python.

But you can read python. It's one of the easier programming languages to read and understand.

Besides, most of the stuff above aren't actually python, and won't run in python. For example, python doesn't use the c ? a : b syntax for conditional statements. They're more of pseudo-code.

Python is just used for syntax highlighting.

How can I guarantee this will work?

Simulate it!

I used a modified copy (to plot) of the code AIMC shared in discord to simulate it, saves me a lot of time.

The simulation code file is attached to this gist.

Note that, it gives HIGHER values than the old system and the new AIMC system on higher waves. This is intentional. It's more of a bug with the simulation rather than with the mmdts system explained here.

Message me on discord if you can't figure out what exactly in the AIMC simulation system is wrong.

Also, if you want to achieve exact results like the ones in simulation, follow the comment that says:

Change Cv to {700, 830} and Ev to 0.2 for exactly matching statistics.

Here are the simulation results with exactly matching statistics for reference:

If it works, how can I guarantee that, if this works, it will give outcomes comparable to the current system instead of something broken?

Again, simulate it! If you don't want, you could just take my word for it (provable) that, with the proper tweaking of Cv, Lv, Ev, Egv, and Egl - that it would work perfectly.

Remind me again, what was wrong with the original system?

These are probably summaried in the New System section (containing AIMC code), but I'll elaborate here Many things:

  1. It's unpredictable, and therefore is prone to exotic behavior. The following points are examples of this exotic behavior.
  2. It discourages overbetting. A proper system would give less-than-linear returns on overbetting, thus, not discouraging it, but significantly decreasing its effectiveness the more you overbet.
  3. It discourages betting little on the underdog, a behavior that was broken, yet has always been a core part of the game. A proper system would give merit to betting little on the underdog, without being too broken (so it shouldn't steal the jackpot).
  4. It doesn't really help people in a trench recover. Previously, jackpots (and stealing them) was one of the only recovery methods. The current system does so little on helping people in a trench, without much money to bet, recover.
  5. The amount of gold generated by the system is not a function of anything, and cannot be determined before the duel ends. This is a problem because it gives the developer very little control over the balance / growth of the game's economy.

I believe "economy" is an important part of this game:

  1. Economy allows you to change your skills to better ones, and gain advantage over "lucky" people, who rolled good skills initially.
  2. Economy allows you to get stats, and gain advantage over "lucky" people, who rolled heroes with good stat builds.
  3. Economy allows you to buy items, and be able to counter people who are otherwise uncounterable through sheer luck.

Other than the capitalist innate, this game's economy relies heavily on the betting system, and therefore, the betting system should be taken care of, and improved to be as good as it could be - even if it's already good the way it is.

Explain the maths! Why did you choose these specific formulas for the stuffs in the system?

Most of the mathematics is explained in the comments in (New System.Section 2). So I will skip over parameter choice, which is explained thoroughly above, and focus on function choice.

Variable Pot

V = (W+L)**Ev * Cv * Lv**(level - 1)

The function for the variable pot, or generated gold, V, is chosen based on the following two criteria:

  1. It needs to accurately mimic the behavior of the legacy base pot growth with level, therefore the exponential function of level part.
  2. It needs to grow with total amount of bet in a less-than-linear fashion, to create the effect of bet-hype, where the total pot grows the more the bet is hyped. This also allows for overbetting effects. The function (W+L)**Ev is a less than linear function when Ev is less than 1, and it kind-of mimics the shape of root functions, which look like a sleeping parabola.

Share Fraction

J = W == 0 ? 0 : atan(Ja * (W/V - Js) ) / ( Jp * pi) + Jn

The function for the share fraction, or better-winner split, J, is chosen based on the following five criteria:

  1. It needs to increase with winners' bet, such that if the winners bet more, they're bound to take a bigger chunk of the pie.
    • For example, if I bet 20K on someone, I want to take significantly more money than he does. I took the risk, I expect a reward.
    • But if I bet 1 gold on someone and he wins, I expect to not take a big chunk of his otherwise-would've-been jackpot.
  2. It needs to decrease with level, such that winners have to bet higher amounts at higher levels to get the same fraction of winnings.
  3. It needs to be capped. It has to have a hard asymptote at a certain value. Ultimately, it needs to be less than 1 since it is a fraction (otherwise, it would allow stealing from the system by overbetting).
    • The atan function does this perfectly. It is capped at pi, and with slight modification, it can be used to suit our needs.
    • Preferably, the function needs to be less than a certain value, decided by Jp. In our default value of Jp = 1.6, the function is capped to 0.7125.
  4. It needs to be strictly increasing, and needs to have a region that is almost linear.
    • Strictly increasing is so that the case where you "make more money by betting less" cannot exist.
    • Linear region presence is so that you could easily, as a player, grow an intuition on that "if I bet that much more, I'm going to get this much more".
    • Again, the atan function has those properties.
  5. The function needs to be independent of the amount bet by losers, and thus, the total bet amount. This is because we're not competing. If I bet 2000 gold on someone (a big risk there), but there just happens to be a giant gambler betting 20K on the other side, I still should expect to make a good earning compared to that made by the duel winner.

The variables Ja, Js, Jp, and Jn are nothig but function translation and stretching variables.

It is simply a function in the form of C * f(A*x-B) + D in mathematics, where:

  • f is the atan function
  • x is the input to the atan function, which is W/V
  • C and D stretch the function and translate it in the y-axis (share fraction axis)
  • A and B stretch the function and translate it in the x-axis (winners' bet over variable pot)

Better gain

Gb = W * (X / W)
   + V * (J * X**Egv / sum(Xi**Egv))
   + L * (J * X**Egl / sum(Xi**Egl))

The function for the better gain, Gb, represents the total amount of money made by the better (including his original investment).

It is made of 3 terms:

  • W * (X / W), the original investment.
  • V * (J * X**Egv / sum(Xi**Egv)), the better's split of the variable pot
  • L * (J * X**Egl / sum(Xi**Egl)), the better's split of the losers pot

The first term is self-explanatory, and is put so that it becomes impossible to lose money on a bet.

The second term and third term are pretty identical in the formula they use, which is:

and which is just as if I just multiplied V, J and some fraction similar to X / W, but that has some "added communism", due to the fact that root functions (or functions on the form of X**n where n is less than 1) satisfy the three criteria I need in the communist fractional-ratio function:

  1. It has to scale less-than-linearly, and I have to be able to control how much it scales (can be controlled through Egv / Egl here). This is, like explained, to make sure that I have firm control on how much low-gold players can catch up, and to prevent high-gold players from monopolizing the game by constantly overbetting and getting much richer than anyone else can catch up.
  2. It has to be strictly increasing, again, to prevent players from making less money by betting more.
  3. It has to be normalized, that is, it has to be a fraction of V * J and L * J respectively, and summing that fraction for all the different players up should always yield 1. This is the reason for the very complex looking formula with a sum in the denominator, as mathematics has not come up with a solution to find the sum of roots without actually manually summing them up.

Winner gain

Gw = (1 - J) * (V + L)

I'll save myself the explanation and say that it's really, just the rest of the gold. The bet winners, as explained in the proof above, altogether make a sum of W + J * (L + V), and the rest of the W + V + L goes to the duel winner.

This "rest of the money" strategy is in place so that I have prior-knowledge (very friendly to mathematicians and developers alike) of over the total amount of gold that gets generated into the game (and thus the total amounts that can be earned) solely by inspecting the total sum of bets, W + L, without any inclination that is based on how much W is alone, how much L is alone, or what their ratio is. The previous duel system used to generate variable amounts of money afaict.

Thank you!

If you read up to this point, thank you for your patience. I hope this gets implemented, would be fun to see it in action. ^.^

~mmdts

import math
import random
import matplotlib.pyplot as plt
def feedback(_input):
megaresults = {
"averagenw" : round(sum(game["anw"] for game in results)/loops),
"averagemaxnw" : round(sum(game["mnw"] for game in results)/loops),
"averagelevel" : round(sum(game["alevel"] for game in results)/loops),
"averagemaxlevel" : round(sum(game["mlevel"] for game in results)/loops),
"maxaveragenw" : round(max(game["anw"] for game in results)),
"maxmaxnw" : round(max(game["mnw"] for game in results)),
"maxaveragelevel" : round(max(game["alevel"] for game in results)),
"maxmaxlevel" : round(max(game["mlevel"] for game in results)),
}
print(f'''
Average Net Worth: {megaresults["averagenw"]}
Average Highest Net Worth: {megaresults["averagemaxnw"]}
Highest Average Net Worth: {megaresults["maxaveragenw"]}
Highest Net Worth: {megaresults["maxmaxnw"]}
Average Level: {megaresults["averagelevel"]}
Average Highest Level: {megaresults["averagemaxlevel"]}
Highest Average Level: {megaresults["maxaveragelevel"]}
Highest Level: {megaresults["maxmaxlevel"]}
''')
class Player:
def __init__(self):
self.gold = 600
self.items = 0
self.nw = self.gold+self.items
self.alive = True
self.dueling = False
self.bet = 0
self.betOnWinner = False
self.winner = False
self.level = 1
def calcnw(self):
self.nw = self.gold + self.items
def wager(self):
if not self.dueling:
if random.randint(0,3) != 0:
if random.randint(0,2) == 0:
self.bet = math.floor(self.gold/2)
else:
self.bet = random.randint(1,math.floor(self.gold/2))
self.gold -= self.bet
if random.random() > 0.5:
self.betOnWinner = True
else:
self.betOnWinner = False
def __lt__(self, other):
return self.nw < other.nw
def game(mode, submode="amic"):
maxPlayers = 10
level = 1
players = [Player() for _ in range(maxPlayers)]
elim = [] #list for dead players
alive = sum(player.alive for player in players)
kill = random.randint(math.floor(2+(5+level)/level),math.ceil(5+(20+level)/level))
while alive > 1:
bet(players,level,alive,kill,mode,submode)
level += 1
kill -= 1
for player in players:
if not player.dueling:
player.gold += random.randint(1,alive)*math.floor(min(max(300 * 1.04**(level-1),300 + 25 * (level-1)),15000))/alive
player.bet = 0
player.dueling = False
player.betOnWinner = False
player.winner = False
player.calcnw()
networth_at_rounds[level-1].append(player.nw)
if not player.alive:
alive = sum(player.alive for player in players)
player.position = alive + 1
elim.append(player)
players.remove(player)
kill = random.randint(math.floor(2+(5+level)/level),math.ceil(5+(20+level)/level))
for player in players:
player.position = 1
elim.append(player)
players.remove(player)
averages = {
"anw" : round(sum(player.nw for player in elim)/10),
"alevel" : round(sum(player.level for player in elim)/10),
"mnw" : round(max(player.nw for player in elim)),
"mlevel" : round(max(player.level for player in elim)),
}
return averages
def bet(players,level,alive,kill,mode,submode):
if mode == "new":
temp = 1
for player in random.sample(players, 2):
player.dueling = True
if temp == 1:
temp = 0
player.winner = True
del temp
for player in players:
player.level = level
if random.randint(0,3) == 0 and player.gold > 1000:
spent = random.randint(math.floor(player.gold*0.01),math.floor(player.gold*0.85))
player.gold -= spent
player.items += spent
for player in players:
player.wager()
player.calcnw()
if submode == "amic":
basePot = 1350 * 1.02 ** (level - 1)
totalBet = sum(player.bet for player in players)
winnerBet = sum(player.bet for player in players if player.betOnWinner)
totalPot = basePot + totalBet
for player in players:
if player.betOnWinner:
player.gold += math.floor(totalPot * min(1, 0.4 + winnerBet / totalPot * 2.5) * player.bet / winnerBet)
if player.winner:
player.gold += min(totalPot * max(0.5,1 - winnerBet / totalPot * 2.5), 2.2 * basePot + totalBet - winnerBet)
if submode == "mmdts":
solo = True
# Change Cv to {700, 830} and Ev to 0.2 for exactly matching statistics.
Cv = solo and 500 or 592
Lv = 1.021
Ev = 0.25
Egv = 0.8
Egl = 0.5
Jn = 0.4
Js = 0.2
Ja = 10
Jp = 1.6
W = sum(player.bet for player in players if player.betOnWinner)
L = sum(player.bet for player in players if not player.betOnWinner)
V = (W+L)**Ev * Cv * Lv**(level - 1)
if W == 0:
J = 0
else:
J = math.atan(Ja * (W/V - Js) ) / ( Jp * math.pi) + Jn
sigma_Xgv = sum(list(player.bet**Egv for player in players if player.betOnWinner))
sigma_Xgl = sum(list(player.bet**Egl for player in players if player.betOnWinner))
for player in players:
if player.betOnWinner:
player.gold += math.floor(player.bet + J * (V * player.bet**Egv / sigma_Xgv + L * player.bet**Egl / sigma_Xgl))
if player.winner:
player.gold += math.floor((1 - J) * (V + L))
if kill <= 0:
min(players).alive = False
else:
temp = 1
for player in random.sample(players, 2):
player.dueling = True
if temp == 1:
temp = 0
player.winner = True
del temp
for player in players:
player.level = level
if random.randint(0,3) == 0 and player.gold > 1000:
spent = random.randint(math.floor(player.gold*0.01),math.floor(player.gold*0.85))
player.gold -= spent
player.items += spent
for player in players:
player.wager()
player.calcnw()
basePot = 1550 * 1.021 ** (level - 1)
totalBet = sum(player.bet for player in players)
winnerBet = sum(player.bet for player in players if player.betOnWinner)
totalPot = basePot + totalBet
for player in players:
if player.betOnWinner:
player.gold += math.floor(totalPot * min(1, winnerBet / totalPot * 3.75) * player.bet / winnerBet)
if player.winner:
player.gold += min(totalPot * max(0.5,1 - winnerBet / totalPot * 2.5), 2.5 * basePot + totalBet - winnerBet)
if kill <= 0:
min(players).alive = False
def calculate_std(name):
for rnd in range(1, 99):
if len(networth_at_rounds[rnd]) > 0:
a = sum(networth_at_rounds[rnd]) / len(networth_at_rounds[rnd])
networth_avg[rnd] = a
k = []
for j in networth_at_rounds[rnd]:
k.append((j - a) ** 2)
std = (sum(k) / len(networth_at_rounds[rnd])) ** 0.5
networth_std[rnd] = std
loops = 2000
fig, axs = plt.subplots(2)
def plot_std(color):
axs[0].plot(networth_std, color=color)
axs[1].plot(networth_avg, color=color)
networth_at_rounds = [[] for _ in range(1,100)]
networth_std = [0 for _ in range(1,100)]
networth_avg = [0 for _ in range(1,100)]
results = []
for i in range(loops):
results.append(game("old"))
print("old")
feedback(results)
calculate_std("old")
plot_std("red")
for mm in ["amic", "mmdts"]:
networth_at_rounds = [[] for _ in range(1,100)]
networth_std = [0 for _ in range(1,100)]
networth_avg = [0 for _ in range(1,100)]
results = []
for i in range(loops):
results.append(game("new", mm))
print("new " + mm)
feedback(results)
calculate_std("new " + mm)
plot_std(mm == "amic" and "green" or "blue")
plt.show()
from typing import List
from math import atan, pi
def transact(Xw: List[int], Xl: List[int], level: int, solo: bool = True): # We start by a list of the winners' bets, and a list of the losers' bets
L = sum(Xl)
W = sum(Xw)
G = [None] * len(Xw) # Bet gains list to be returned. Has the same size as the winners' bets list.
Cv = solo and 500 or 592
Lv = 1.021
Ev = 0.25
Egv = 0.8
Egl = 0.5
Jn = 0.4
Js = 0.2
Ja = 10
Jp = 1.6
V = (W+L)**Ev * Cv * Lv**(level - 1)
if W == 0:
J = 0
else:
J = atan(Ja * (W/V - Js) ) / ( Jp * pi) + Jn
Xgv = list(Xi**Egv for Xi in Xw)
Xgl = list(Xi**Egl for Xi in Xw)
for i, X in enumerate(Xw, 0): # for each winner
G[i] = int(X + J * (V * X**Egv / sum(Xgv) + L * X**Egl / sum(Xgl)))
Gw = (1 - J) * (V + L)
return J, int(V), G, int(Gw), 2.5 * 1350 * 1.02 ** (level - 1)
print("OUTPUT: ", transact([1000, 2000, 1400], [100, 140], level=19, solo=True))
print("OUTPUT: ", transact([1000, 20000, 1400], [100, 140], level=19, solo=True))
print("OUTPUT: ", transact([100, 140], [1000, 2000, 1400], level=19, solo=True))
print("OUTPUT: ", transact([1000, 1400], [1000, 2000, 1400], level=19, solo=True))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment