Robot Roulette: High stakes robot gambling

55

18

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:

  1. They choose a number smaller than that of their opponent
  2. Their hp drops to or below zero
  3. 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.

KBriggs

Posted 2018-10-02T14:22:23.177

Reputation: 691

If a bot wins in the final by bidding all of its hp, does it get the point for victory or is considered dead? – AlexRacer – 2018-10-02T21:08:03.847

It would count as a tie – KBriggs – 2018-10-02T21:10:13.947

4FWIW, I plan to use a neural network trained on all the other bots just for fun once you set up the controller :) – Quintec – 2018-10-02T22:27:33.457

@Quintec That's what I was waiting for ^_^. I will post the controller tomorrow, I'm away from my computer at the moment – KBriggs – 2018-10-02T22:30:10.377

@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 – 2018-10-03T13:02:05.687

@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. – KBriggs – 2018-10-03T13:23:55.380

1Is 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. – Markov Chained – 2018-10-03T21:03:21.183

I don't collect that data currently but I certainly could. I'll implement that tomorrow – KBriggs – 2018-10-03T21:04:55.243

1@MarkovChained I added elimination probability information to the leaderboards. – KBriggs – 2018-10-04T05:33:42.930

Thanks! Also this would make for some really interesting analogous study for disease evolution yanno? – Markov Chained – 2018-10-04T07:47:52.813

What do you mean? That's not my expertise, could you elaborate? – KBriggs – 2018-10-04T12:02:42.530

@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 – 2018-10-04T15:43:45.887

@Johan I will do that, but I have to run for the moment. Stay tuned for this afternoon – KBriggs – 2018-10-04T15:54:08.717

Sure, when you have time. On my local (with python 3.6) it seems sufficient to ensure that num != None with current bots. – Johan – 2018-10-04T15:59:02.250

2The type check was for the benefit of antiantiantiantiupyoursbot. I'll find another way – KBriggs – 2018-10-04T16:10:36.597

Er classybot, that is – KBriggs – 2018-10-04T16:26:50.873

@Johan how's that? I think that takes care of everything so far and doesn't obviously break anyone. – KBriggs – 2018-10-04T18:06:31.630

@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. – Markov Chained – 2018-10-04T19:18:49.123

@KBriggs This has been a very weird competition. I love it. Sorry for making a mess of things. <3 – Qfwfq – 2018-10-04T19:47:08.617

@KBriggs Indeed, looks perfect, apart from Kamikaze breaking itself anyway haha – Johan – 2018-10-04T19:48:17.320

@Qfwfq no need to apologize, I fully endorse exploiting my insecure code to get an advantage, within the spirit of the rules, anyway ^_^ – KBriggs – 2018-10-04T20:12:49.617

1@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. – Sarcoma – 2018-10-04T20:40:16.020

Me too, actually – KBriggs – 2018-10-04T20:41:59.377

3@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 – Markov Chained – 2018-10-04T21:27:42.307

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 – 2018-10-04T21:33:50.187

@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. – KBriggs – 2018-10-04T21:37:43.650

@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 – 2018-10-04T22:22:31.323

1

@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 – 2018-10-04T23:31:50.387

@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.

– Sarcoma – 2018-10-04T23:47:23.930

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 – 2018-10-06T09:54:34.230

1@Sarcoma: 2:00 UTC, 2 hours from the timestamp on this comment – KBriggs – 2018-10-06T12:01:47.537

@KBriggs How long does a whole update with N = 100.000 take on your computer (approximately) ? – Johan – 2018-10-06T13:11:52.117

1About 10 minutes. I'll run a million for the final – KBriggs – 2018-10-06T13:16:28.273

@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. – Sarcoma – 2018-10-06T14:05:05.013

3WOOOOOOOOOOOOOO! – Sarcoma – 2018-10-06T15:23:40.417

1Well played @Sarcoma ! – Johan – 2018-10-06T15:25:03.337

2Thanks for playing, everyone, I had a lot of fun. I'm already shopping the format in my head for next time ^_^ – KBriggs – 2018-10-06T15:28:21.543

