56
\$\begingroup\$

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.

\$\endgroup\$
  • 4
    \$\begingroup\$ FWIW, I plan to use a neural network trained on all the other bots just for fun once you set up the controller :) \$\endgroup\$ – Quintec Oct 2 '18 at 22:27
  • 2
    \$\begingroup\$ The type check was for the benefit of antiantiantiantiupyoursbot. I'll find another way \$\endgroup\$ – KBriggs Oct 4 '18 at 16:10
  • 3
    \$\begingroup\$ @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 \$\endgroup\$ – Markov Chained Oct 4 '18 at 21:27
  • 3
    \$\begingroup\$ WOOOOOOOOOOOOOO! \$\endgroup\$ – Sarcoma Oct 6 '18 at 15:23
  • 5
    \$\begingroup\$ Oh my god. The deliberate trolling of changing mean_kick to always return zero when it was used so many places is brilliant. \$\endgroup\$ – Magua Oct 6 '18 at 17:07

37 Answers 37

12
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$ – Mego Oct 4 '18 at 23:58
17
\$\begingroup\$

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.

\$\endgroup\$
  • 1
    \$\begingroup\$ Hahahaha this is beautiful. I'm undecided if I should allow it, but I'll let it play for now since I didn't think to say it wasn't allowed. You got the function names wrong in a few places - see the controller on github. \$\endgroup\$ – KBriggs Oct 3 '18 at 18:18
  • 1
    \$\begingroup\$ It does very well, obviously, but it still loses to Kick Bot \$\endgroup\$ – KBriggs Oct 3 '18 at 19:20
  • 1
    \$\begingroup\$ Ha, good effort! \$\endgroup\$ – Sarcoma Oct 3 '18 at 19:28
  • 1
    \$\begingroup\$ @Sarcoma I couldn't have done it without you. ;) I really like your bot, too, buddy. \$\endgroup\$ – Qfwfq Oct 3 '18 at 20:03
  • 1
    \$\begingroup\$ Sarcomabot's Upyoursbot protection really messes with this one \$\endgroup\$ – KBriggs Oct 3 '18 at 20:57
15
\$\begingroup\$

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
\$\endgroup\$
  • 11
    \$\begingroup\$ Welp, that was inevitable \$\endgroup\$ – KBriggs Oct 2 '18 at 16:07
  • \$\begingroup\$ I was going to add a pacifist bot as well but I don't want to flood your challenge with brain dead bots \$\endgroup\$ – DobromirM Oct 2 '18 at 16:19
  • 5
    \$\begingroup\$ Based on some quick testing, the kamikaze bot deosn't change much - all it does is remove another bot from the round at random, which, over a large enough number of tournaments, simply averages to zero. The One hot is neat, though. Without it, my AverageBot tends to do the best - but if there are a few OneShots in play, is skews the average toward large numbers and tends to make the AverageBots die off quickly. Same for LastBot. You can really mess with the behavior of other robots by skewing your own betting patterns. With OneShot in play, RandomBot wins . Without it, AverageBot wins. \$\endgroup\$ – KBriggs Oct 2 '18 at 16:19
14
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Very nice, thanks for the bot! I'll give some stats when I get a few more. \$\endgroup\$ – KBriggs Oct 2 '18 at 16:55
  • \$\begingroup\$ 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 \$\endgroup\$ – Yodie Oct 2 '18 at 16:58
  • \$\begingroup\$ It runs, so no worries there \$\endgroup\$ – KBriggs Oct 2 '18 at 17:00
  • \$\begingroup\$ @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. \$\endgroup\$ – Jonathan Frech Oct 2 '18 at 17:04
  • 2
    \$\begingroup\$ I think this bot does quite well if it makes it past the first round, but because so many people bet big ont he first round it almost always dies early. You might improve performance by changing the initial behavior to bid higher when there is no history. For example, if you triple your no-history bet, this bot wins by a comfortable margin among contestants so far. \$\endgroup\$ – KBriggs Oct 2 '18 at 19:22
11
\$\begingroup\$

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))
\$\endgroup\$
  • \$\begingroup\$ Nice. I'm seeing a lot of submissions that directly counter others now, that's exactly what I was hoping for \$\endgroup\$ – KBriggs Oct 3 '18 at 1:04
  • \$\begingroup\$ Double return on the last line? \$\endgroup\$ – Veskah Oct 3 '18 at 1:56
  • \$\begingroup\$ Ah I hadn't run it yet or I would have caught that. \$\endgroup\$ – KBriggs Oct 3 '18 at 2:30
  • \$\begingroup\$ This one has taken a comfortable lead! \$\endgroup\$ – KBriggs Oct 3 '18 at 13:53
  • 1
    \$\begingroup\$ @KBriggs here is some backup! \$\endgroup\$ – Johan Oct 4 '18 at 22:50
