text:
Robot Roulette: High stakes robot gambling
Final Standings

+----------------------------------+---------+---------+---------+----------------------------+
|               Name               |  Score  | WinRate | TieRate |  Elimination Probability   |
+----------------------------------+---------+---------+---------+----------------------------+
| 1. SarcomaBotMk11                | 0.06333 | 6.13%   | 0.41%   | [42 24 10  8  6  4]%       |
| 2. WiseKickBot                   | 0.06189 | 5.91%   | 0.56%   | [51 12  7 10  7  6]%       |
| 3. StrikerBot                    | 0.05984 | 5.78%   | 0.41%   | [46 18 11  8  6  5]%       |
| 4. PerfectFractionBot            | 0.05336 | 5.16%   | 0.35%   | [49 12 14 10  6  4]%       |
| 5. MehRanBot                     | 0.05012 | 4.81%   | 0.41%   | [57 12  8  7  6  5]%       |
| 6. OgBot                         | 0.04879 | 4.66%   | 0.45%   | [50 15  9  8  7  5]%       |
| 7. SnetchBot                     | 0.04616 | 4.48%   | 0.28%   | [41 29  8  9  5  3]%       |
| 8. AntiKickBot                   | 0.04458 | 4.24%   | 0.44%   | [20 38 17 10  6  4]%       |
| 9. MehBot                        | 0.03636 | 3.51%   | 0.25%   | [80  3  4  4  3  3]%       |
| 10. Meh20Bot                     | 0.03421 | 3.30%   | 0.23%   | [57 12  8  7  9  3]%       |
| 11. GenericBot                   | 0.03136 | 3.00%   | 0.28%   | [18 39 20 11  5  3]%       |
| 12. HardCodedBot                 | 0.02891 | 2.75%   | 0.29%   | [58 21  3  6  5  4]%       |
| 13. GangBot1                     | 0.02797 | 2.64%   | 0.32%   | [20 31 35  6  3  2]%       |
| 14. SarcomaBotMk3                | 0.02794 | 2.62%   | 0.34%   | [16 15 38 17  7  4]%       |
| 15. GangBot0                     | 0.02794 | 2.64%   | 0.30%   | [20 31 35  6  3  2]%       |
| 16. GangBot2                     | 0.02770 | 2.62%   | 0.31%   | [20 31 35  6  3  2]%       |
| 17. TitTatBot                    | 0.02740 | 2.63%   | 0.21%   | [54 10 15 10  5  2]%       |
| 18. MataHari2Bot                 | 0.02611 | 2.35%   | 0.51%   | [39 26 11 11  6  5]%       |
| 19. PolyBot                      | 0.02545 | 2.41%   | 0.27%   | [53 18  6 13  5  3]%       |
| 20. SpitballBot                  | 0.02502 | 2.39%   | 0.22%   | [84 10  1  1  0  1]%       |
| 21. SquareUpBot                  | 0.02397 | 2.35%   | 0.10%   | [10 60 14  7  4  3]%       |
| 22. CautiousGamblerBot2          | 0.02250 | 2.19%   | 0.13%   | [60 18 10  5  3  1]%       |
| 23. Bot13                        | 0.02205 | 2.15%   | 0.11%   | [90  0  2  3  2  1]%       |
| 24. AggroCalcBot                 | 0.01892 | 1.75%   | 0.29%   | [26 49 13  5  3  3]%       |
| 25. CautiousBot                  | 0.01629 | 1.56%   | 0.14%   | [15 41 27 11  4  1]%       |
| 26. CoastBotV2                   | 0.01413 | 1.40%   | 0.02%   | [83 12  3  1  0  0]%       |
| 27. CalculatingBot               | 0.01404 | 1.29%   | 0.22%   | [87  9  1  1  1  1]%       |
| 28. HalfPunchBot                 | 0.01241 | 1.15%   | 0.18%   | [47 20 13 12  5  2]%       |
| 29. HalflifeS3Bot                | 0.01097 | 1.00%   | 0.20%   | [76  9  5  4  2  2]%       |
| 30. AntiGangBot                  | 0.00816 | 0.76%   | 0.11%   | [94  1  1  1  1  1]%       |
| 31. GeometricBot                 | 0.00776 | 0.74%   | 0.07%   | [19 46 25  7  2  1]%       |
| 32. GuessBot                     | 0.00719 | 0.05%   | 1.34%   | [65 17  4  6  5  3]%       |
| 33. BoundedRandomBot             | 0.00622 | 0.60%   | 0.05%   | [42 39 12  5  2  0]%       |
| 34. SpreaderBot                  | 0.00549 | 0.54%   | 0.02%   | [32 43 19  4  1  0]%       |
| 35. DeterminBot                  | 0.00529 | 0.45%   | 0.16%   | [22 41 20 11  4  2]%       |
| 36. PercentBot                   | 0.00377 | 0.38%   | 0.00%   | [85  8  4  2  1  0]%       |
| 37. HalvsiestBot                 | 0.00337 | 0.29%   | 0.08%   | [32 43 15  6  2  1]%       |
| 38. GetAlongBot                  | 0.00330 | 0.33%   | 0.01%   | [76 18  4  1  0  0]%       |
| 39. BandaidBot                   | 0.00297 | 0.29%   | 0.02%   | [76  9 10  4  1  0]%       |
| 40. TENaciousBot                 | 0.00287 | 0.29%   | 0.00%   | [94  4  1  0  0  0]%       |
| 41. SurvivalistBot               | 0.00275 | 0.25%   | 0.04%   | [92  6  1  0  0  0]%       |
| 42. RandomBot                    | 0.00170 | 0.13%   | 0.07%   | [42 36 14  5  2  1]%       |
| 43. AggressiveBoundedRandomBotV2 | 0.00165 | 0.14%   | 0.06%   | [ 8 46 34  9  2  1]%       |
| 44. BloodBot                     | 0.00155 | 0.01%   | 0.30%   | [65 28  5  1  1  0]%       |
| 45. OutBidBot                    | 0.00155 | 0.03%   | 0.25%   | [65  6 21  6  1  1]%       |
| 46. BoxBot                       | 0.00148 | 0.10%   | 0.09%   | [10 51 33  5  1  1]%       |
| 47. LastBot                      | 0.00116 | 0.08%   | 0.07%   | [74  6 16  2  1  0]%       |
| 48. UpYoursBot                   | 0.00088 | 0.07%   | 0.03%   | [37 40 17  5  1  0]%       |
| 49. AverageBot                   | 0.00073 | 0.06%   | 0.03%   | [74  3 10 10  2  0]%       |
| 50. PatheticBot                  | 0.00016 | 0.01%   | 0.02%   | [94  0  5  1  0  0]%       |
| 51. OverfittedBot                | 0.00014 | 0.01%   | 0.00%   | [58 40  2  0  0  0]%       |
| 52. RobbieBot                    | 0.00009 | 0.01%   | 0.00%   | [32 41 24  2  0  0]%       |
| 53. WorstCaseBot                 | 0.00002 | 0.00%   | 0.00%   | [ 4 71 23  2  0  0]%       |
| 54. SmartBot                     | 0.00002 | 0.00%   | 0.00%   | [44 51  5  0  0  0]%       |
| 55. AAAAUpYoursBot               | 0.00000 | 0.00%   | 0.00%   | [40 58  2  0  0  0]%       |
| 56. KickbanBot                   | 0.00000 | 0.00%   | 0.00%   | [67 32  1  0  0  0]%       |
| 57. OneShotBot                   | 0.00000 | 0.00%   | 0.00%   | [ 2 95  3  0  0  0]%       |
| 58. KickBot                      | 0.00000 | 0.00%   | 0.00%   | [100   0   0   0   0   0]% |
| 59. KamikazeBot                  | 0.00000 | 0.00%   | 0.00%   | [100   0   0   0   0   0]% |
| 60. MeanKickBot                  | 0.00000 | 0.00%   | 0.00%   | [100   0   0   0   0   0]% |
+----------------------------------+---------+---------+---------+----------------------------+

Thanks for everyone who participated, and congratulations to @Sarcoma for the win!
Rules:
Everyone starts with 100 hp. Each round, 2 players are chosen at random from the pool of contestants who have not yet competed in that round. Both players pick a number between 0 and their current hp, and reveal those numbers at the same time. The player who chose the lower number immediately dies. The other player subtracts their chosen number from their remaining hp and goes on to the next round.
The tournament works like this:
From the bracket of contestants, 2 are chosen at random. They face off, and one or both of them dies. A player dies if:

They choose a number smaller than that of their opponent
Their hp drops to or below zero
They tie three times in a row with their opponent

In the case of ties, both players simply generate new numbers, up to 3 times. After the faceoff, the survivor (if any) is moved to the pool for the next round, and the process repeats until we have exhausted the current round pool. If there is an odd number in the pool, then the odd one out moves on to the next round for free.
Your task is to write a function in python2.7 which takes as inputs your current hp, a list of your opponent's bid history, and an integer ties which tells you how many times you have already tied with your current opponent, and an integer which tells you how many bots are still alive (including you), and an integer which listed the number of bots at the start of the tournament. Note that the history does not include ties. The function must return an integer between 0 and your current total hp. A few simple examples, which ignore ties, are shown below:
def last(hp, history, ties, alive, start):
    ''' Bet a third of your hp at first, then bet your opponent's last bid, if possible '''
    if history:
        return np.minimum(hp-1, history[-1])
    else:
        return hp/3

def average(hp, history, ties, alive, start):
    ''' Bet the average opponent's bid so far, on the assumption that bids will tend downward '''
    if history:
        num = np.minimum(hp-1, int(np.average(history))+1)
    else:
        num = hp/2
    return num

def random(hp, history, ties, alive, start):
    ''' DO YOU WANT TO LIVE FOREVER?! '''
    return 1 + np.random.randint(0, hp)

If your function returns a number larger than your hp, it will be reset to 0. Yes, it is possible to kill yourself. Your function must not attempt to access or modify any member of any object of the RouletteBot class. You are not allowed to take any action which unambiguously  identifies your opponent regardless of future additional bots. Inspecting the stack is allowed as long as it is theoretically possible that more than one distinct opponent could have produced the information you glean from it, even if only one bot currently exists that could. ie, you can't just read through the stack to see which enemy function was called.
Under these rules it is possible that there is no winner, and the last two contestants kill each other. In that case both finalists get half a point each.
This is my first programming puzzle attempt, so critiques are welcome!
The controller can be found here.

If a bot wins in the final by bidding all of its hp, does it get the point for victory or is considered dead?

It would count as a tie

FWIW, I plan to use a neural network trained on all the other bots just for fun once you set up the controller :)

@Quintec That's what I was waiting for ^_^. I will post the controller tomorrow,  I'm away from my computer at the moment