5Oh my god. The deliberate trolling of changing mean_kick to always return zero when it was used so many places is brilliant. – Magua – 2018-10-06T17:07:39.280

1Welp, looks like I didn't have time to finish the neural network because of school, maybe next challenge :) – Quintec – 2018-10-06T18:02:43.070

1Too bad, I was really looking forward to seeing how that is done – KBriggs – 2018-10-06T18:03:25.307

1Would it be ok to use the Robot Roulette script for a developer meetup? – Sarcoma – 2018-10-06T19:24:50.107

What's a developer meetup? But sure, feel free to use it however you please – KBriggs – 2018-10-06T19:26:22.990

@Sarcoma you had in mind doing a live roulettebots competition? Could be fun. Let me know how it goes. – KBriggs – 2018-10-06T20:03:11.470

@KBriggs Yeah, I think it could be cool, it's something anyone can have a go at. – Sarcoma – 2018-10-06T20:10:58.250

1@Sarcoma I added a license to make it explicit. have fun! – KBriggs – 2018-10-06T20:54:19.770

1Thanks for the great challenge, following along with this was indeed a lot of fun! – Joshua Webb – 2018-10-07T01:55:22.753

Cheers, I'll probably tweak the format a bit and try again in a while – KBriggs – 2018-10-07T02:04:03.613

@KBriggs So do I get a tick? – Sarcoma – 2018-10-08T14:19:14.233

Oh, right. Forgot all about it – KBriggs – 2018-10-08T15:31:14.297

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.

– KBriggs – 2018-10-10T14:15:28.357

1I'm voting to close this question because it is already de-facto closed to new answers ("Final Standings") – pppery – 2019-09-04T20:44:32.623

@pppery no objections here – KBriggs – 2019-09-06T13:58:47.583

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) – pppery – 2019-09-06T18:54:03.353

Doesn't really make a practical difference at this point – KBriggs – 2019-09-07T20:48:16.997

Answers

12

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

Sarcoma

Posted 2018-10-02T14:22:23.177

Reputation: 236

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

– Mego – 2018-10-04T23:58:40.597

17

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.

Qfwfq

Posted 2018-10-02T14:22:23.177

Reputation: 371

1Hahahaha 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 – 2018-10-03T18:18:16.223

@KBriggs, thanks! Corrected. – Qfwfq – 2018-10-03T18:26:22.963

1It does very well, obviously, but it still loses to Kick Bot – KBriggs – 2018-10-03T19:20:43.780

1Ha, good effort! – Sarcoma – 2018-10-03T19:28:05.120

1@Sarcoma I couldn't have done it without you. ;)

I really like your bot, too, buddy. – Qfwfq – 2018-10-03T20:03:46.100

1Sarcomabot's Upyoursbot protection really messes with this one – KBriggs – 2018-10-03T20:57:01.027

I'm not even mad, this is quite creative in its own way. – Belhenix – 2018-10-03T22:58:33.517

I think your second line (willToLive = True if "I" is in "VICTORY") causes a syntax error. – Solomon Ucko – 2018-10-04T00:19:17.843

@SolomonUcko it did, I just set that to True in the running version – KBriggs – 2018-10-04T03:42:22.900

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... – Persona – 2018-10-04T05:17:53.447

15

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

DobromirM

Posted 2018-10-02T14:22:23.177

Reputation: 313

11Welp, that was inevitable – KBriggs – 2018-10-02T16:07:20.407

I was going to add a pacifist bot as well but I don't want to flood your challenge with brain dead bots – DobromirM – 2018-10-02T16:19:15.143

5Based 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. – KBriggs – 2018-10-02T16:19:44.260

14

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.

Yodie

Posted 2018-10-02T14:22:23.177

Reputation: 141

Very nice, thanks for the bot! I'll give some stats when I get a few more. – KBriggs – 2018-10-02T16:55:54.597

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 – Yodie – 2018-10-02T16:58:23.227

It runs, so no worries there – KBriggs – 2018-10-02T17:00:18.273

@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. – Jonathan Frech – 2018-10-02T17:04:44.023

@JonathanFrech sorry I copied from a text document so there might've been some copy-and-paste errors. I'll fix them quickly. – Yodie – 2018-10-02T17:06:17.483