8
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Comments are not for extended discussion; this conversation has been moved to chat. \$\endgroup\$ – Mego Oct 4 '18 at 23:57
8
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ You'll have to rename him, there is already a kamikaze bot ^_^ \$\endgroup\$ – KBriggs Oct 2 '18 at 17:01
  • \$\begingroup\$ So far this one is the winner, though \$\endgroup\$ – KBriggs Oct 2 '18 at 17:03
  • \$\begingroup\$ Your function ceil appears not to be defined. \$\endgroup\$ – Jonathan Frech Oct 2 '18 at 17:05
  • \$\begingroup\$ I changed to np.ceil() to run it \$\endgroup\$ – KBriggs Oct 2 '18 at 17:06
  • \$\begingroup\$ edited, thanks for the heads-up \$\endgroup\$ – Belhenix Oct 2 '18 at 17:21
7
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ Very cool. How many are needed? I'll probably have to cap the number of entries... \$\endgroup\$ – KBriggs Oct 2 '18 at 21:32
  • \$\begingroup\$ Your code block appears to miss your source's first line. \$\endgroup\$ – Jonathan Frech Oct 2 '18 at 21:41
  • \$\begingroup\$ 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. \$\endgroup\$ – Jim Hat Oct 2 '18 at 21:42
  • \$\begingroup\$ You have a bug: the bot will identify anyone with len(history)>1 as a gang member \$\endgroup\$ – KBriggs Oct 2 '18 at 22:01
  • \$\begingroup\$ My bad, should be fixed now. \$\endgroup\$ – Jim Hat Oct 2 '18 at 22:07
6
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 2 '18 at 18:32
  • \$\begingroup\$ I updated the contest based on your request \$\endgroup\$ – KBriggs Oct 2 '18 at 18:45
  • \$\begingroup\$ @KBriggs Thanks :) \$\endgroup\$ – Quintec Oct 2 '18 at 19:13
  • \$\begingroup\$ You also need to add 1 to start-alive, since this is 0 for the first round \$\endgroup\$ – KBriggs Oct 2 '18 at 19:15
  • \$\begingroup\$ @KBriggs fixed, actually should +2 so it doesn’t return 0, lol \$\endgroup\$ – Quintec Oct 2 '18 at 19:23
6
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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 \$\endgroup\$ – KBriggs Oct 2 '18 at 16:40
  • 3
    \$\begingroup\$ If HP is 0, then I shouldn't be invoked. :P You're right about HP 1 though. \$\endgroup\$ – Draco18s Oct 2 '18 at 16:41
6
\$\begingroup\$

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
\$\endgroup\$
  • 1
    \$\begingroup\$ Your bid should be an integer, so as long as you floor or ceil your base value to get rid of the decimal it's OK \$\endgroup\$ – KBriggs Oct 2 '18 at 19:11
  • \$\begingroup\$ Yep, I floor it right after doing all the calculations. Thanks for the quick reply! \$\endgroup\$ – MegaWidget Oct 2 '18 at 19:21
  • 2
    \$\begingroup\$ You might be able to optimize this if you aren't dividing your hp by the number of remaining contestants but by the number of remaining rounds (which should be math.ceil(math.log(alive, 2)) \$\endgroup\$ – Black Owl Kai Oct 2 '18 at 22:22
  • 1
    \$\begingroup\$ Based on other bots most of them seem to front load bids, so this might improve if you increase the first round bid above 10 \$\endgroup\$ – KBriggs Oct 2 '18 at 22:22
  • \$\begingroup\$ 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! \$\endgroup\$ – MegaWidget Oct 3 '18 at 0:04
5
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ 5th place on the first try, not bad at all \$\endgroup\$ – KBriggs Oct 3 '18 at 15:48
5
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 4 '18 at 12:46
5
\$\begingroup\$

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)
\$\endgroup\$
  • 2
    \$\begingroup\$ I think len(history)*len(history) could be changed to len(history)**2 if my knowledge of python is correct. \$\endgroup\$ – Yodie Oct 3 '18 at 0:03
  • \$\begingroup\$ You have a divide by zero for when len(history)==0 \$\endgroup\$ – KBriggs Oct 3 '18 at 13:30
  • \$\begingroup\$ Code has been updated. Upon finding no history it goes to the first else \$\endgroup\$ – Markov Chained Oct 3 '18 at 17:12
  • \$\begingroup\$ oi............. \$\endgroup\$ – Sarcoma Oct 4 '18 at 11:59
  • 2
    \$\begingroup\$ @Sarcoma it is a cutthroat bot world out there man! \$\endgroup\$ – Markov Chained Oct 4 '18 at 18:22