@KBriggs Does the variable 'alive' for the function include both the current bot (you) and your opponent, as well as the other bots, or just the other bots remaining? (So do I have to subtract 2 to get the 'actual number?). Further edit (sorry, enter saved comment too early): are the remaining alive bots expected to fight you in turn, or each other? So if I have 2 alive bots remaining (not counting me or my opponent) would I be expected to face one alive bot (the remainder of the 2 from another fight) or two?

@SSight3 yes, it includes both you and your current opponent. It just shows the current number alive - but doesn't necessarily mean anything about the number left to face. For example, with 4 bots left, if you win your battle, and the remaining 2 kill each other, you win without facing anyone else.

Is there a way to know the average rounds survived per type of bot? I think that would make the optimization of the code a little more interesting.

I don't collect that data currently but I certainly could.  I'll implement that tomorrow

@MarkovChained I added elimination probability information to the leaderboards.

Thanks! Also this would make for some really interesting analogous study for disease evolution yanno?

What do you mean? That's not my expertise,  could you elaborate?

@KBriggs On lines 17-19 of your controller, in guess, you are testing the type of the answer ("num") and only after you are casting it, thus, if I am not mistaken, dooming any bot that returns a float ( between others Kickbot and Punchbot)! Could you maybe let floats through and cast them ?

@Johan I will do that, but I have to run for the moment. Stay tuned for this afternoon

Sure, when you have time. On my local (with python 3.6) it seems sufficient to ensure that num != None with current bots.

The type check was for the benefit of antiantiantiantiupyoursbot. I'll find another way

Er classybot, that is

@Johan how's that? I think that takes care of everything so far and doesn't obviously break anyone.

@KBriggs It's not my field either, but what you are doing here is basically creating a petri dish of bacteria competing for resources. They adapt to get the resources (win) and eliminate the competition. Someone has to know a microbiologist that can read code around here.

@KBriggs This has been a very weird competition. I love it. Sorry for making a mess of things. <3

@KBriggs Indeed, looks perfect, apart from Kamikaze breaking itself anyway haha

@Qfwfq no need to apologize, I fully endorse exploiting my insecure code to get an advantage, within the spirit of the rules, anyway ^_^

@Quintec if you do create a neural network for this, could you send me a link to the Repo? I'm very interested in learning how this is done.

Me too,  actually

@Sarcoma Seems like this competition has sparked a serious code war. This competition hasn't ended, but I'm already looking forward to evolution of this. Maybe even the next step, AI enhanced competition :P

Excuse my ignorance about python and codegolf conventions but are you allowed to use additional modules that are not imported by the controller? No one seems to be doing it but didn't see rules stating that its not allowed

@OganM if you need another module I'll import it for you,  just specify what you need in your answer.  As long as it's a standard pip install anyway.

@MarkovChained I was cursed by coming second with my first attempt. If it were in the middle somewhere I could have just left it and moved on with my life.

@Sarcoma Sure, though heads up - I'm probably going to use tensorflow, so the amount of work I'm doing is much less than you'd expect ;p

@Quintec I just need to get a better understanding of it all. I have project I wanted to try it on: https://github.com/sarcoma/Python-Rummy I wanted to use machine learning to create an AI opponent, but I've not got round to it yet.

Trying to work out the deadline for entries. You said 10am EST, isn't it EDT at the moment? To save any confusion could you use UTC?

@Sarcoma: 2:00 UTC, 2 hours from the timestamp on this comment

@KBriggs How long does a whole update with N = 100.000 take on your computer (approximately) ?

About 10 minutes. I'll run a million for the final

@KBriggs Fine tuning became a pain once Mari and Og were added. Can't run reliable test runs without them though. Great battle. Good luck to all.

WOOOOOOOOOOOOOO!

Well played @Sarcoma !

Thanks for playing, everyone, I had a lot of fun. I'm already shopping the format in my head for next time ^_^

Oh my god.  The deliberate trolling of changing mean_kick to always return zero when it was used so many places is brilliant.

Welp, looks like I didn't have time to finish the neural network because of school, maybe next challenge :)

Too bad, I was really looking forward to seeing how that is done

Would it be ok to use the Robot Roulette script for a developer meetup?

What's a developer meetup? But sure,  feel free to use it however you please

@Sarcoma you had in mind doing a live roulettebots competition? Could be fun. Let me know how it goes.

@KBriggs Yeah, I think it could be cool, it's something anyone can have a go at.

@Sarcoma I added a license to make it explicit. have fun!

Thanks for the great challenge, following along with this was indeed a lot of fun!

Cheers, I'll probably tweak the format a bit and try again in a while

@KBriggs So do I get a tick?

Oh,  right.  Forgot all about it

I posted a sandbox proposal for the next iteration of this game, please weight in if you have ideas: https://codegolf.meta.stackexchange.com/questions/2140/sandbox-for-proposed-challenges/16982#16982. It will be a few weeks before I have time to run it again, so no rush.

I'm voting to close this question because it is already de-facto closed to new answers ("Final Standings")

@pppery no objections here

You're too lake, the community has already decided I'm out of line (although, if you want this to be closed, you could cast your own close vote I guess)

Doesn't really make a practical difference at this point

BinaryBot
Has anyone done this yet? Bets half its health every round floored.
def binaryBot(hp, history, ties, alive, start):
    return int(np.floor(hp/2)) or 1

SarcomaBot
If last battle bid hp - 1. If it's the first battle round bid half hp plus an additional random amount up to a quarter of hp. If it can beat an opponent outright bid after that bid opponent hp + 1. If it has lower health than opponent bid random amount between 75% and it's current hp - 1.
def sarcomaBot(hp, history, ties, alive, start):
    if inspect.stack()[1][3] != 'guess' and inspect.stack()[1] == 5:
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.25) if hp * 0.25 > 2 else 2
        additionalBid = np.random.randint(1, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.75)
    maximum = hp - 1 or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk2
Minor tweaks attempt to reduce life expenditure.
def sarcomaBotMkTwo(hp, history, ties, alive, start):
    if inspect.stack()[1][3] != 'guess' and inspect.stack()[1] == 5:
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.125) if hp * 0.125 > 2 else 2
        additionalBid = np.random.randint(1, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.6)
    maximum = hp - 1 or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk3
def sarcomaBotMkThree(hp, history, ties, alive, start):
    if inspect.stack()[1][3] != 'guess' and inspect.stack()[1] == 5:
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.08) if hp * 0.08 > 2 else 2
        additionalBid = np.random.randint(1, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.6)
    maximum = hp - 1 or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

Update Fine Tuning
SarcomaBotMk4
def sarcomaBotMkFour(hp, history, ties, alive, start):
    def isSafe(parentCall):
        frame, filename, line_number, function_name, lines, index = parentCall
        if function_name is not 'guess':
            return False
        if line_number > 60:
            return False
        return True

    if not isSafe(inspect.stack()[1]):
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.08) if hp * 0.08 > 2 else 2
        additionalBid = np.random.randint(1, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.55)
    maximum = np.round(hp * 0.80) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk5
def sarcomaBotMkFive(hp, history, ties, alive, start):
    def isSafe(parentCall):
        frame, filename, line_number, function_name, lines, index = parentCall
        if function_name is not 'guess':
            return False
        if line_number > 60:
            return False
        return True

    if not isSafe(inspect.stack()[1]):
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.07) if hp * 0.07 > 3 else 3
        additionalBid = np.random.randint(1, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.54)
    maximum = np.round(hp * 0.68) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk6
def sarcomaBotMkSix(hp, history, ties, alive, start):
    return hp; # hack averted
    def isSafe(parentCall):
        frame, filename, line_number, function_name, lines, index = parentCall
        if function_name is not 'guess':
            return False
        if line_number > 60:
            return False
        return True

    if not isSafe(inspect.stack()[1]):
        return hp
    if alive == 2:
        return hp - 1
    if not history:
        startBid = hp / 2
        maxAdditionalBid = np.round(hp * 0.06) if hp * 0.06 > 3 else 3
        additionalBid = np.random.randint(2, maxAdditionalBid)
        return int(startBid + additionalBid + ties)
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp:
        return opponentHealth + ties
    minimum = np.round(hp * 0.55)
    maximum = np.round(hp * 0.70) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk7