2I 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. – KBriggs – 2018-10-02T19:22:29.090

@Yodie, you're well in the lead now, see the standings – KBriggs – 2018-10-02T19:45:56.427

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 – DobromirM – 2018-10-02T21:07:53.890

Love anti gang bot. The arms race has begun – KBriggs – 2018-10-03T01:02:58.323

@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. – Yodie – 2018-10-03T01:04:27.853

Cool. Soon i'll update the contest with submission count limits and a deadline. – KBriggs – 2018-10-03T01:17:14.960

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 – KBriggs – 2018-10-03T13:32:11.483

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). – Graham – 2018-10-03T13:44:07.667

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. – KBriggs – 2018-10-03T14:00:40.227

Your upgraded bot has an infinite loop! if len(fractions)>1... – KBriggs – 2018-10-03T20:49:01.283

ok, i will fix it. – Yodie – 2018-10-03T22:17:17.330

Since Gang Bot got fixed, AntiGangBot is back in the running if you want to reinstate him – KBriggs – 2018-10-04T04:29:26.920

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. – KBriggs – 2018-10-04T04:48:32.713

11

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))

Johan

Posted 2018-10-02T14:22:23.177

Reputation: 111

Nice. I'm seeing a lot of submissions that directly counter others now, that's exactly what I was hoping for – KBriggs – 2018-10-03T01:04:45.553

Double return on the last line? – Veskah – 2018-10-03T01:56:19.860

Ah I hadn't run it yet or I would have caught that. – KBriggs – 2018-10-03T02:30:35.937

This one has taken a comfortable lead! – KBriggs – 2018-10-03T13:53:38.130

@Johan I was wrong, my controller was killing poor kickbot since you return a float. Fixed now, he's still kicking. – KBriggs – 2018-10-04T18:10:54.927

@KBriggs Sweet! Now I'll just have to give him some backup. – Johan – 2018-10-04T19:46:27.797

1@KBriggs here is some backup! – Johan – 2018-10-04T22:50:34.973

That's some effective backup – KBriggs – 2018-10-05T01:47:31.530

@KBriggs the last kick brother arrived and sadly both his brother had to neutralize themselves. – Johan – 2018-10-06T13:42:06.197

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 – KBriggs – 2018-10-06T13:44:04.273

Oh I'm not asking to remove them, just to update their code hehe – Johan – 2018-10-06T13:45:22.893

RIP everyone else – KBriggs – 2018-10-06T13:55:20.790

8

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

Belhenix

Posted 2018-10-02T14:22:23.177

Reputation: 193

You'll have to rename him, there is already a kamikaze bot ^_^ – KBriggs – 2018-10-02T17:01:33.017

So far this one is the winner, though – KBriggs – 2018-10-02T17:03:21.187

Your function ceil appears not to be defined. – Jonathan Frech – 2018-10-02T17:05:51.093

I changed to np.ceil() to run it – KBriggs – 2018-10-02T17:06:30.777

edited, thanks for the heads-up – Belhenix – 2018-10-02T17:21:21.600

@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 – 2018-10-02T19:49:04.620

@KBriggs revisited and working on a better one (will keep this punch bot though) – Belhenix – 2018-10-05T21:55:36.537

@KBriggs done editing, best I could do without changing everything completely – Belhenix – 2018-10-06T11:15:10.827

8

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.

Persona

Posted 2018-10-02T14:22:23.177

Reputation: 181

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

– Mego – 2018-10-04T23:57:49.110

7

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

Jim Hat

Posted 2018-10-02T14:22:23.177

Reputation: 71

Very cool. How many are needed? I'll probably have to cap the number of entries... – KBriggs – 2018-10-02T21:32:12.083

Your code block appears to miss your source's first line. – Jonathan Frech – 2018-10-02T21:41:18.563

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. – Jim Hat – 2018-10-02T21:42:30.313

You have a bug: the bot will identify anyone with len(history)>1 as a gang member – KBriggs – 2018-10-02T22:01:15.007

My bad, should be fixed now. – Jim Hat – 2018-10-02T22:07:29.673

Looks like patheticbot has an anti gang bot spinoff now – KBriggs – 2018-10-03T01:03:41.763