5
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ There are quite a few bots that exist to take advantage of exactly this behavior, so you might have a hard time gaining traction! \$\endgroup\$ – KBriggs Oct 4 '18 at 12:51
  • \$\begingroup\$ @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 \$\endgroup\$ – meh Man Oct 4 '18 at 13:34
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 4 '18 at 14:04
  • \$\begingroup\$ @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 \$\endgroup\$ – meh Man Oct 4 '18 at 15:44
  • \$\begingroup\$ @KBriggs Please do this one as well, Muchas gracias \$\endgroup\$ – meh Man Oct 4 '18 at 16:31
4
\$\begingroup\$

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

\$\endgroup\$
4
\$\begingroup\$

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)
\$\endgroup\$
  • 1
    \$\begingroup\$ Neat idea, but I see two issues. One is most other bots bid big on the first round, so this one will probably die early most of the time. The second is that in the final, most bots bid hp-1, so this one will lose those unless you have double their hp. But for the intermediate rounds I like the idea. If you address the two special cases you can probably improve performance. \$\endgroup\$ – KBriggs Oct 3 '18 at 12:38
4
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ Opponent_Average_Bid = Opponent_Remaining_HP / float(len(history)) ZeroDivisionError: float division by zero. This line needs to handle the 0 length history case. \$\endgroup\$ – KBriggs Oct 3 '18 at 14:22
  • \$\begingroup\$ Thanks, I'll correct it. \$\endgroup\$ – SSight3 Oct 3 '18 at 14:24
  • \$\begingroup\$ Fixed. Let me know if there are any other errors. \$\endgroup\$ – SSight3 Oct 3 '18 at 14:28
  • 1
    \$\begingroup\$ Couple of syntax errors: missing :` after else, math.[func] -> np.[func], and at one point you use Lowest where you mean LowestBid. All fixed in the controller on github and scores updated shortly. \$\endgroup\$ – KBriggs Oct 3 '18 at 14:34
  • \$\begingroup\$ Thanks. Fixed all the aforementioned errors in the post. \$\endgroup\$ – SSight3 Oct 3 '18 at 14:49
4
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ Cute. I think this one will do better with a very large bot pool. \$\endgroup\$ – KBriggs Oct 2 '18 at 21:56
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 3 '18 at 13:37
  • \$\begingroup\$ @KBriggs Updated the code to fix it. \$\endgroup\$ – Bob Cratchit Oct 3 '18 at 16:00
  • 1
    \$\begingroup\$ confirmed, score update incoming. You're in the top 10 for sure. \$\endgroup\$ – KBriggs Oct 3 '18 at 16:03
  • \$\begingroup\$ @KBriggs I added a couple more bots to try out :) \$\endgroup\$ – Bob Cratchit Oct 3 '18 at 20:30
4
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ you need to use np.maximum instead of np.max, same for min \$\endgroup\$ – KBriggs Oct 4 '18 at 5:43
  • \$\begingroup\$ @KBriggs Thanks :) Hmm, it seems like the generic bots rule this game \$\endgroup\$ – Quintec Oct 4 '18 at 17:26
  • \$\begingroup\$ Seems like they would be pretty easy targets, I'm surprised nobody has made a parasite yet \$\endgroup\$ – KBriggs Oct 4 '18 at 17:28
  • \$\begingroup\$ @KBriggs Yeah, I'm surprised. Time to add protections... \$\endgroup\$ – Quintec Oct 4 '18 at 17:28
  • \$\begingroup\$ Still planning on making neuralbot? \$\endgroup\$ – KBriggs Oct 4 '18 at 17:31
4
\$\begingroup\$

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
\$\endgroup\$
4
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ Pretty cool idea. Sacking the first round is a new trend in bots, and it seems to be working reasonably well. \$\endgroup\$ – KBriggs Oct 4 '18 at 12:50
  • \$\begingroup\$ @KBriggs I've updated this response to contain my second attempt. Mentioning you as per the new rules. Great puzzle btw! \$\endgroup\$ – Wazz Oct 4 '18 at 13:33
  • \$\begingroup\$ Want me to enter both, or just the latest version? Right now it's just V2 \$\endgroup\$ – KBriggs Oct 4 '18 at 14:00
  • \$\begingroup\$ @KBriggs I'd like both entered if that's okay please. Would be good to see how they weigh up against each other. \$\endgroup\$ – Wazz Oct 4 '18 at 14:04
  • \$\begingroup\$ Pretty similar performance overall \$\endgroup\$ – KBriggs Oct 4 '18 at 14:09
4
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ 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 \$\endgroup\$ – Kevin Oct 8 '18 at 5:14
4
\$\begingroup\$

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)
\$\endgroup\$
  • 1
    \$\begingroup\$ I think your indentation is a bit off. \$\endgroup\$ – Jonathan Frech Oct 5 '18 at 7:48
  • \$\begingroup\$ oops, thanks, strange code editor messed with the first line \$\endgroup\$ – HRSE Oct 5 '18 at 9:55
  • \$\begingroup\$ a valuable lesson in trusting code that you don't control \$\endgroup\$ – OganM Oct 6 '18 at 21:49
4
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 5 '18 at 3:56
  • \$\begingroup\$ @KBriggs Added a new bot with new and improved odds ;) \$\endgroup\$ – Joshua Webb Oct 5 '18 at 6:53
  • \$\begingroup\$ Want them both in there, or just 1? \$\endgroup\$ – KBriggs Oct 5 '18 at 12:14
  • \$\begingroup\$ @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 \$\endgroup\$ – Joshua Webb Oct 6 '18 at 3:39
  • 1
    \$\begingroup\$ Yep, you still have time \$\endgroup\$ – KBriggs Oct 6 '18 at 12:16
4
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ Really neat idea. I wonder how much impact it willl have \$\endgroup\$ – KBriggs Oct 6 '18 at 0:22
  • \$\begingroup\$ error: return np.random.randint(history[-1], hp/2): ValueError: low >= high This case needs to be handled somehow \$\endgroup\$ – KBriggs Oct 6 '18 at 0:24
  • \$\begingroup\$ @KBriggs should be fixed now! \$\endgroup\$ – Maya Sol Oct 6 '18 at 12:06
  • \$\begingroup\$ @KBriggs updated to fix randomization \$\endgroup\$ – Maya Sol Oct 6 '18 at 13:45
3
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ I suspect you don't have to worry about having 2^9 opponents ^_^. \$\endgroup\$ – KBriggs Oct 2 '18 at 22:43
  • \$\begingroup\$ But with an opening bet of 10, he's rarely going to make it past the first round \$\endgroup\$ – KBriggs Oct 2 '18 at 22:47
  • \$\begingroup\$ 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. \$\endgroup\$ – AlexRacer Oct 2 '18 at 23:12
  • \$\begingroup\$ Haha fair enough \$\endgroup\$ – KBriggs Oct 2 '18 at 23:15
3
\$\begingroup\$

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)
\$\endgroup\$
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 4 '18 at 12:25
  • \$\begingroup\$ 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. \$\endgroup\$ – KBriggs Oct 4 '18 at 12:36
  • \$\begingroup\$ Nice catch @KBriggs. Think I fixed it. \$\endgroup\$ – Jesús Ros Oct 4 '18 at 15:04
  • \$\begingroup\$ 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 \$\endgroup\$ – KBriggs Oct 4 '18 at 15:06
  • \$\begingroup\$ @KBriggs probably because ceil returns a float. Missed that one... Ty again :P \$\endgroup\$ – Jesús Ros Oct 4 '18 at 15:15
3
\$\begingroup\$

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

\$\endgroup\$
  • \$\begingroup\$ Pretty good first shot, score update incoming \$\endgroup\$ – KBriggs Oct 5 '18 at 18:26
  • \$\begingroup\$ @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 \$\endgroup\$ – snetch Oct 5 '18 at 18:42
  • \$\begingroup\$ You squeeze a little more juice out of him \$\endgroup\$ – KBriggs Oct 5 '18 at 18:51
3
\$\begingroup\$

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
\$\endgroup\$
  • \$\begingroup\$ Not a bad first shot, scores have been updated \$\endgroup\$ – KBriggs Oct 5 '18 at 17:36
  • \$\begingroup\$ @KBriggs I have updated the bot so the fraction detector actually works. \$\endgroup\$ – Diamond Dust Oct 5 '18 at 19:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.