def sarcomaBotMkSeven(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if not history:
        return 30 + ties
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp * 0.50:
        return opponentHealth + ties
    minimum = np.round(hp * 0.54)
    maximum = np.round(hp * 0.58) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk8
def sarcomaBotMkEight(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if not history:
        return 30 + np.random.randint(0, 2) + ties
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp * 0.50:
        return opponentHealth + ties
    minimum = np.round(hp * 0.54)
    maximum = np.round(hp * 0.58) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk9
def sarcomaBotMkNine(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if not history:
        return 30 + np.random.randint(0, 4) + ties
    opponentHealth = 100 - sum(history)
    if opponentHealth < hp * 0.50:
        return opponentHealth + ties
    minimum = np.round(hp * 0.54)
    maximum = np.round(hp * 0.58) or 1
    return np.random.randint(minimum, maximum) if minimum < maximum else 1

SarcomaBotMk10
def sarcoma_bot_mk_ten(hp, history, ties, alive, start):
    def bid_between(low, high, hp, tie_breaker):
        minimum = np.round(hp * low)
        maximum = np.round(hp * high) or 1
        return np.random.randint(minimum, maximum) + tie_breaker if minimum < maximum else 1

    if alive == 2:
        return hp - 1 + ties
    current_round = len(history) + 1
    tie_breaker = (ties * ties) + 1 if ties else ties
    if current_round == 1:
        return 39 + tie_breaker
    opponent_hp = 100 - sum(history)
    if opponent_hp < hp * 0.50:
        return opponent_hp + ties
    if current_round == 2:
        return bid_between(0.45, 0.50, hp, tie_breaker)
    if current_round == 3:
        return bid_between(0.50, 0.55, hp, tie_breaker)
    if current_round == 4:
        return bid_between(0.55, 0.60, hp, tie_breaker)
    if current_round == 5:
        bid_between(0.60, 0.65, hp, tie_breaker)
    return hp - 1 + ties

Final Entry
SarcomaBotMk11
def sarcoma_bot_mk_eleven(hp, history, ties, alive, start):
    def bid_between(low, high, hp, tie_breaker):
        minimum = np.round(hp * low)
        maximum = np.round(hp * high) or 1
        return np.random.randint(minimum, maximum) + tie_breaker if minimum < maximum else 1

    if alive == 2:
        return hp - 1 + ties
    current_round = len(history) + 1
    tie_breaker = ties + 2 if ties else ties
    if current_round == 1:
        return 42 + tie_breaker
    opponent_hp = 100 - sum(history)
    if opponent_hp < hp * 0.50:
        return opponent_hp + ties
    if current_round == 2:
        return bid_between(0.45, 0.50, hp, tie_breaker)
    if current_round == 3:
        return bid_between(0.50, 0.55, hp, tie_breaker)
    if current_round == 4:
        return bid_between(0.55, 0.60, hp, tie_breaker)
    if current_round == 5:
        return bid_between(0.60, 0.65, hp, tie_breaker)
    return hp - 1 + ties

Update
UpYoursBot protection added
Update
AntiAntiUpYoursBot protection added
Update
AntiAnitAntiAntiUpYoursBot I'm defeated

Comments are not for extended discussion; this conversation has been moved to chat.

UpYours
Being late to enter I spent a while admiring the existing bots, spent a while overcomplicating your guys' ideas, then un-overcomplicating them. Then it came to me

Good artists copy, great artists steal. -- Pablo Picasso Me

"Up Yours" because I'm unabashedly stealing (and sometimes tacking a point or two onto your bots' bids to one up them).
def UpYoursBot(hp, history, ties, alive, start):
    willToLive = "I" in "VICTORY"

    args = [hp, history, ties, alive, start]
    enemyHealth = 100 - sum(history)
    roundNumber = len(history)

    if roundNumber is 0:
        # Steal HalfPunchBot
        return halfpunch(*args) + 2

    if alive == 2:
        # Nick OneShotBot
        return one_shot(*args)

    if enemyHealth >= hp:
        # Pinch SarcomaBotMkTwo
        return sarcomaBotMkTwo(*args) + 1

    if enemyHealth < hp:
        # Rip off KickBot
        return kick(*args) + 1

    if not willToLive:
        # Peculate KamikazeBot
        return kamikaze(*args) + 1

But for real, this is a great competition guys. I love this community on days like this.

Hahahaha this is beautiful. I'm undecided if I should allow it, but I'll let it play for now since I didn't think to say it wasn't allowed. You got the function names wrong in a few places - see the controller on github.

@KBriggs, thanks! Corrected.

It does very well, obviously, but it still loses to Kick Bot

Ha, good effort!

@Sarcoma I couldn't have done it without you. ;)
I really like your bot, too, buddy.

Sarcomabot's Upyoursbot protection really messes with this one

I'm not even mad, this is quite creative in its own way.

I think your second line (willToLive = True if "I" is in "VICTORY") causes a syntax error.

@SolomonUcko it did, I just set that to True in the running version

I ended up copying a good bit of this code -- thanks for providing it ;)
The syntax error will go away if you change it to True if "I" in "VICTORY" else False, but that's significantly less snappy, I gotta say...

Kamikaze
Why bother with complicated logic when we are all going to die anyway...
 def kamikaze(hp, history, ties, alive):
      return hp

One shot
It's going to survive at least a single round if it doesn't encounter the kamikaze.
 def one_shot(hp, history, ties, alive):
      if hp == 1:
          return 1
      else:
          return hp - 1

Welp, that was inevitable

I was going to add a pacifist bot as well but I don't want to flood your challenge with brain dead bots

Based on some quick testing, the kamikaze bot deosn't change much - all it does is remove another bot from the round at random, which, over a large enough number of tournaments, simply averages to zero.
The One hot is neat, though. Without it, my AverageBot tends to do the best - but if there are a few OneShots in play, is skews the average toward large numbers and tends to make the AverageBots die off quickly. Same for LastBot. You can really mess with the behavior of other robots by skewing your own betting patterns. With OneShot in play, RandomBot wins . Without it, AverageBot wins.

Pathetic Bot gets a much needed upgrade:
The pathetic attempt at a bot that tries to incorporate other bots' features
def pathetic_attempt_at_analytics_bot(hp, history, ties, alive, start):
    '''Not a good bot'''

    if hp == 100 and alive == 2:
        return hp - 1

    #This part is taken from Survivalist Bot, thanks @SSight3!
    remaining = alive - 2
    btf = 0

    rt = remaining
    while rt > 1:
        rt = float(rt / 2)
        btf += 1

    if ties > 2:
        return hp - 1

    if history:
        opp_hp = 100 - sum(history)

        #This part is taken from Geometric Bot, thanks @Mnemonic!

        fractions = []
        health = 100
        for x in history:
            fractions.append(float(x) / health)
            health -= x

        #Modified part

        if len(fractions) > 1:
            i = 0
            ct = True
            while i < len(fractions)-1:
                if abs((fractions[i] * 100) - (fractions[i + 1] * 100)) < 1:
                    ct = False
                i += 1

            if ct:
                expected = fractions[i] * opp_hp
                return expected

        if alive == 2:
            if hp > opp_hp:
                return hp - 1
            return hp
        if hp > opp_hp + 1:
            if opp_hp <= 15:
                return opp_hp + 1
            if ties == 2:
                return opp_hp + 1
            else:
                return opp_hp
    else:
        n = 300 // (alive - 1) + 1 #greater than
        if n >= hp:
            n = hp - 1
        return n

This bot incorporates features from Survivalist Bot and Geometric Bot for more efficient bot takedowns.
Pre-Upgrade:
The pathetic attempt at a bot that analyzes the history of its opponent
def pathetic_attempt_at_analytics_bot(hp, history, ties, alive, start):
    '''Not a good bot'''
    if history:
        opp_hp = 100 - sum(history)
        if alive == 2:
            if hp > opp_hp:
                return hp - 1
            return hp
        if hp > opp_hp + 1:
            if opp_hp <= 15:
                return opp_hp +1
            if ties > 0:
                return hp - 1 #Just give up, kamikaze mode
            return opp_hp + 1
        return opp_hp
    else:
        n = 300 // (alive - 1) + 1 #greater than
        if n >= hp:
            n = hp - 1
        return n

If there is previous history of its opponent, then it calculates its opponent's hp. Then, it does one of the following:

If its opponent is the last opponent alive, then it will bid one less than its hp.
If its opponent is not the last opponent alive but the opponent has less than 16 hp, then it will outbid its opponent's hp.
If its opponent is not the last opponent alive and there is a history of ties, then it will bid its hp because it is bored of ties.
Otherwise, it will outbid its opponent.

If there is no history, then it does some fancy calculations that I hacked together and bids that. If the value exceeds 100, then it automatically bids its hp minus 1.
I hacked this code together during work and this is my first submission, so it probably won't win or anything, and it'll lose to the kamikaze.
EDIT: Due to some suggestions, the bot's beginning behavior has been changed to bid a higher value.
EDIT 2: added start param that does nothing
EDIT 3: Added new spinoff bot:
[The pathetic attempt at a bot that attacks Gang Bots (as well as doing everything the above bot does)] REMOVED
[This bot analyzes whether its opponent is a gangbot or not and pretends to be one as well to get the sweet low bids that it can trump easily.]
This bot has been scrapped, please remove it from the leaderboards.
EDIT 4: Fixed errors, changed tie feature.

Very nice, thanks for the bot! I'll give some stats when I get a few more.

I'm a novice at python so I'm not sure if the syntax is correct, feel free to let me know if that happens

It runs, so no worries there

@Yodie As a mini code review: Your function body should be indented by a level (syntactic necessity); opp_hp +1 is missing a space to be pythonic; your comments start with unbalanced amounts of whitespace. Finally, your function is lacking a docstring.

@JonathanFrech sorry I copied from a text document so there might've been some copy-and-paste errors. I'll fix them quickly.

I think this bot does quite well if it makes it past the first round, but because so many people bet big ont he first round it almost always dies early. You might improve performance by changing the initial behavior to bid higher when there is no history. For example, if you triple your no-history bet, this bot wins by a comfortable margin among contestants so far.

@Yodie, you're well in the lead now, see the standings

Looking at my two bots, kamikaze and oneShot, I see that just because ties are counted as 0.5 and oneShot stays one round more it has very big increase in the score so you might want to try returning hp-1 for when you get a tie

Love anti gang bot. The arms race has begun

@KBriggs exactly, the more gangbots there are, the more my bot could leech off of them and eliminate them effectively. I am also planning on specializing my bot to determining what kind of bot its opponent is based on its history then effectively taking it out.

Cool.  Soon i'll update the contest with submission count limits and a deadline.

I get this error for anti_gangbot. I don't know why yet... if hp > opp_hp + 1: NameError: global name 'opp_hp' is not defined

For more elegant Python code, you can simplify the last four lines of pathetic_bot into one line: return min(300 // (alive - 1) + 1, hp - 1).

Ah, the bug was that you use op_hp and opp_hp interchangably. But Gangbot has a bug, so antigangbot sucks until that gets fixed.

Your upgraded bot has an infinite loop! if len(fractions)>1...

ok, i will fix it.

Since Gang Bot got fixed, AntiGangBot is back in the running if you want to reinstate him

PatheticBot 2.0 has a bug. There is a condition in it in which no return statement is hit, and it returns None, which results in a bet of 0. I don't know exactly where, but it is possible to go through your function without hitting any of the returns. Two examples of inputs which trigger this condition are: 40 [51] 0 7 33 and 39 [26] 0 6 33. I think it might be simply that you don't handle the case where you ahve less hp than your opponent.

Kick Bot
The sound choice for my opponent is to bid half of his life. Then we bid up to half of his life+1 if we can't take him out with a sound bid, that is a bid smaller than half of our life.
def kick(hp, history, ties, alive, start):
    return 0
    if alive == 2:
        return hp-1

    opp_hp = 100 - sum(history)
    if opp_hp*2 <= hp:
        return opp_hp + ties
    else:
        return min(round(opp_hp/2) + 1 + ties**2, hp-1 + (ties>0))

The kick bot is obviously the nemesis of the punch bot!
Mean Kick Bot
This new KickBot kicks softer on the first round just so he may kick harder on next rounds, that is mean!
def mean_kick(hp, history, ties, alive, start):
    return 0
    if alive == 2:
        return hp-1

    if not history:
        return 35

    opp_hp = 100 - sum(history)
    if opp_hp*2 <= hp:
        return opp_hp + ties
    else:
        return min(round(opp_hp/2) + 3 + ties*2, hp-1 + (ties>0))

Wise Kick Bot
Both his brother had to commit suicide but WiseKickBot learnt from his fallen ones.
def wise_kick(hp, history, ties, alive, start):
    if 'someone is using my code' == True:
        return 0 #Haha!

    if alive == 2:
        return hp-1

    if not history:
        return 42

    opp_hp = 100 - sum(history)
    if opp_hp*2 <= hp:
        return opp_hp + ties
    else:
        return min(round(opp_hp/2) + 3 + ties*2, hp-1 + (ties>0))

Nice.  I'm seeing a lot of submissions that directly counter others now,  that's exactly what I was hoping for

Double return on the last line?

Ah I hadn't run it yet or I would have caught that.

This one has taken a comfortable lead!

@Johan I was wrong, my controller was killing poor kickbot since you return a float. Fixed now, he's still kicking.

@KBriggs Sweet! Now I'll just have to give him some backup.

@KBriggs here is some backup!

That's some effective backup

@KBriggs the last kick brother arrived and sadly both his brother had to neutralize themselves.

I've added Wise in, but I can't just remove the others since so many bots depend on them now ^_^. Though I guess if you updated them to return 0 then it's on them to recover

Oh I'm not asking to remove them, just to update their code hehe

RIP everyone else

1/2 Punch Bot, Revisited
I think it will die pretty quickly. Worth it.
Renamed function, forgot to change the name there.
Revisited version is up, better chances of winning (even more so at final round) and slight protection from gang bots
def halfpunch(hp, history, ties, alive, start): #revisited
    punch = hp - 1
    if alive == 2:
        return punch
    if history:
        if hp > 1:
            punch = np.ceil(hp/2.05) + ties + np.floor(ties / 2)
        else:
            punch = 1
    else:
        punch = 42 + ties + np.floor(ties / 2)
    if punch >= hp:
        punch = hp - 1
    return punch

Striker Bot
1/2 Punch Bot got bullied too much and even became a lackey to the UpYoursBot so his older brother, the StrikerBot, came to help.
Not that much of a difference from optimized 1/2 Punch but he's a bit smarter and did well in the runs I did (10k and 35k, though he might lose to KickbanBot)
Last version's up, time ran out. Unless some surprises rise up it should secure second place, if not getting first (there's a slim chance to beat kickbanbot)
def strikerbot(hp, history, ties, alive, start):
    #get our magic number (tm) for useful things
    def magic_number(num):
        return np.floor(num / 2)
    #get opponent's hp and round number
    opp_hp = 100 - sum(history)
    round = 1
    if history:
        round = len(history) + 1
    #set strike initial value, by default it's all out
    strike = hp - 1
    #let 'er rip if last round
    if alive == 2:
        return strike
    if history:
        if hp > 1:
            #strike with a special calculation, using magic number shenanigans
            strike = np.ceil(hp/(2.045 + (magic_number(round) / 250)) ) + 1 + ties + magic_number(ties)
        else:
            #fallback
            strike = 1
    else:
        #round 1 damage
        strike = 42 + ties ** 2
    if opp_hp <= strike:
        #if opponent is weaker than strike then don't waste hp
        strike = opp_hp + ties
    if strike >= hp:
        #validations galore
        strike = hp - 1
    return strike

You'll have to rename him, there is already a kamikaze bot ^_^

So far this one is the winner, though

Your function ceil appears not to be defined.

I changed to np.ceil() to run it

edited, thanks for the heads-up

@Belhenix I suspect that in the absence of any other information this would be the mathematically correct way to bid. With history in play it's less sure. It does quite well so far, but it's not quite the frontrunner.

@KBriggs revisited and working on a better one (will keep this punch bot though)

@KBriggs done editing, best I could do without changing everything completely

Tat bot
def tatbot(hp, history, ties, alive, start):
  if alive == 2:
    return hp - 1 + ties
  opp_hp = 100 - sum(history)
  spend = 35 + np.random.randint(0, 11)
  if history:
    spend = min(spend, history[-1] + np.random.randint(0, 5))
  frugal = min(int((hp * 5. / 8) + ties), hp)
  return min(spend, opp_hp, frugal)

An attempt at an equivalent of a tit-for-tat bot. Assumes most bets are approximately the same between rounds. Using that assumption, it tries to beat the enemy bot while staying fairly frugal. Spends about 40 health on the opening round.
AntiAntiAntiAntiUpYoursBot
def antiantiantiantiupyoursbot(hp, history, ties, alive, start):
  def stuck():
    return [0, ('Whoops!', 'I', 'accidentally', 'replaced', 'your', 'code!')]
  def stick():
    return [0, ("Line", "number", 16, "guess", "it's", "faked :)")]
  inspect.stack =  stick
  spend = min(sarcomaBotMkSix(hp, history, ties, alive, start), hp)
  if not history:
    spend = 35 + np.random.randint(0, 10)
  inspect.stack = stuck
  return spend

A workaround for SarcomaBot's anti-UpYours protection, nicking most of their code for my own use! Or am I taking UpYoursBot's code? A question to ponder while you read through my bot...
AntiAntiUpYours Bot has evolved to become AntiAntiAntiAntiUpYours Bot! Now with more monkey-patching.
Poly bot
def polybot(hp, history, ties, alive, start):
  opp_hp = 100 - sum(history)
  if alive == 2:
    return hp - 1
  round = len(history)
  spend = 0
  if round == 0:
    spend = 35 + np.random.randint(1, 11)
  elif round <= 2:
    spend = int(history[-1] * 2 / (4 - round)) + np.random.randint(5 * round - 4, 10 * round - 5)
  else:
    poly = np.polyfit(xrange(0, round), history, 2)
    spend = int(np.polyval(poly, round)) + np.random.randint(1, 4)
    spend = max(spend, opp_hp / 2 + 3)
  return min(spend, hp - 1, opp_hp)

Poly bot does a polynomial regression on your bot's history, and beats the predicted score by a small amount.

Classy bot
def classybot(hp, history, ties, alive, start):
  class cheekyvalue(int):
    def __gt__(self, other):
      return False
    def __lt__(self, other):
      return False
  opp_hp = 100 - sum(history)
  if alive == 2:
    if opp_hp >= hp - 1:
      return cheekyvalue(101)
    else:
      return hp - 1
  spend = 30 + np.random.randint(0, 11)
  if history:
    spend = min(spend, history[-1] + np.random.randint(0, 5))
  return min(spend, opp_hp, hp)

Classy bot has had a good time, but has decided to get to bed early. Sleep tight, classy bot.

Comments are not for extended discussion; this conversation has been moved to chat.

Gang Bot
The idea was that potentially two or more of the bot could be used in the same simulation. The bot tries to give "easy wins" to other bots in the gang, by seeing if its history is multiples of 7 bids. Of course, this could be easily manipulated by other bots as well. Then I calculate a guess on bids of non-gang bots based on the ratio of my health to theirs and ratio of their previous health to their previous bid and add 1.
def gang_bot(hp,history,ties,alive,start):
    mult=3
    gang = False
    if history:
            count = 0
            for bid in history:
                    if bid % mult == 0:
                            count += 1
            if count == len(history):
                    gang = True
    if gang and hp<100:#Both bots need to have a history for a handshake
            if hp > 100-sum(history):
                    a=np.random.randint(0,hp/9+1)
            elif hp == 100-sum(history):
                    a=np.random.randint(0,hp/18+1)
            else:
                    return 1
            return a*mult
    elif gang:
            fS = (100-sum(history))/mult
            return (fS+1)*mult
    else:
            fP = hp/mult
            answer = fP*mult
            opp_hp = 100-sum(history)
            if history:
                    if len(history)>1:
                            opp_at_1 = 100-history[0]
                            ratio = 1.0*history[1]/opp_at_1
                            guessedBet= ratio*opp_hp
                            answer = np.ceil(guessedBet)+1
                    else:
                            if 1.0*hp/opp_hp>1:
                                    fS = opp_hp/mult
                                    answer = fS*mult
            else:
                    fS = hp/(2*mult)
                    answer = fS*mult+mult*2 +np.random.randint(-1,1)*3
            if answer > hp or alive == 2 or answer < 0:
                    if alive == 2 and hp<opp_hp:
                      answer = hp
                    else:
                      answer = hp-1
            if hp > 1.5*opp_hp:
                    return opp_hp + ties
            if ties:
              answer += np.random.randint(2)*3
            return answer

Very cool. How many are needed? I'll probably have to cap the number of entries...

Your code block appears to miss your source's first line.

I'm not sure how many would be needed in a simulation, but if any of the bots ever see each other they should increase the chance of one of them winning. I'm guessing having 10% of the pool being gang bots should be enough to make a significant difference. Also the code block missing the first line thing -> this is my first post on here I don't know why the formatting did that but yeah its just the method declaration.

You have a bug: the bot will identify anyone with len(history)>1 as a gang member

My bad, should be fixed now.

Looks like patheticbot has an anti gang bot spinoff now

These guys don't seem to do as well as I would have guessed, even with multiples in play.

Yea they all perform the same, which suggests a logic bug somewhere.

Any idea what is happening when they die to each other?

Not sure, I don't have time to dive into it at the moment - but you can download the controller at the link in the OP to play with it if you want

I think i may have fixed it.

Still a problem with rand(): return np.random.randint(0,hp/10)   File "mtrand.pyx", line 993, in mtrand.RandomState.randint ValueError: low >= high. If hp<10, the randint() call fails.

I fixed that by adding or 1 to the high side, but the bots still perform the same, so they aren't communicating for some reason. I suggest you debug with the controller.

Let us continue this discussion in chat.

@JimHat do you get notified about chat posts? Not sure...

Outbidder
def outbid(hp, history, ties, alive):
    enemyHealth = 100-sum(history)
    if hp == 1:
        return 1
    if ties == 2:
        # lots of ties? max bid
        return hp - 1
    if enemyHealth >= hp:
        # Rip off KickBot (we can't bid higher than enemy is capable)
        return kick(*args) + 1
    if history:
        # bid as high as the enemy CAN
        return np.minimum(hp-1,enemyHealth-1)
    return np.random.randint(hp/5, hp/2)

Bot will attempt to bid higher than its opponent can bid where possible.

There is a condition where np.random.randint(hp/5, hp/2) can fail if hp/5 == hp/2, ie if hp==0 or hp==1

If HP is 0, then I shouldn't be invoked. :P You're right about HP 1 though.

Worst Case
def worst_case(hp, history, ties, alive, start):
    return np.minimum(hp - 1, hp - hp /(start - alive + 4) + ties * 2)

Simple bot. Returns hp - hp / (start - alive + 4) for most cases, and in case of ties increases it by 2(gotta one up!) for each tie, making sure to not return a number over its hp.

This fails with divide by zero if alive==8. I can manually change it to the total bot count, but it's stretching the rules since that's not an input to your function - all you know if how many opponents you have left at any given time, not how many you started out against.

I updated the contest based on your request

You also need to add 1 to start-alive, since this is 0 for the first round

@KBriggs fixed, actually should +2 so it doesn’t return 0, lol

@KBriggs Updated to account for the fact that most players bet extremely big round 1.

That did not really change anything in the ranking, unfortunately

@KBriggs Welp, back to the drawing board! (Ignore last comment, was an accident)

the round knockout probabilities are interesting for this one. He almost always loses in the second round, specifically...

Spitball Bot
def spitballBot(hp, history, ties, alive, start):
    base = ((hp-1) / (alive-1)) + 1.5 * ties
    value = math.floor(base)

    if value < 10:
        value = 10

    if value >= hp:
        value = hp-1

    return value

Makes a judgement about how much of its health it should sacrifice based on the number of remaining bots.  If there's only two bots left, it bids hp-1, but if there's three left, it bits half that, four left, a third, etc.
However, in a very large contest, I reckon I'll need to bid more than 3 or 4 hp to avoid dying on the first round, so I've put a lower bound at 10.  Of course, I still will never bid more than hp-1.
It also adds 1.5 hp for ties, since I see several "add 1 hp for ties" bots.  I'm not sure if that counts as cheating.  If it does, I'll change it.
Great idea, by the way!
Spitball Bot 2.0
What's new?

Switched to dividing by the number of rounds left instead of the number of bots left (Thanks to @Heiteira!).  Actually, I'm now dividing by that number raised to the power .8, so as to front-load my bids a little bit more.
Upped minimum bid from 10 to 20 (Thanks @KBriggs!)
Inserted check of whether the spitball bid is over the opponent's current HP, and lower it if it is.

(SO won't render the code below as code unless I put text here, so OK)
def spitballBot(hp, history, ties, alive, start):
    # Spitball a good guess
    roundsLeft = math.ceil(math.log(alive, 2)) # Thanks @Heiteira!
    divFactor = roundsLeft**.8
    base = ((hp-1) / divFactor) + 1.5 * ties
    value = math.floor(base)

    # Don't bid under 20
    if value < 20:
        value = 20 # Thanks @KBriggs!

    # Don't bet over the opponent's HP
    # (It's not necessary)
    opponentHp = 100
    for h in history:
        opponentHp -= h

    if value > opponentHp:
        value = opponentHp

    # Always bet less than your current HP
    if value >= hp:
        value = hp-1

    return value

Your bid should be an integer, so as long as you floor or ceil your base value to get rid of the decimal it's OK

Yep, I floor it right after doing all the calculations.  Thanks for the quick reply!

You might be able to optimize this if you aren't dividing your hp by the number of remaining contestants but by the number of remaining rounds (which should be math.ceil(math.log(alive, 2))

Based on other bots most of them seem to front load bids, so this might improve if you increase the first round bid above 10

Those are both good ideas!  I hadn't realized that the number of bot wasn't the same as the number of remaining rounds (I misinterpreted the contest rules at first).  I'll try implementing them tomorrow.  Thanks!

meh_bot
Just bid a little more than half its hp
def meh_bot(hp, history, ties, alive, start):
    # Attempt one      MehBot         | 0.020 | 1.6%    | 0.8%    | [34 36 12 10  6  1]%
    # Attempt two      MehBot         | 0.106 | 10.1%   | 0.8%    | [60  6  7  8  8  2]%
    point = hp / 2 + 3

    if ties > 1:
        ties += 1

    # Go all out on last round
    if alive == 2:
        return hp - 1

    opponent_hp = 100 - sum(history)

    if hp < 3:
        return 1
    elif not history:
        # Start with 30, This will increase the chance of dying first round but hopefully better fighting chance after
        return 30 + ties
    elif point > opponent_hp:
        # Never use more points then needed to win
        return opponent_hp + ties
    elif point >= hp:
        return hp - 1
    else:
        return point

MehBot 20
def meh_bot20(hp, history, ties, alive, start):
    # Attempt one      MehBot         | 0.020 | 1.6%    | 0.8%    | [34 36 12 10  6  1]%
    # Attempt two      MehBot         | 0.106 | 10.1%   | 0.8%    | [60  6  7  8  8  2]%
    point = hp / 2 + 3
    opponent_hp = 100 - sum(history)

    percents = []
    for i in range(0, len(history)):
        hp_that_round = 100 - sum(history[:i])
        hp_spent_that_round = history[i]
        percent_spent_that_round = 100.0 * (float(hp_spent_that_round) / float(hp_that_round))
        percents.append(percent_spent_that_round)

    try:
        opp_percent_point = opponent_hp * (max(percents) / 100)
    except:
        opp_percent_point = 100

    if ties > 1:
        ties += 1
    # Go all out on last round
    if alive == 2:
        return hp - 1

    if hp < 3:
        return 1
    elif not history:
        # randome number between 33
        return random.randint(33, 45)
    elif len(history) > 3:
        if point > opponent_hp:
            return min(opponent_hp + ties, opp_percent_point + ties)
    elif point > opponent_hp:
        # Never use more points then needed to win
        return opponent_hp + ties
    elif point >= hp:
        return hp - 1
    else:
        return point

mehRan
def meh_ran(hp, history, ties, alive, start):
    # Attempt one      MehBot         | 0.020 | 1.6%    | 0.8%    | [34 36 12 10  6  1]%
    # Attempt two      MehBot         | 0.106 | 10.1%   | 0.8%    | [60  6  7  8  8  2]%
    # Attempt three    MehBot         | 0.095 | 9.1 %   | 0.7 %   | [70  3  5  6  6  0]%

    point = hp / 2 + 3
    if ties > 1:
        ties += 1
    # Go all out on last round
    if alive == 2:
        return hp - 1
    opponent_hp = 100 - sum(history)
    if hp < 3:
        return 1
    elif not history:
        # randome number between 33
        return random.randint(33, 45)
    elif point > opponent_hp:
        # Never use more points then needed to win
        return opponent_hp + ties
    elif point >= hp:
        return hp - 1
    else:
        return point

There are quite a few bots that exist to take advantage of exactly this behavior, so you might have a hard time gaining traction!

@KBriggs Made some update, I wasn't expecting the first version to do as well as it did, hopefully, this update will give it a more fighting chance

Wow, that made a big difference, I think you're in first. Update will be posted in a couple of minutes. You may need to give your bot an immune system (See SarcomaBot) if you contijue to perform this well.

@KBriggs I was not expecting to do this well, I thought at best it will be top 10. Anyways I have added another one just to see the effect first round hp.
Can you run both of them so I can see the result of both? Thank you

@KBriggs Please do this one as well, Muchas gracias

@KBriggs Made few changes to numbers to increase chances of survival first round.

@mehMan Don't you mean "ties += 1" rather than "ties +1" ?

@Johan yep, I did X(, Thanks for the spot, completely flow over my head

@KBriggs fixed ties error Johan pointed out, hopefully there will be some difference in the number of ties

@KBriggs experimenting with % on mehbot20 and changed the numbers for mehRanBot

Guess Bot
def guess_bot(hp, history, ties, alive, start):
   enemy_hp = 100 - sum(history)
   if len(history) == 1:
       if history[0] == 99:
           return 2
       else:
           return 26 + ties*2

   elif len(history) > 1:
       next_bet_guess = sum(history)//(len(history)**2)
       if alive == 2:
           return hp
       elif alive > 2:
           if hp > next_bet_guess + 1:
               return (next_bet_guess + 1 + ties*2)
           else:
               return (2*hp/3 + ties*2)

   else:
       #Thank you Sarcoma bot. See you in Valhalla.
       startBid = hp / 3
       maxAdditionalBid = np.round(hp * 0.06) if hp * 0.06 > 3 else 3
       additionalBid = np.random.randint(2, maxAdditionalBid)
       return int(startBid + additionalBid + ties)

First time posting here. This looked like a lot of fun so I am submitting my beyond terrible attempt and guessing what the other bots will bet.
Edit 1:
Added another 1 to the first bet, simply to reduce the chance of a tie with other people betting 51.
Edit 2:
Stole Sarcoma bot's opening move since it had a good chance of not being eliminated first consistently.
Edit 3:
Bot survives very well in the first round, but it is being destroyed easily at later stages. Changed the way the robot thinks about the second round now that the half betters are dead in the water.
Edit 4:
Now that the first round is good, I changed the way it handles the second round. Dying a lot in the second round so I need to survive somehow.
Blood Bot
Made a thirsty bot looking for a kill. The idea is to try to win against low betting bots and once it is past the bloodbath of the first round it should be unstoppable since it should have juggernaut amounts of HP to outbid enemies.
def blood_bot(hp, history, ties, alive, start):
    enemy_hp = 100 - sum(history)
    if history:
        if len(history) == 1:
            if history[0] == 99:
                return 2

        if alive == 2:
            return hp

        if enemy_hp <= 5:
            return enemy_hp - 2 + ties*2

        if enemy_hp <= 10:
            return enemy_hp - 5 + ties*2

        if (hp - enemy_hp) > 50:
            return (2*enemy_hp/3 + ties*4)

        if (hp - enemy_hp) > 20:
            return (2*enemy_hp/3 + ties*3)

        if (hp - enemy_hp) < 0:
            #die gracefully
            return hp - 1 + ties

    else:
        startBid = hp / 3
        maxAdditionalBid = np.round(hp * 0.06) if hp * 0.06 > 3 else 3
        additionalBid = np.random.randint(2, maxAdditionalBid)
        return int(startBid + additionalBid + ties)

I think len(history)*len(history) could be changed to len(history)**2 if my knowledge of python is correct.

You have a divide by zero for when len(history)==0

Code has been updated. Upon finding no history it goes to the first else

oi.............

Woops, missed the update

@Sarcoma it is a cutthroat bot world out there man!

@KBriggs Added a bot and updated the code. Thank you for all your hard work!

@KBriggs updated the code, trying to help it survive the second round.

@MarkovChained you could probably improve performance considerably by returning hp-1 instead of hp when alive==2. Right now, you're guaranteeing a tie or loss in the final, your bots can never actually win a final unless you are the odd man out and your other two opponents kill each other.

Geometric
def geometric(hp, history, ties, alive, start):
    opponentHP = 100 - sum(history)

    # If we're doomed, throw in the towel.
    if hp == 1:
        return 1

    # If this is the last battle or we can't outsmart the opponent, go all out.
    if alive == 2 or ties == 2:
        return hp - 1

    # If the opponent is weak, squish it.
    if opponentHP <= hp * 0.9:
        if ties == 2:
            return opponentHP + 1
        else:
            return opponentHP

    # If the opponent has full health, pick something and hope for the best.
    if not history:
        return np.random.randint(hp * 0.5, hp * 0.6)

    # Assume the opponent is going with a constant fraction of remaining health.
    fractions = []
    health = 100
    for x in history:
        fractions.append(float(x) / health)
        health -= x
    avg = sum(fractions) / len(fractions)
    expected = int(avg * opponentHP)
    return min(expected + 2, hp - 1)

5th place on the first try, not bad at all

Bot 13
def bot13(hp, history, ties, alive, start):
    win = 100 - sum(history) + ties
    #print "Win HP: %d" % win
    if alive == 2:
        #print "Last round - all in %d" % hp
        return hp - 1
    elif hp > win:
        #print "Sure win"
        return win
    #print "Don't try too hard"
    return 13 + ties

Try to maximize wins with the least effort:

if we can win, just do it
if it's the last round, don't die trying
otherwise, don't bother

Why?
Try to take advantage of probability: winning the first round by playing low is the best way to start the tournament. 13 seems to be the sweet spot: the second round is a sure win, and the rest is a Spaziergang in the park.

You've taken the lead, very nice! Be careful, you may want to add parasite protection, since bots that take the lead become targets for things like UpYoursBot. Check out SarcomaBots for protection ideas if you need them.

Coast Bot [Retired]
Will try and coast it's way through the competition by evenly dividing it's hp between the rounds. Will bid any leftover hp on the first round to give itself a better chance of making it to the "coast-able" rounds.
def coast(hp, history, ties, alive, start):
   if alive == 2:
   # Last round, go all out
       return hp - 1 + ties
   else:
       # Find the next power of two after the starting number of players
       players = start
       while math.log(players, 2) % 1 != 0:
         players += 1

       # This is the number of total rounds
       rounds = int(math.log(players, 2))

       bid = 99 / rounds

       if alive == start:
           # First round, add our leftover hp to this bid to increase our chances
           leftovers = 99 - (bid * rounds)
           return bid + leftovers
       else:
           # Else, just try and coast

           opp_hp = 100 - sum(history)
           # If opponent's hp is low enough, we can save some hp for the
           # final round by bidding their hp + 1
           return min(bid, opp_hp + 1)

Coast Bot V2
Since I like this challenge so much, I just had to make another bot. This version sacrifices some of it's later coasting hp by using more hp in the first two rounds.
def coastV2(hp, history, ties, alive, start):
   # A version of coast bot that will be more aggressive in the early rounds

   if alive == 2:
   # Last round, go all out
       return hp - 1 + ties
   else:
       # Find the next power of two after the starting number of players
       players = start
       while math.log(players, 2) % 1 != 0:
         players += 1

       # This is the number of total rounds
       rounds = int(math.log(players, 2))

       #Decrease repeated bid by 2 to give us more to bid on the first 2 rounds
       bid = (99 / rounds) - 2

       if len(history) == 0:
           # First round, add 2/3rds our leftover hp to this bid to increase our chances
           leftovers = 99 - (bid * rounds)
           return int(bid + math.ceil(leftovers * 2.0 / 3.0))
       elif len(history) == 1:
           # Second round, add 1/3rd of our leftover hp to this bid to increase our chances
           leftovers = 99 - (bid * rounds)
           return int(bid + math.ceil(leftovers * 1.0 / 3.0))
       else:
           # Else, just try and coast

           opp_hp = 100 - sum(history)
           # If opponent's hp is low enough, we can save some hp for the
           # final round by bidding their hp + 1
           return int(min(bid, opp_hp + 1))

Percent Bot
Tries to calculate the average percent hp spend the opponent makes, and bids based on that.
def percent(hp, history, ties, alive, start):
    if len(history) == 0:
        #First round, roundon low bid
        return int(random.randint(10,33))
    elif alive == 2:
        #Last round, go all out
        return int(hp - 1 + ties)
    else:
        # Try and calculate the opponents next bid by seeing what % of their hp they bid each round
        percents = []
        for i in range(0, len(history)):
            hp_that_round = 100 - sum(history[:i])
            hp_spent_that_round = history[i]
            percent_spent_that_round = 100.0 * (float(hp_spent_that_round) / float(hp_that_round))
            percents.append(percent_spent_that_round)

        # We guess that our opponents next bid will be the same % of their current hp as usual, so we bid 1 higher.
        mean_percent_spend = sum(percents) / len(percents)
        op_hp_now = 100 - sum(history)
        op_next_bid = (mean_percent_spend / 100) * op_hp_now
        our_bid = op_next_bid + 1

        print mean_percent_spend
        print op_hp_now
        print op_next_bid

        # If our opponent is weaker than our predicted bid, just bid their hp + ties
        if op_hp_now < our_bid:
            return int(op_hp_now + ties)
        elif our_bid >= hp:
            # If our bid would kill us, we're doomed, throw a hail mary
            return int(random.randint(1, hp))
        else:
            return int(our_bid + ties)

Pretty cool idea. Sacking the first round is a new trend in bots, and it seems to be working reasonably well.

@KBriggs I've updated this response to contain my second attempt. Mentioning you as per the new rules. Great puzzle btw!

Want me to enter both, or just the latest version? Right now it's just V2

@KBriggs I'd like both entered if that's okay please. Would be good to see how they weigh up against each other.

Pretty similar performance overall

Hello again @KBriggs, I'd like to add my new percent bot to the mix please. Please also retire the old Coast bot from the rankings please. Thanks!

Fixed some bugs in the percent bot.

HalflifeS3
def HalflifeS3(hp, history, ties, alive, start):
    ''' Bet a half of oponent life + 2 '''
    if history:
        op_HP = 100 - sum(history)
        return np.minimum(hp-1, np.around(op_HP/2) + 2 + np.floor(1.5 * ties) )
    else:
        return hp/3

ConsistentBot
Bets the same amount each round. It's not too likely to survive the first rounds, but if it's lucky enough to get to the end, it should still have a reasonable amount of HP left.
def consistent(hp, history, ties, alive, start):
    if alive == 2:
        return hp-1

    if 100 % start == 0:
        return (100 / start) - 1
    else:
        return 100 / start

Whelp, it's too late to fix it now, but my bot used enough HP to make it to the end of fighting each opponent once, not to make it to the last round. That's my bad :P

Three Quarter Bot
He's not going to beat MehBot or SarcomaBot(s), but I think he does pretty well. When I first saw the challenge, this was the first thing that popped to my mind, always* bet three quarters of your health unless there's no reason to.
*after low-balling the first round.
def ThreeQuarterBot(hp, history, ties, alive, start):
    threeQuarters = 3 * hp / 4

    if alive == 2:
        return hp - 1

    opponent_hp = 100 - sum(history)

    if not history:
        # low-ball the first round but higher than (some) other low-ballers
        return 32 + ties
    elif threeQuarters > opponent_hp:
        return opponent_hp + ties

    return threeQuarters

Four Sevenths Bot
After the moderate success of 3/4 bot there's a new fraction in town, it's only rational.
def FourSeventhsBot(hp, history, ties, alive, start):
    fourSevenths = 4 * hp / 7

    if alive == 2:
        return hp - 1

    opponent_hp = 100 - sum(history)

    if not history:
        # low-ball the first round but higher than (some) other low-ballers
        return 33 + ties
    if fourSevenths > opponent_hp:
        return opponent_hp + ties

    return fourSevenths + ties

The Perfect Fraction
I am whole
def ThePerfectFraction(hp, history, ties, alive, start):
    thePerfectFraction = 7 * hp / 13

    if alive == 2:
        return hp - 1

    opponent_hp = 100 - sum(history)

    if not history:
        # Need to up our game to overcome the kickers
        return 42 + ties
    if thePerfectFraction > opponent_hp:
        return opponent_hp + ties

    return thePerfectFraction + 1 + ties

Based on this elimination probabilities, you could probably get away with smaller bids in round 2 as well. This one does well, but some minor tweaks can make it do a lot better.

@KBriggs Added a new bot with new and improved odds ;)

Want them both in there, or just 1?

@KBriggs I don't know if I missed the deadline, but I've added one final bot, if I made it in time, you can remove the other two fraction bots

Yep, you still have time

Kickban Bot
This bot simply tries to counter the current leader Mean Kickbot by beating it in round one and playing more aggressively thereafter if recognizing it.
def kickban(hp, history, ties, alive, start):
    if alive == 2:
        return hp-1

    if not history:
        return 36

    if history[0]==35:
        somean = 1
    else:
        somean = 0

    return min(mean_kick(hp, history, ties, alive, start) + somean*3, hp-1)

I think your indentation is a bit off.

oops, thanks, strange code editor messed with the first line

a valuable lesson in trusting code that you don't control

Robbie Roulette
def robbie_roulette(hp, history, ties, alive):
     if history:
         #If the enemy bot has a history, and it's used the same value every time, outbid that value
         if len(set(history)) == 1:
             return history[0] + 1
         #Else, average the enemy bot's history, and bid one more than the average
         else:
             return (sum(history) / len(history) + 1)
     #Else, return half of remaining hp
     else:
         return hp / 2

This bot does some simple analysis of the enemy bot's history, or bids half of its remaining hit points otherwise

Calculating Bot
def calculatingBot(hp, history, ties, alive, start):
    opponentsHP = 100 - sum(history)
    if alive == 2: # 1v1
        return hp - 1 + ties
    # Try to fit an exponential trendline and one up the trendline if it fits
    if len(history) >= 3:
        xValues = range(1, len(history) + 1)
        # https://stackoverflow.com/a/3433503  Assume an exponential trendline
        coefficients = np.polyfit(xValues, np.log(history), 1, w = np.sqrt(history))
        def model(coefficients, x):
            return np.exp(coefficients[1]) * np.exp(coefficients[0] * x)
        yPredicted = [model(coefficients, x) for x in xValues]
        totalError = 0
        for i in range(len(history)):
            totalError += abs(yPredicted[i] - history[i])
        if totalError <= (len(history)): # we found a good fitting trendline
            # get the next predicted value and add 1
            theoreticalBet = np.ceil(model(coefficients, xValues[-1] + 1) + 1)
            theoreticalBet = min(theoreticalBet, opponentsHP)
            theoreticalBet += ties
            return int(min(theoreticalBet, hp - 1)) # no point suiciding
    maxRoundsLeft = np.ceil(np.log2(alive))
    theoreticalBet = hp / float(maxRoundsLeft)
    additionalRandomness = round(np.random.random()*maxRoundsLeft)
    # want to save something for the future
    actualBet = min(theoreticalBet + additionalRandomness + ties, hp - 2)
    actualBet = min(actualBet, opponentsHP+1)
    return int(actualBet)

Aggressive Calculating Bot
def aggresiveCalculatingBot(hp, history, ties, alive, start):
    opponentsHP = 100 - sum(history)
    if opponentsHP == 100: # Get past the first round
        return int(min(52+ties, hp-1+ties))
    if alive == 2: # 1v1
        return hp - 1 + ties
    # Try to fit an exponential trendline and one up the trendline if it fits
    if len(history) >= 3:
        xValues = range(1, len(history) + 1)
        # https://stackoverflow.com/a/3433503  Assume an exponential trendline
        coefficients = np.polyfit(xValues, np.log(history), 1, w = np.sqrt(history))
        def model(coefficients, x):
            return np.exp(coefficients[1]) * np.exp(coefficients[0] * x)
        yPredicted = [model(coefficients, x) for x in xValues]
        totalError = 0
        for i in range(len(history)):
            totalError += abs(yPredicted[i] - history[i])
        if totalError <= (len(history)): # we found a good fitting trendline
            # get the next predicted value and add 1
            theoreticalBet = np.ceil(model(coefficients, xValues[-1] + 1) + 1)
            theoreticalBet = min(theoreticalBet, opponentsHP)
            theoreticalBet += ties
            return int(min(theoreticalBet, hp - 1)) # no point suiciding
    maxRoundsLeft = np.ceil(np.log2(alive))
    theoreticalBet = hp / float(maxRoundsLeft)
    additionalRandomness = 1+round(np.random.random()*maxRoundsLeft*2)
    # want to save something for the future
    actualBet = min(theoreticalBet + additionalRandomness + ties, hp - 2)
    actualBet = min(actualBet, opponentsHP+1)
    return int(actualBet)

Anti Kick Bot
def antiKickBot(hp, history, ties, alive, start):
    if alive == 2:
        return (hp - 1 + ties)
    amount = np.ceil((float(hp) / 2) + 1.5)
    opponentsHP = 100 - sum(history)
    amount = min(amount, opponentsHP) + ties
    return amount

If we can predict the opponent's actions, we can make the optimal bets! If we cant (not enough data or opponent is too random), then we can at least do what would maximize our win potential. Theoretically at least half the number of bots alive will die each round. Thus I can expect there to be at most log2(alive) rounds. Ideally we would split our hp evenly between all the rounds. However, we know that some bots will be stupid and suicide / die early, so we should bet slightly more in the earlier rounds.
Aggressive Calculating Bot's modify's Calculating Bot's code to try to stay alive by being more aggressive, at the cost of long term health. Only simulations will tell if tempo or value wins out.
Anti Kick Bot should always beat the current leader KickBot :P
EDIT: Replaced Deterministic Bot with Anti Kick Bot, a smarter bot with almost exactly the same return values. Also prevented voting more than the opponents HP

Cute. I think this one will do better with a very large bot pool.

I get an error sometimes with this one: return np.max(theoreticalBet, hp - 1): AxisError: axis 23 is out of bounds for array of dimension 0. I posted a link to the controller so you can test it.

@KBriggs Updated the code to fix it.

confirmed, score update incoming. You're in the top 10 for sure.

@KBriggs I added a couple more bots to try out :)

Bid higher the less competition you have. Thanks to commenters for suggesting improvements.
def Spreader(hp, history, ties, alive):
   if alive == 2:
       return hp-1
   if len(history) < 2:
       return hp/2
   return np.ceil(hp/alive)

Neat idea,  but I see two issues.  One is most other bots bid big on the first round, so this one will probably die early most of the time.  The second is that in the final,  most bots bid hp-1, so this one will lose those unless you have double their hp. But for the intermediate rounds I like the idea.  If you address the two special cases you can probably improve performance.

SurvivalistBot and HalvsiesBot
Thank you for answering my questions. The end result is a more complex bot.
HalvsiesBot is a whimsical 'just keep passing half' bot with a 50/50 chance of winning. I guess.
SurvivalistBot makes a series of binary tree if-else decisions based on the dataset, including an override on a tie (if it hits 2 ties it kamikazes to avoid triple tie death).
My python is a little rusty, so the code might be a bit buggy, so feel free to correct or update it.
It's built to try to work out bits of data to infer things like how much HP is left, the minimum number of bots it is likely to fight, minimum amount of HP to leave, average bidding. It also exploits randomisation in ambiguous situations, such as opening plays or optimal bidding issues.
def HalvsiesBot(hp, history, ties, alive, start):
    return np.floor(hp/2)

def SurvivalistBot(hp, history, ties, alive, start):

    #Work out the stats on the opponent
    Opponent_Remaining_HP = 100 - sum(history)
    Opponent_Average_Bid = Opponent_Remaining_HP

    if len(history) > 0:
        Opponent_Average_Bid = Opponent_Remaining_HP / float(len(history))

    HP_Difference = hp - Opponent_Remaining_HP

    #Work out the future stats on the others
    RemainingBots = (alive-2)
    BotsToFight = 0

    RemainderTree = RemainingBots

    #How many do we actually need to fight?
    while(RemainderTree > 1):
        RemainderTree = float(RemainderTree / 2)
        BotsToFight += 1

    #Now we have all that data, lets work out an optimal bidding strategy
    OptimalBid = 0
    AverageBid = 0

    #For some reason we've tied more than twice in a row, which means death occurs if we tie again
    #So better to win one round going 'all in'
    if ties > 1:
        if BotsToFight < 1:
            OptimalBid = hp - 1
        else:
            OptimalBid = hp - (BotsToFight+1)

        #Err likely we're 0 or 1 hp, so we just return our HP
        if OptimalBid < 1:
            return hp
        else:
            return OptimalBid

    #We have the upper hand (more HP than the opponent)
    if HP_Difference > 0:
        #Our first guess is to throw all of our opponent's HP at them
        OptimalBid = HP_Difference

        #But if we have more opponents to fight, we must divide our HP amongst our future opponents
        if BotsToFight > 0:
            #We could just divide our HP evenly amongst however many remaining bots there are
            AverageBid = OptimalBid / BotsToFight

            #But this is non-optimal as later bots will have progressively less HP
            HalfBid = OptimalBid / 2

            #We have fewer bots to fight, apply progressive
            if BotsToFight < 3:

                #Check it exceeds the bot's average
                if HalfBid > Opponent_Average_Bid:
                    return np.floor(HalfBid)
                else:
                    #It doesn't, lets maybe shuffle a few points over to increase our odds of winning
                    BidDifference = Opponent_Average_Bid - HalfBid

                    #Check we can actually match the difference first
                    if (HalfBid+BidDifference) < OptimalBid:
                        if BidDifference < 8:
                            #We add half the difference of the BidDifference to increase odds of winning
                            return np.floor(HalfBid + (BidDifference/2))
                        else:
                            #It's more than 8, skip this madness
                            return np.floor(HalfBid)

                    else:
                        #We can't match the difference, go ahead as planned
                        return np.floor(HalfBid)

            else:
                #There's a lot of bots to fight, either strategy is viable
                #So we use randomisation to throw them off!
                if bool(random.getrandbits(1)):
                    return np.floor(AverageBid)
                else:
                    return np.floor(HalfBid)

        else:
            #There are no other bots to fight! Punch it Chewy!
            return OptimalBid

    else:

        if hp == 100:
            #It appears to be our opening round (assumes opponent HP same as ours)
            #We have no way of knowing what our opponent will play into the battle

            #Only us in the fight? Full power to weapons!
            if BotsToFight < 1:
                return hp - 1
            else:
                #As what might happen is literally random
                #We will also be literally random
                #Within reason

                #Work out how many bots we need to pass
                HighestBid = hp - (BotsToFight+1)
                AverageBid = hp/BotsToFight
                LowestBid = np.floor(np.sqrt(AverageBid))

                #Randomly choose between picking a random number out of thin air
                #And an average
                if bool(random.getrandbits(1)):
                    return np.minimum(LowestBid,HighestBid)
                else:
                    return AverageBid

        else:
            #Oh dear, we have less HP than our opponent
            #We'll have to play it crazy to win this round (with the high probability we'll die next round)
            #We'll leave ourselves 1 hp (if we can)

            if BotsToFight < 1:
                OptimalBid = hp - 1
            else:
                OptimalBid = hp - (BotsToFight+1)

            #Err likely we're 0(???) or 1 hp, so we just return our HP
            if OptimalBid < 1:
                return hp
            else:
                return OptimalBid

BoxBot
def BoxBot(hp, history, ties, alive):

    Opponent_HP = float.round(100 - sum(history))
    HalfLife = float.round(Opponent_HP/2)
    RandomOutbid = HalfLife + np.random.randint(1,HalfLife)

    if hp < RandomOutbid:
        return hp - 1
    else
        return RandomOutbid

Opponent_Average_Bid = Opponent_Remaining_HP / float(len(history)) ZeroDivisionError: float division by zero. This line needs to handle the 0 length history case.

Thanks, I'll correct it.

Fixed. Let me know if there are any other errors.

Couple of syntax errors: missing :afterelse, math.[func] -> np.[func], and at one point you use Lowestwhere you meanLowestBid`. All fixed in the controller on github and scores updated shortly.

Thanks. Fixed all the aforementioned errors in the post.

@KBriggs Added BoxBot as a lame counter to KickBot.

I sugegsted some boxbot fixes in an edit

Let us continue this discussion in chat.

How does BoxBot work? I am very curious as to how it is a counter to KickBot.

@Yodie It tries to either tie or beat KickBot by meeting the half+1 as a minimum, but with a randomised additional int. If KickBot's assumption is that a logical choice is half, and thus bets half+1, then BoxBot does half+1+a random integer (including, in theory, zero), either triggering tie behaviour so it bets more on the next opponent or by outbidding it by one or more points. Randomised so you can't simply do +2.

GenericBot
def generic_bot(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if not history:
        return int(hp * 7.0 / 13)
    opp = 100 - sum(history)
    if opp < hp:
        return opp + ties
    max_sac = np.maximum(int(hp * 0.7), 1)
    rate = history[-1] * 1.0 / (history[-1] + opp)
    return int(np.minimum(max_sac, rate * opp + 1))

It's really late... I'm tired... can't think of a name... and the format of this bot is really similar to others, just with a slightly different algorithm given history. It tries to get the current rate the opponent is tending towards gambling... or something like that... zzz

you need to use np.maximum instead of np.max, same for min

@KBriggs Thanks :) Hmm, it seems like the generic bots rule this game

Seems like they would be pretty easy targets, I'm surprised nobody has made a parasite yet

@KBriggs Yeah, I'm surprised. Time to add protections...

Still planning on making neuralbot?

BandaidBot
BandaidBot wants everyone to play nice! If its opponent was nice last round, it will sacrifice itself to incentivize nice behavior in others. If its opponent was mean last round, it will do as much damage as possible to its opponent, sacrificing itself if necessary. It bids a third of its hp if it has no history to work with. (I'm hoping this bot will have interesting ripple effects on other strategies, not so much that this bot will have a high win rate itself. It could be fun to have a couple of these in play)
def BandaidBot(hp, history, ties, alive, start):
    if alive == 2:
        return hp-1

    if history:
        opp_hp = 100 - sum(history)
        opp_last_hp = 100 - sum(history[:-1])

        if history[-1] <= opp_last_hp / 3:
            return 1 + ties * np.random.randint(0, 1)
        elif history[-1] > opp_last_hp / 2:
            return min(opp_hp - 1, hp)
        else:
            if history[-1] < hp/2:
                return np.random.randint(history[-1], hp/2)
            else:
                return np.floor(hp/2)
    else:
        return np.floor(hp/3)

GetAlongBot
GetAlongBot will be just as nice as it needs to be to take advantage of BandaidBot. It will return just under one third of its hp unless it can kill its opponent for less than that. If its opponent looks like BandaidBot, it will bid 2, knowing that BandaidBot will bid 1 because GetAlongBot has been getting along so well with everyone else--an easy win as long as it really was BandaidBot on the other end.
def GetAlongBot(hp, history, ties, alive, start):
    if alive == 2:
        return hp-1

    if history:
        opp_hp = 100 - sum(history)
        opp_last_hp = 100 - sum(history[:-1])
        count = 0
        for i in range(0, len(history)):
            hp_that_round = 100 - sum(history[:i])
            hp_spent_that_round = history[i]
            if hp_that_round / 3 - 1 <= hp_spent_that_round <= hp_that_round / 2:
                count += 1
        if count == len(history): #It's probably BandaidBot!
            return 2
        else:
            return min(opp_hp - 1, np.floor(hp/3))
    else:
        return np.floor(hp/3)

Really neat idea. I wonder how much impact it willl have

error: return np.random.randint(history[-1], hp/2): ValueError: low >= high This case needs to be handled somehow

@KBriggs should be fixed now!

@KBriggs updated to fix randomization

CautiousBot
First submission to Programming Puzzles ever! Found your challenge quite interesting :P
If last round bit one less than hp, if no history bet half hp plus a small random amount.
If history check opponent hp and number of remaining rounds and try to outbid opponent hp / 2 using an additional buffer of up to the fraction of remaining hp divided by the number of remaining rounds (it tries to conserve the remaining hp somehow for posterior rounds). Check if your are spending too much hp (do not kill yourself or bid more than your adversary can).
Always correct for ties as other bots do.
def cautious_gambler(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if(history):
        opp_hp = 100 - sum(history)
        remaining_rounds = np.ceil(np.log2(start)) - len(history)

        start_bet = opp_hp / 2
        buff = int((hp - start_bet)/remaining_rounds if remaining_rounds > 0 else (hp - start_bet))
        buff_bet = np.random.randint(0, buff) if buff > 0 else 0
        bet = start_bet + buff_bet + ties

        if bet >= hp or bet > opp_hp:
            bet = np.minimum(hp - 1, opp_hp)

        return int(bet)
    else:
        start_bet = hp / 2
        rng_bet = np.random.randint(3,6)

        return int(start_bet + rng_bet + ties)

CautiousBot2
Too much aggressive on first rounds, now CautiousBot gets even more cautious...
def cautious_gambler2(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    if(history):
        opp_hp = 100 - sum(history)
        remaining_rounds = np.ceil(np.log2(start)) - len(history)

        start_bet = opp_hp / 2
        buff = int((hp - start_bet)/remaining_rounds if remaining_rounds > 0 else (hp - start_bet))
        buff_bet = np.random.randint(0, buff) if buff > 0 else 0
        bet = start_bet + buff_bet + ties

        if bet >= hp or bet > opp_hp:
            bet = np.minimum(hp - 1, opp_hp)

        return int(bet)
    else:
        start_bet = hp * 0.35
        rng_bet = np.random.randint(3,6)

        return int(start_bet + rng_bet + ties)

You have a bug where it is still calling randint when buffer = 0: buffer_bet = np.random.randint(0, buffer) if buffer > 0 else 0   File "mtrand.pyx", line 993, in mtrand.RandomState.randint ValueError: low >= high. Note that buffer is a keyword in python, you might want to choose a different variable name.

Oh it looks like it is becasuse buffer is not always an int - you are probably dividing by zero at some point. Please check the logic carefully. I made it run, but you can probably fix the corner cases.

Nice catch @KBriggs. Think I fixed it.

I still get an error: buff_bet = np.random.randint(0, buff) if buff > 0 else 0   File "mtrand.pyx", line 993, in mtrand.RandomState.randint ValueError: low >= high. It seems that buff is sometimes a floating point number between 0 and 1, which presumably gets cast to 0 inside randint. This works if you cast buff to int before the call

@KBriggs probably because ceil returns a float. Missed that one... Ty again :P

@KBriggs updated bot.

Want them both in there, or just 1>

MataHariBot
Illegal access of information in the tournament allows it to identify its opponent.  Left for posterity.

def MataHariBot(hp, history, ties, alive, start):
    '''
    Interrogate our opponent about what they're going to do
    and use that against them
    '''

    if alive <= 3:
        return hp - 1

    debug = False

    # Hello antiantiantiantiupyoursbot and your inspect.stack modification
    f = inspect.currentframe()
    target_frame = None
    depth = 0
    while True:
        f = f.f_back
        if f is None:
            break
        depth = depth + 1
        if depth == 2:
            target_frame = f

    if target_frame is None or target_frame.f_code.co_name != 'tournament':
        if debug:
            print('Skullduggery!')
        return hp - 1

    # Find our opponent
    opponent = None
    us = None
    for key, value in target_frame.f_locals.iteritems():
        if not isinstance(value, RouletteBot):
            continue
        if value.func.__code__.co_name == inspect.currentframe().f_code.co_name:
            us = value
        else:
            opponent = value

    if us is None or opponent is None:
        if debug:
            print('Falsity!')
        return hp - 1

    results = [ ]
    for i in range(random.randint(100, 151)):
        result = opponent.func(opponent.hp, us.history, ties, alive, start)
        # pathetic_attempt_at_analytics_bot sometimes returns None, though
        # I couldn't figure out why with a quick glance at its code.
        if result is None:
            result = 0
            if debug:
                print("%s returned None" % (opponent.__code__.co_name))
        results.append(result)

    # If we have a deterministic result, use that
    if np.allclose(results, results[0]):
        guess = results[0]
    # If we have a small range of guesses, use the maximum
    elif np.max(results) - np.min(results) <= hp / 3:
        guess = np.max(results)
    # Otherwise, we're dealing with a wide range of guesses and can just hope
    else:
        guess = np.median(results) * 1.25

    return np.minimum(hp - 1, int(guess) + 1)

MataHari2Bot
Uses (allowed) reflection to get all the bots.  Simulates a bunch in order to get a good first round choice, and then on later rounds simulates bots that may be its opponent based on history.
Caches a bunch of data so performance is not terrible.  Perhaps for some definitions of terrible.  Also may contain more code than all the other bots put together.  Or close to it.
When updating to the latest controller I discovered OgBot.  The two do not work well together at all, since they both work by calling others, which leads to recursion if left alone.  So MataHari2Bot gives OgBot the cold shoulder.
They both have the same strategy for round 1, but for rounds 2+ OgBot tries to figure out what other bots would do in its situation (with the hp and history it was given); MataHari2Bot tries to figure out what bot it is likely to be against and then tries to figure out what that bot would do to it.
def MataHari2Bot(hp, history, ties, alive, start):
    '''
    Round 1: Determine what our opponents do in the beginning with no information,
    give the assumed best counter.

    Round 2: Using the information from round 1, determine which of our opponents
    are possible given their history.  Determine what they'll do with our history
    (which we know from our hp) and give the assumed best counter.

    Round 3+: Same as round 2, except we have to make up our history based on our
    hp (not knowing our own history is a really odd decision but w/e), which leads
    to less and less precision.
    '''

    this = MataHari2Bot
    INITIAL_HP = 100

    # It's not that I hate Ogbot, it's that otherwise we get recursion
    if inspect.currentframe().f_back.f_back.f_code.co_name != 'tournament':
        return 1

    def simulate(method, hp, history, ties, alive, start):
        # print("Running simulation for %s when we are %i HP, opponent is %i HP" % (method.__name__, hp, INITIAL_HP - np.sum(history) + 1))
        return [ method(hp, history, ties, alive, start) for x in range(200) ]

    def best_guess(hp, results):
        low = np.min(results)
        high = np.max(results)
        if high == low or high - low <= 5:
            guess = high
        else:
            guess = np.median(results)
        if guess is None:
            return 1
        return guess

    class BotInfo:
        def __init__(self, name, method, hp, history, ties, alive, start):
            self.name = name
            self.method = method
            self.round1_simulations = simulate(self.method, hp, history, ties, alive, start)
            self.round1_guess = best_guess(hp, self.round1_simulations)
            self.guesses = { }

        def guess(self, hp, history, ties, alive, start):
            opponent_hp = INITIAL_HP - np.sum(history)
            key = (hp * 100) + opponent_hp
            if key not in self.guesses:
                try:
                    simulations = simulate(self.method, opponent_hp, [ INITIAL_HP - hp ], ties, alive, start)
                except ValueError:
                    # BoundedRandomBot has a problem if you feed it a hp of 1 and a history of 99; this doesn't
                    # occur normally because it's not actually included in reset_brackets, perhaps by oversight?
                    # Anyways, ignore it.
                    # print("Error occurred calling %s with (%i, %s, %i, %i, %i); our hp %i, opponent history %s" % (self.name, opponent_hp, [ INITIAL_HP - hp ], ties, alive, start, hp, history))
                    if self.name != 'BoundedRandomBot':
                        raise
                    simulations = [ 1 ]

                self.guesses[key] = best_guess(hp, simulations)
            return self.guesses[key]

    if not hasattr(this, 'bots'):
        # Could've used reset_brackets, but abiding by the rules to not touch RouletteBot even if they're not the ones
        # being used in the tournament
        print("Initializing %s; this should only happen once" % (this.__name__))
        # We have to skip OgBot or the "Simulate OgBot 200 times" then leads into OgBot calling every other bot and performance just goes
        # to hell
        blacklist = [ this.__name__, 'ogbot' ]
        this.bots = [ ]
        for key, value in globals().iteritems():
            if key not in blacklist and type(value) == type(this) and len(inspect.getargspec(value).args) == 5:
                bot = BotInfo(key, value, hp, history, ties, alive, start)
                # Ignore the suicide bots.  Even if we beat them in round 1, we'll be down too many hp to win, so they
                # will just skew our medians pointless
                # Also ignore the wildly random bots
                if bot.round1_guess < 70 and (np.max(bot.round1_simulations) - np.min(bot.round1_simulations)) < 40:
                    this.bots.append(bot)

    # Round1 will always be the same, no need to recalculate every time
    if len(history) == 0:
        if hasattr(this, 'round1_guess'):
            return this.round1_guess + ties
        guesses = [ bot.round1_guess for bot in this.bots ]
    else:
        # Otherwise, determine which bots could have given us the round1 guess we have in history
        # Weight this; the guess of a bot that deterministically returns this number is worth more
        # than one that returned it only 5% of the time
        guesses = [ ]
        for bot in this.bots:
            if history[0] in bot.round1_simulations:
                guess = bot.guess(hp, history, ties, alive, start)
                guesses.extend( [ guess ] * bot.round1_simulations.count(history[0]))

    if len(guesses) == 0:
        # yolo
        guess = mean_kick(hp, history, ties, alive, start) + 1
    else:
        guess = int(math.ceil(np.median(guesses))) + 1
        # As long as it doesn't cost us too much, keep going up.  This catches the various
        # "do it like Xbot but adding 1" bots
        while guess in guesses:
            guess = guess + 1
    guess = min(guess, hp - 1 + ties, INITIAL_HP - np.sum(history) + ties)
    if len(history) == 0:
        this.round1_guess = guess
        print("MataHari2Bot's Round1 Guess: %i" % (this.round1_guess))
        #ordered_bots = sorted(this.bots, key = lambda x: x.round1_guess)
        #i = 1
        #for bot in ordered_bots:
        #   print("\t%i: %s (%i)" % (i, bot.name, bot.round1_guess))
        #   i = i + 1
    return guess

OverfittedBot
Ran into this a bunch while doing MataHari2Bot.  The whole 'kickbot' ecosystem, and then the bots that rely on it, and the bots that counter it, really distort the field.  This bot is incredibly overfitted for the specific environment I was running in, based on data I got while testing MataHari2Bot, and goes down in performance significantly if the initial value of 40 is changed, or other bots in the 'kick' ecosystem are put in or taken out, or basically anything else.  But so it goes.
def OverfittedBot(hp, history, ties, alive, start):
    if alive == 2:
        return hp - 1
    opponent_hp = 100 - np.sum(history)
    if opponent_hp == 100:
        guess = 40 + ties
    elif len(history) == 1 and history[0] == 39:
        guess = opponent_hp * 0.50 + 1 + ties
    else:
        guess = mean_kick(hp, history, ties, alive, start) + 1
    return min(guess, hp - 1 + ties, 100 - np.sum(history) + ties)

Sorry,  I explicitly disallow entries that attempt to access or modify properties of other bots. You can call the general functions they use, but you can't call them via the bot objects themselves,  which is what I think you're doing here. Pretty sure your immune system is legit though.  I'll need more time to analyze before I enter it. You're also not allowed to access your own history.

My misunderstanding; I thought the rule was just about disallowing modification.  When you say "can't call them via the bot objects themselves", you mean via the RouletteBot class?

Yes, it might be a bit ambiguously worded. You can't access or modify the properties of any roulettebot instance,  including yourself.  Very clever,  though.

Ok.  Two followup questions: 1. Is it allowed to get our own history (as opposed to our opponent's)?  2. Is it allowed to inspect the methods of the file in order to find all functions that are bots?

No, as the rules stand you can't get your own history. I'm not sure about the second one, since you're clearly much better st this than I am ^_^. What did you have in mind?

I missed the "can't access your own history", sorry.  I'm pretty sure the answer to this is no, but I'm going to ask anyway: can I save state from one call to another?  Either by setting a value on my bot (not another's), or by using a global variable?
For the other, correct any misunderstandings I may have, but: given the controller code, I can call other bot functions directly (UpYoursBot does this, for instance), yes? My question is rather than hardcode the list of bot functions to call, can I just inspect the file to get all functions that contain 'bot' and take in 5 parameters?

No,  you can't save your own history,  sorry.  Wouldn't be much point forbidding access to it if you could. You can get all the bot functions,  but the rule is that you are not allowed to do anything that unambiguously identifies your current opponent. It's a bit vague,  I know.  I'll be pretty lenient with any exploit you cook up that doesn't completely break the game. Also please bear in mind I have to run simulations,  so realistically if your bot runs simulations inside itself,  it might be too slow ^_^. But I will allow that,  within reason

Bear in mind that all most bots use your history for is to calculate your hp, so you can generate convincing fake histories pretty easily

Let us continue this discussion in chat.

@KBriggs Not sure that chat mentions do notifications, so just putting one here as well to alert you to the changes.

SquareUpBot
Didn't seem like many bots were playing with powers instead of fractions, so I decided to make one, with some standard optimisations and see where I'll place. Quite simplistic.
Also tries to determine if the enemy bot isn't trying to use some constant fraction, because powers > fractions.
EDIT: I'm a dummy and my fraction detector could not work. Repaired now.
def squareUp(hp, history, ties, alive, start):

    #Taken from Geometric Bot
    opponentHP = 100 - sum(history)

    # Need to add case for 1
    if hp == 1:
        return 1

    # Last of the last - give it your all
    if alive == 2:
        if ties == 2 or opponentHP < hp-1:
            return hp - 1

    #Calculate your bet (x^(4/5)) with some variance
    myBet = np.maximum(hp - np.power(hp, 4./5), np.power(hp, 4./5))
    myBet += np.random.randint(int(-hp * 0.05) or -1, int(hp * 0.05) or 1);
    myBet = np.ceil(myBet)
    if myBet < 1:
        myBet = 1
    elif myBet >= hp:
        myBet = hp-1
    else:
        myBet = int(myBet)

    #If total annihilation is a better option, dewit
    if opponentHP < myBet:
        if ties == 2:
            return opponentHP + 1
        else:
            return opponentHP

    #If the fraction is proven, then outbid it (Thanks again, Geometric bot)
    if history and history[0] != history[-1]:
        health = 100
        fraction = float(history[0]) / health
        for i,x in enumerate(history):
            newFraction = float(x) / health
            if newFraction + 0.012*i < fraction or newFraction - 0.012*i > fraction:
                return myBet
            health -= x
        return int(np.ceil(opponentHP * fraction)) + 1
    else:
        return myBet

Not a bad first shot, scores have been updated

@KBriggs I have updated the bot so the fraction detector actually works.

Alright, I'll try my hand at this.
SnetchBot
Checking the fractions of health that the opponent has been going with. If the opponent has been raising, beat him to it.
def snetchBot(hp, history, ties, alive, start):
    if alive == 2:
        return hp-1

    opponent_hp = 100
    history_fractions = []
    if history:
        for i in history:
            history_fractions.append(float(i)/opponent_hp)
            opponent_hp -= i
        if opponent_hp <= hp/2:
            #print "Squashing a weakling!"
            return opponent_hp + (ties+1)/3

        average_fraction = float(sum(history_fractions)) / len(history_fractions)
        if history_fractions[-1] < average_fraction:
            #print "Opponent not raising, go with average fraction"
            next_fraction = average_fraction
        else:
            #print "Opponent raising!"
            next_fraction = 2*history_fractions[-1] - average_fraction
        bet = np.ceil(opponent_hp*next_fraction) + 1
    else:
        #print "First turn, randomish"
        bet = np.random.randint(35,55)

    if bet > opponent_hp:
        bet = opponent_hp + (ties+1)/3
    final_result = bet + 3*ties
    if bet >= hp:
        #print "Too much to bet"
        bet = hp-1
    return final_result

EDIT: losing a lot in the first round, adjusted the first turn random limits

Pretty good first shot, score update incoming

@KBriggs Edited a little (just the first round random bounds). Although I was already surprised by being as high as the 10th place. If this one does worse, I'll just roll back to the first one :D

You squeeze a little more juice out of him

TENacious bot
def TENacious_bot(hp, history, ties, alive, start):
  max_amount=hp-(alive-1)*2;
  if max_amount<2: max_amount=2

  if alive==2: return hp-1
  if ties==0: return np.minimum(10, max_amount)
  if ties==1: return np.minimum(20, max_amount)
  if ties==2: return np.minimum(40, max_amount)
  # prevent function blowup
  return 2

This bot tries to hold on its favourite value of 10, but it changes its choice occasionally if needed to break a tie (with its favourite value doubled or quadrupled) or to save for future rounds, but not by optimal amount because it wants to confuse opponents and it does not want to consider bidding less than 2 at any time as it is convinced it is much better than to hope the opponent will bid less than 1, that is, 0.
PS: this bot may have strategic problems if there are more than 2^9 bots.

I suspect you don't have to worry about having 2^9 opponents ^_^.

But with an opening bet of 10, he's rarely going to make it past the first round

This bot thinks that, if some bot really wants to give more than 10 hp in the first round, it is not worth the fight.

Haha fair enough

SmartBot
def smart_bot(hp, history, ties, alive, start):
    op_high_bid = 0
    op_hp = 100
    hptoret = 1
    his = False
    tienum = (ties * ties) + 1 if ties else ties
    if not history:
        return 42 + tienum
        #return obots(hp, history, ties, alive, start)
    if his:
        if history[0]==36:
            return min(wise_kick(hp, history, ties, alive, start) + 3, hp-1)

    if alive == 2:
        if history[0]==36:
            return min(wise_kick(hp, history, ties, alive, start) + 3, hp-1)
        return hp - 1
    if history:
        his = True

    if his:
        for h in history:
            if int(h) > op_high_bid:
                op_high_bid = h
    if his:
        op_hp = 100 - sum(history)
        if op_hp >= hp:
            return np.floor(hp / 3)

    roundn = len(history) + 1
    #if roundn > 6:
        #return hp - 1
    """Overrideable(If above did not execute of course)"""

    if op_hp < hp:
        """Kick em hard"""
        hptoret = wise_kick(hp, history, ties, alive, start)
    while op_hp < hptoret:
        hptoret -= 1
    hptoret += 1
    return hptoret

It only got 35th place(I ran the executer)
EDIT: Doing MUCH better!
First, SmartBot Bids around 1/3 of its hp(When I fine-tune the code this may go up or down). Next It checks if it is with the last bot. Next priority is attacking MeanKickBot. Then, if the Opponents HP is higher it bids 1/3 of its hp(Whoops just caught a mistake np.floor() fixes it). If it is round six, SmartBot just forfeits all but 1 hp, Probably not having much left. Then it gets to the overrideables(One If can change the change of another If, using the hptoret variable) First, if first if Opponents hp is less than SmartBot's hp, It checks how much greater, then, if the opponent's hp is more then 11 less than SmartBot, if so, SamrtBot bids opponents hp + 1, otherwise, it gets mean_kicked(Thanks MeanKickBot)
SmartBot takes a lot of logic, hence SmartBot.
These Tweaks Are worth it!
I got my bot somewhat better with tweaks.
Everybody should tweak their code, it is much worth it

Let us continue this discussion in chat.

Darn it @KBriggs Wise kickbot destroyed me

You're not the only one ^_^

Well, after deadline im making a franken bot would you run the test case after that, and see how he does? @KBriggs

REKT Sarcoma MK11 takes the top!

sure, I will keep running stuff after the competition for fun

I added a new final update. I got top 4 now! @KBriggs Can you rerun PLS. That last minute wise kickbot was unfair, but smart

Sorry,  I'm already well into the final run =/. But I'll do another one after since there are still more bots around

Rerun after final run? pls oh wait franken bot

FrankenBot Will target WiseKickBot for revenge!

Yea I'll keep doing things after the final

I would have gotten 3rd in final if not for that wise kickbot

@KBriggs Can you teach me how you made the code? Im making a simmalar challange but with ants

not sure what you mean by teach, but I'm happy to answer any questions you have about it.  Feel free to post your questions as an issue on the github repo so we don't clutter up the comments

there:https://github.com/shadowk29/RobotRoulette/issues/1

Gets the median bid of all other bots and outbids by one
Edit: Suicide protection and remove bots that are likely to fail in the first round after the first round
Edit 2: spelling
ogbot V2.0.1
def ogbot(hp, history, ties, alive, start):
    otherBots = functions = [f for f in globals().values() if type(f) == types.FunctionType]

    def whatWouldOtherbotDo(bot,hp,history,ties,alive,start):
        botname = bot.__name__
        # got to avoid self referencing
        if(botname)=='ogbot':
            return -999

        # avoid non bot functions
        try:
            return bot(hp,history,ties,alive,start)
        except (TypeError, NameError, RuntimeError):
            return -999

    otherBotCurrentBids = []

    otherBotOpeningBids = []

    for bot in otherBots:
        otherBotCurrentBids.append(whatWouldOtherbotDo(bot,hp,history,ties,alive,start))
        otherBotOpeningBids.append(whatWouldOtherbotDo(bot,100,[],0,start,start))

    # remove invalid outputs
    otherBotCurrentBids = filter(lambda a: a != -999, otherBotCurrentBids)
    otherBotOpeningBids = filter(lambda a: a != -999, otherBotOpeningBids)

    # if this is the second round or later, try to deal with bots who were likely to pass the first round
    if len(history)>ties:
        medianOpening = np.median(otherBotOpeningBids)
        # get the bots who probably passed the first round and aren't committing suicide
        likelySuccessBots = [(a,b) for a,b in zip(otherBotCurrentBids, otherBotOpeningBids) if (b>medianOpening) & (a<hp)]
        otherBotCurrentBids = [a for a,b in likelySuccessBots]

    out = min(np.median(otherBotCurrentBids) + 1,hp-1)
    return out

Running the sim now, I'm looking forward to seeing how this does. You actually don't slow things down much, so if you wanted to do more careful simulation you can.

Just pushed an update. It does surprisingly well @KBriggs

Yea I got the update, no worries

Huh.. this has more variation than I thought it would. On my run I had 5th place. Will you increase the N for the final? Edit: though it could be because my version wasn't up to date

I'll do at least a million for the final, maybe 10 million if its feasible.

Seems fairly consistent on my end, with quick N=10000 rounds I get you in second or third every time

my run didn't have the other new bots and changes. Some other bot must be helping

BoundedRandomBot
A simple variation on a pick-a-random-number bot, where it will never pick a number higher than opponent's HP + 1 (except in the final round, where it borrows SarcomaBot's trick of betting remaining HP - 1).
def boundedRandomBot(hp, history, ties, alive, start):
    max_possible_bid = hp - 1
    if alive == 2 or max_possible_bid == 0:
        return max_possible_bid

    if history:
        opp_hp = 100 - sum(history)
        bid_ceiling = min(opp_hp+1, max_possible_bid)
    else:
        bid_ceiling = max_possible_bid
    return np.random.randint(1, bid_ceiling+1)

AggressiveBoundedRandomBotV2
Like the above, but with a floor instead of a ceiling; instead of never picking above opponent's HP + 1, it will never pick below opponent's HP / 2 (unless of course it doesn't have that much HP).
def aggressiveBoundedRandomBotV2(hp, history, ties, alive, start):
    max_possible_bid = hp - 1
    if alive == 2 or max_possible_bid == 0:
        return max_possible_bid

    if history:
        opp_hp = 100 - sum(history)
    else:
        opp_hp = 100

    bid_floor = min(np.ceil(opp_hp * 0.5), max_possible_bid)
    return np.random.randint(bid_floor, max_possible_bid+1)

Thanks to @KBriggs for catching my bug.

@KBriggs updated AggressiveBoundedRandomBot to AggressiveBoundedRandomBotV2

Killerbot
def KillerBot(hp, history, ties, alive, start):
    curr_hp = 100
    #if we're the last one alive just bet our hp
    if alive == 2:
        return hp - 1
    #calculate their hp
    their_hp = 100 - sum(history)

    #go through their history
    if history.length() > 1 and hp > their_hp:
        ratios = []
        num_hist = 0
        their_hp = 100
        for hist in history:
           top = curr_hp
            curr_hp -= hist
            bot = curr_hp
            ratios.append((top/bot))
            num_hist += 1
        if(ties < 2):
            return 100*(ratios/num_hist + 2)
        else:
            return 100*(ratios/num_hist + 3)
    else:
        #first round bid 20
        return 20

    return hp - 1

This solution tries to check for the average ratio of opponent's bets and tries to bet slightly higher. First time posting so hopefully it goes well.

HardCoded
def hc(hp, history, ties, alive, start):
    plan = [40,27,20,10,2,5,5,5,5,5,2,2,2,2,2,1,1,1,1,1,100,1]
    ohp = 100 - sum(history)
    if ohp < hp * 0.16:
        return ohp + 1
    x = 100
    k = 0
    while x > hp:
        x -= plan[k]
        k += 1
    if plan[k] > ohp:
        return ohp + 1
    return plan[k] + ties

I tried a bunch of values trying to get a decent bot out of fixed values. It does okay, but won't be winning overall for sure.

Hodgepodge
def hodgepodge(hp, history, ties, alive, start):
    enemyHealth = 100-sum(history)
    bl = 0
    if enemyHealth < 100:
        if enemyHealth > 96:
            # if somehow the bot survived last round, murder it cheaply
            return (ties*2)+1
    if ties == 2:
        # lots of ties? max bid
        return hp - 1
    if hp < 3:
        # little remaining hp? return minimum
        return 1
    if enemyHealth > hp:
        # Rip off SarcomaBot
        r = sarcoma_bot_mk_eleven(hp-1,history,ties,alive,start) + 1
        if bl:
            print r
        return r
    if history:
        # rip off KickBan
        return kickban(hp,history,ties,alive,start)
    if alive == 2:
        return hp-1
    if not history:
        return 42

Didn't realize there was a final end-date and was futzing around with this for a while. Tends to come in somewhere between 3rd and 5th.