These guys don't seem to do as well as I would have guessed, even with multiples in play. – KBriggs – 2018-10-03T13:33:11.850

Yea they all perform the same, which suggests a logic bug somewhere. – KBriggs – 2018-10-03T13:44:20.047

Any idea what is happening when they die to each other? – Jim Hat – 2018-10-03T13:46:26.850

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 – KBriggs – 2018-10-03T13:56:02.683

I think i may have fixed it. – Jim Hat – 2018-10-03T14:04:43.847

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. – KBriggs – 2018-10-03T15:17:12.410

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. – KBriggs – 2018-10-03T15:55:26.117

Let us continue this discussion in chat.

– Jim Hat – 2018-10-03T16:02:56.857

@JimHat do you get notified about chat posts? Not sure... – KBriggs – 2018-10-04T04:33:30.437

6

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.

Draco18s no longer trusts SE

Posted 2018-10-02T14:22:23.177

Reputation: 3 053

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 – KBriggs – 2018-10-02T16:40:02.323

3If HP is 0, then I shouldn't be invoked. :P You're right about HP 1 though. – Draco18s no longer trusts SE – 2018-10-02T16:41:43.567

6

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.

Quintec

Posted 2018-10-02T14:22:23.177

Reputation: 2 801

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. – KBriggs – 2018-10-02T18:32:43.720

I updated the contest based on your request – KBriggs – 2018-10-02T18:45:33.517

@KBriggs Thanks :) – Quintec – 2018-10-02T19:13:38.753

You also need to add 1 to start-alive, since this is 0 for the first round – KBriggs – 2018-10-02T19:15:18.573

@KBriggs fixed, actually should +2 so it doesn’t return 0, lol – Quintec – 2018-10-02T19:23:07.543

@KBriggs Updated to account for the fact that most players bet extremely big round 1. – Quintec – 2018-10-03T16:24:52.857

That did not really change anything in the ranking, unfortunately – KBriggs – 2018-10-03T16:27:42.823

@KBriggs Welp, back to the drawing board! (Ignore last comment, was an accident) – Quintec – 2018-10-03T16:28:47.833

the round knockout probabilities are interesting for this one. He almost always loses in the second round, specifically... – KBriggs – 2018-10-04T18:14:00.833

6

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

MegaWidget

Posted 2018-10-02T14:22:23.177

Reputation: 61

1Your 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 – KBriggs – 2018-10-02T19:11:34.803

Yep, I floor it right after doing all the calculations. Thanks for the quick reply! – MegaWidget – 2018-10-02T19:21:44.493

2You 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)) – Black Owl Kai – 2018-10-02T22:22:06.563

1Based on other bots most of them seem to front load bids, so this might improve if you increase the first round bid above 10 – KBriggs – 2018-10-02T22:22:34.030

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! – MegaWidget – 2018-10-03T00:04:36.303

5

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)

Markov Chained

Posted 2018-10-02T14:22:23.177

Reputation: 51

2I think len(history)len(history) could be changed to len(history)*2 if my knowledge of python is correct. – Yodie – 2018-10-03T00:03:36.253

You have a divide by zero for when len(history)==0 – KBriggs – 2018-10-03T13:30:48.043

Code has been updated. Upon finding no history it goes to the first else – Markov Chained – 2018-10-03T17:12:47.890

oi............. – Sarcoma – 2018-10-04T11:59:21.167

Woops, missed the update – KBriggs – 2018-10-04T13:05:11.787

2@Sarcoma it is a cutthroat bot world out there man! – Markov Chained – 2018-10-04T18:22:24.963

@KBriggs Added a bot and updated the code. Thank you for all your hard work! – Markov Chained – 2018-10-04T20:29:26.180

@KBriggs updated the code, trying to help it survive the second round. – Markov Chained – 2018-10-05T02:48:08.313

@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. – KBriggs – 2018-10-05T15:09:22.300

5

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)

user48543

Posted 2018-10-02T14:22:23.177

Reputation:

5th place on the first try, not bad at all – KBriggs – 2018-10-03T15:48:06.730

5

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.

G B

Posted 2018-10-02T14:22:23.177

Reputation: 11 099

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. – KBriggs – 2018-10-04T12:46:52.693

5

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

meh Man

Posted 2018-10-02T14:22:23.177

Reputation: 51

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 – 2018-10-04T12:51:26.627

@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 – meh Man – 2018-10-04T13:34:31.350

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 – 2018-10-04T14:04:58.773

@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 – meh Man – 2018-10-04T15:44:48.167

@KBriggs Please do this one as well, Muchas gracias – meh Man – 2018-10-04T16:31:53.587

@KBriggs Made few changes to numbers to increase chances of survival first round. – meh Man – 2018-10-04T22:06:40.360

@mehMan Don't you mean "ties += 1" rather than "ties +1" ? – Johan – 2018-10-04T22:26:46.547

@Johan yep, I did X(, Thanks for the spot, completely flow over my head – meh Man – 2018-10-04T22:56:42.407

@KBriggs fixed ties error Johan pointed out, hopefully there will be some difference in the number of ties – meh Man – 2018-10-04T23:07:48.510

@KBriggs experimenting with % on mehbot20 and changed the numbers for mehRanBot – meh Man – 2018-10-05T13:49:31.077

4

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

Belgabad

Posted 2018-10-02T14:22:23.177

Reputation: 141

4

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

Bob Cratchit

Posted 2018-10-02T14:22:23.177

Reputation: 61

Cute. I think this one will do better with a very large bot pool. – KBriggs – 2018-10-02T21:56:03.323

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 – 2018-10-03T13:37:50.540

@KBriggs Updated the code to fix it. – Bob Cratchit – 2018-10-03T16:00:12.923

1confirmed, score update incoming. You're in the top 10 for sure. – KBriggs – 2018-10-03T16:03:13.263

@KBriggs I added a couple more bots to try out :) – Bob Cratchit – 2018-10-03T20:30:33.973

4

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)

Gus314

Posted 2018-10-02T14:22:23.177

Reputation: 101

1Neat 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. – KBriggs – 2018-10-03T12:38:59.453

4

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

SSight3

Posted 2018-10-02T14:22:23.177

Reputation: 141

Opponent_Average_Bid = Opponent_Remaining_HP / float(len(history)) ZeroDivisionError: float division by zero. This line needs to handle the 0 length history case. – KBriggs – 2018-10-03T14:22:38.913

Thanks, I'll correct it. – SSight3 – 2018-10-03T14:24:00.157

Fixed. Let me know if there are any other errors. – SSight3 – 2018-10-03T14:28:22.677

1Couple of syntax errors: missing :afterelse,math.[func] -> np.[func], and at one point you useLowestwhere you meanLowestBid`. All fixed in the controller on github and scores updated shortly. – KBriggs – 2018-10-03T14:34:20.777

Thanks. Fixed all the aforementioned errors in the post. – SSight3 – 2018-10-03T14:49:59.320

@KBriggs Added BoxBot as a lame counter to KickBot. – SSight3 – 2018-10-03T16:10:55.403

I sugegsted some boxbot fixes in an edit – KBriggs – 2018-10-03T16:13:19.817

Let us continue this discussion in chat.

– KBriggs – 2018-10-03T16:15:33.333

How does BoxBot work? I am very curious as to how it is a counter to KickBot. – Yodie – 2018-10-03T16:28:45.133

@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. – SSight3 – 2018-10-04T16:44:56.197

4

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

Quintec

Posted 2018-10-02T14:22:23.177

Reputation: 2 801

you need to use np.maximum instead of np.max, same for min – KBriggs – 2018-10-04T05:43:39.237

@KBriggs Thanks :) Hmm, it seems like the generic bots rule this game – Quintec – 2018-10-04T17:26:47.897

Seems like they would be pretty easy targets, I'm surprised nobody has made a parasite yet – KBriggs – 2018-10-04T17:28:12.110

@KBriggs Yeah, I'm surprised. Time to add protections... – Quintec – 2018-10-04T17:28:52.607

Still planning on making neuralbot? – KBriggs – 2018-10-04T17:31:57.007

4

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)

Wazz

Posted 2018-10-02T14:22:23.177

Reputation: 41

Pretty cool idea. Sacking the first round is a new trend in bots, and it seems to be working reasonably well. – KBriggs – 2018-10-04T12:50:27.660

@KBriggs I've updated this response to contain my second attempt. Mentioning you as per the new rules. Great puzzle btw! – Wazz – 2018-10-04T13:33:46.147

Want me to enter both, or just the latest version? Right now it's just V2 – KBriggs – 2018-10-04T14:00:15.143

@KBriggs I'd like both entered if that's okay please. Would be good to see how they weigh up against each other. – Wazz – 2018-10-04T14:04:53.593

Pretty similar performance overall – KBriggs – 2018-10-04T14:09:47.940

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! – Wazz – 2018-10-04T16:21:51.517

Fixed some bugs in the percent bot. – Wazz – 2018-10-04T16:36:42.300

4

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

Roo4data

Posted 2018-10-02T14:22:23.177

Reputation: 41

4

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

Kevin

Posted 2018-10-02T14:22:23.177

Reputation: 141

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 – Kevin – 2018-10-08T05:14:35.680

4

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

Joshua Webb

Posted 2018-10-02T14:22:23.177

Reputation: 141

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 – 2018-10-05T03:56:20.923

@KBriggs Added a new bot with new and improved odds ;) – Joshua Webb – 2018-10-05T06:53:28.073

Want them both in there, or just 1? – KBriggs – 2018-10-05T12:14:08.293

@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 – Joshua Webb – 2018-10-06T03:39:42.137

1Yep, you still have time – KBriggs – 2018-10-06T12:16:50.380

4

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)

HRSE

Posted 2018-10-02T14:22:23.177

Reputation: 141

1I think your indentation is a bit off. – Jonathan Frech – 2018-10-05T07:48:36.153

oops, thanks, strange code editor messed with the first line – HRSE – 2018-10-05T09:55:45.807

a valuable lesson in trusting code that you don't control – OganM – 2018-10-06T21:49:38.010

4

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)

Maya Sol

Posted 2018-10-02T14:22:23.177

Reputation: 41

Really neat idea. I wonder how much impact it willl have – KBriggs – 2018-10-06T00:22:33.613

error: return np.random.randint(history[-1], hp/2): ValueError: low >= high This case needs to be handled somehow – KBriggs – 2018-10-06T00:24:26.977

@KBriggs should be fixed now! – Maya Sol – 2018-10-06T12:06:57.687

@KBriggs updated to fix randomization – Maya Sol – 2018-10-06T13:45:10.753

3

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.

AlexRacer

Posted 2018-10-02T14:22:23.177

Reputation: 979

I suspect you don't have to worry about having 2^9 opponents ^_^. – KBriggs – 2018-10-02T22:43:24.093

But with an opening bet of 10, he's rarely going to make it past the first round – KBriggs – 2018-10-02T22:47:25.200

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. – AlexRacer – 2018-10-02T23:12:43.517

Haha fair enough – KBriggs – 2018-10-02T23:15:48.270

3

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)

Jesús Ros

Posted 2018-10-02T14:22:23.177

Reputation: 131

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. – KBriggs – 2018-10-04T12:25:22.767

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. – KBriggs – 2018-10-04T12:36:20.887

Nice catch @KBriggs. Think I fixed it. – Jesús Ros – 2018-10-04T15:04:20.497

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 – 2018-10-04T15:06:47.863

@KBriggs probably because ceil returns a float. Missed that one... Ty again :P – Jesús Ros – 2018-10-04T15:15:50.667

@KBriggs updated bot. – Jesús Ros – 2018-10-05T09:32:33.260

Want them both in there, or just 1> – KBriggs – 2018-10-05T12:12:46.253

3

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)   

Magua

Posted 2018-10-02T14:22:23.177

Reputation: 131

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. – KBriggs – 2018-10-04T21:47:33.350

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? – Magua – 2018-10-04T21:48:51.063

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. – KBriggs – 2018-10-04T21:49:37.210

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? – Magua – 2018-10-04T21:51:02.083

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? – KBriggs – 2018-10-04T21:53:24.833

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? – Magua – 2018-10-04T21:57:09.167

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 – KBriggs – 2018-10-04T22:01:11.043

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 – KBriggs – 2018-10-04T23:53:25.033

Let us continue this discussion in chat.

– KBriggs – 2018-10-05T01:12:07.610

@KBriggs Not sure that chat mentions do notifications, so just putting one here as well to alert you to the changes. – Magua – 2018-10-06T07:54:50.407

3

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

Diamond Dust

Posted 2018-10-02T14:22:23.177

Reputation: 31

Not a bad first shot, scores have been updated – KBriggs – 2018-10-05T17:36:21.823

@KBriggs I have updated the bot so the fraction detector actually works. – Diamond Dust – 2018-10-05T19:25:15.110

3

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

snetch

Posted 2018-10-02T14:22:23.177

Reputation: 131

Pretty good first shot, score update incoming – KBriggs – 2018-10-05T18:26:47.963

@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 – snetch – 2018-10-05T18:42:44.063

You squeeze a little more juice out of him – KBriggs – 2018-10-05T18:51:19.590

3

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

Menotdan

Posted 2018-10-02T14:22:23.177

Reputation: 31

Let us continue this discussion in chat.

– KBriggs – 2018-10-06T13:10:18.933

Darn it @KBriggs Wise kickbot destroyed me – Menotdan – 2018-10-06T14:02:46.143

You're not the only one ^_^ – KBriggs – 2018-10-06T14:03:15.580

Well, after deadline im making a franken bot would you run the test case after that, and see how he does? @KBriggs – Menotdan – 2018-10-06T14:04:35.360

REKT Sarcoma MK11 takes the top! – Menotdan – 2018-10-06T14:05:27.587

sure, I will keep running stuff after the competition for fun – KBriggs – 2018-10-06T14:05:41.480

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 – Menotdan – 2018-10-06T14:14:41.003

Sorry, I'm already well into the final run =/. But I'll do another one after since there are still more bots around – KBriggs – 2018-10-06T14:16:08.323

Rerun after final run? pls oh wait franken bot – Menotdan – 2018-10-06T14:17:17.367

FrankenBot Will target WiseKickBot for revenge! – Menotdan – 2018-10-06T14:17:49.193

Yea I'll keep doing things after the final – KBriggs – 2018-10-06T14:18:27.507

1I would have gotten 3rd in final if not for that wise kickbot – Menotdan – 2018-10-06T14:21:53.090

@KBriggs Can you teach me how you made the code? Im making a simmalar challange but with ants – Menotdan – 2018-10-07T18:04:46.343

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 – KBriggs – 2018-10-07T19:12:24.397

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

– Menotdan – 2018-10-07T19:19:06.293

3

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

OganM

Posted 2018-10-02T14:22:23.177

Reputation: 211

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. – KBriggs – 2018-10-05T23:59:44.757

Just pushed an update. It does surprisingly well @KBriggs – OganM – 2018-10-06T00:00:13.833

Yea I got the update, no worries – KBriggs – 2018-10-06T00:00:47.820

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 – OganM – 2018-10-06T00:09:21.323

I'll do at least a million for the final, maybe 10 million if its feasible. – KBriggs – 2018-10-06T00:09:42.740

Seems fairly consistent on my end, with quick N=10000 rounds I get you in second or third every time – KBriggs – 2018-10-06T00:19:07.557

my run didn't have the other new bots and changes. Some other bot must be helping – OganM – 2018-10-06T00:21:17.973

2

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)

MJ713

Posted 2018-10-02T14:22:23.177

Reputation: 121

Thanks to @KBriggs for catching my bug. – MJ713 – 2018-10-05T19:45:24.217

@KBriggs updated AggressiveBoundedRandomBot to AggressiveBoundedRandomBotV2 – MJ713 – 2018-10-05T23:04:18.913

2

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.

Doug Gawne

Posted 2018-10-02T14:22:23.177

Reputation: 21

2

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.

Jo.

Posted 2018-10-02T14:22:23.177

Reputation: 974

1

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.

Draco18s no longer trusts SE

Posted 2018-10-02T14:22:23.177

Reputation: 3 053