Let's play some Reaper - Closed for Submissions NOTE: The winner of this competition is Jack!!!. No more submissions will be accepted.

Here is the chat room for this king-of-the-hill challenge. This is my first one so I'm open to suggestions!

Reaper is a game concept developed by the Art of Problem Solving which involves patience and greed. After modifying the game to fit a KOTH style contest (Thanks to @NathanMerrill and @dzaima for your suggestions and improvements), here is the challenge.

The game works as the following: we have a value known as the Reap that multiplies by a given constant every tick. After each tick, each bot has the option of "reaping", which means adding the current value of Reap to one's score, and reducing Reap down to 1.

However, there's a fixed number of ticks that a bot must wait in between "reaps", and a fixed number of points necessary to win the game.

Simple enough? Here are your inputs:

I/O

You are to write a function in Python 3 that takes 3 inputs. The first is self, used for referencing class objects (shown later). The second is the Reap, the current value of the Reap that you would earn if you were to "reap". The third is prevReap, a list of the bots that reaped during the previous tick.

Other objects you can access in your function:

self.obj: An object for your use to store information between ticks.
self.mult: The multiplier that Reap is multiplied by each tick
self.win: The score you need to win
self.points: Your current set of points
self.waittime: The amount of ticks that you must wait between reaps during the game
self.time: The number of ticks since your last reap
self.lenBots: The number of bots (including you) in the game.
self.getRandom(): Use to produce a random number between 0 and 1.


You MUST not edit any of the contents of these objects, except for self.obj.

You must output 1 to reap, and anything else (or nothing) to not reap. Note that if you reap when you haven't waited enough ticks, I'll ignore the fact that you have chosen to reap.

Rules

The parameters I will be using are winning_score=10000, multiplier=1.6-(1.2/(1+sqrt(x))), waittime = floor(1.5*x) where x is the number of bots in the KOTH.


The game ends when a player (or multiple) reach the winning score.
When multiple bots ask to reap at once, priority is given to the bots who have waited longer(in case of ties, the bots that have waited the max time all are allowed to reap and gain the points in the Reap)
Your bot must take no more than 100 ms on average across 5 ticks.
If you want to import libraries, ask! I'll try to add any libraries that I can run on my desktop version of Python (math is already imported: feel free to use it)
All of the standard loopholes for KoTHs, such as duplicate bots, 1-up bots, etc, are similarly banned.
Any bots that use any sort of randomness must use the getRandom function I've provided.


You can find the controller in the TIO link below. To use it, add the name of your function to BotList as a string, and then add the function to the code. Modify multiplier to change what the Reap is multiplied by each tick, modify winning_score to change what score is necessary to end the game, and modify waittime to change the number of ticks to wait between reaps.

For your convenience, here are some sample (and rather silly) bots. Submitting bots similar to these will not be permitted. However, they demonstrate how the controller works.

def Greedybot(self,Reap, prevReap):
    return 1
def Randombot(self,Reap, prevReap):
    if self.obj == None:
        self.obj=[]
    self.obj.append(prevReap)
    if self.getRandom()&gt;0.5:
        return 1


For those interested, here is the Controller with the 15 submissions built into it: Try it Online

FINAL RESULTS

WOO THEY ARE FINALLY HERE! Check the TIO Link above to see what code I used to generate the final standings. The results are not terribly interesting. Over the 1000 runs I did with different random seeds, the results were

1000 wins - Jack
0 wins - everyone else


Congratulations to the Bounty winner Jack!! (aka @Renzeee)
Sniper

A bot fueled by spite. Keeps track of opponent's cooldowns and scores. Attempts to keep others from winning. Pretty much never actually wins, but makes the game frustrating to play for others.

EDIT:


If reaping would cause it to win, reap.
If no one is >= 70% of the winning score:


If everyone else is on their cooldown, wait until the last possible moment to reap.
If anyone else would win by reaping the current value, and they are active now or would be active next turn, reap.
If at least half of the other users are on their cooldown, attempt to reap. This makes it difficult to target specific opponents, and so was removed.
Otherwise, reap 25% of the time (essentially to guarantee that this bot does reap SOMETIMES, just in case something weird happens, like everyone is waiting several turns).

If someone IS >= 70% of the winning score:


If Sniper can win a tiebreaker, and next round would be above the average Reap value for the highest scoring opponent, reap
If highest scoring opponent will leave their cooldown next turn, reap.





def Sniper(self, Reap, prevReap):
    # initialize opponents array
    if not hasattr(self.obj, "opponents"):
        self.obj.opponents = {}

    # initialize previous Reap value
    if not hasattr(self.obj, "lastReap"):
        self.obj.lastReap = 0

    # increment all stored wait times to see who will be "active" this turn
    for opponent in self.obj.opponents:
        self.obj.opponents[opponent]["time"] += 1

    # update opponents array
    for opponent in prevReap:
        # don't track yourself, since you're not an opponent
        if opponent != "Sniper":
            # initialize opponent
            if opponent not in self.obj.opponents:
                self.obj.opponents[opponent] = {"time": 0, "points": 0, "num_reaps": 0, "avg": 0}
            self.obj.opponents[opponent]["time"] = 0
            self.obj.opponents[opponent]["points"] += self.obj.lastReap
            self.obj.opponents[opponent]["num_reaps"] += 1
            self.obj.opponents[opponent]["avg"] = self.obj.opponents[opponent]["points"] / self.obj.opponents[opponent]["num_reaps"]

    # done "assigning" points for last round, update lastReap
    self.obj.lastReap = Reap

    # get current 1st place(s) (excluding yourself)
    winner = "" if len(self.obj.opponents) == 0 else max(self.obj.opponents, key=lambda opponent:self.obj.opponents[opponent]["points"])

    # you are ready now
    if self.time &gt;= self.waittime:
        # current Reap is sufficient for you to win
        if self.points + Reap &gt;= self.win:
            return 1

        if (
                # a 1st place exists
                winner != ''
                # if current 1st place is close to winning
                and self.obj.opponents[winner]["points"] / self.win &gt;= .7
        ):
            if (
                    # next round's Reap value will be above opponent's average Reap
                    (Reap * self.mult &gt;= self.obj.opponents[winner]["avg"])
                    # we have been waiting at least as long as our opponent (tiebreaker)
                    and self.time &gt;= self.obj.opponents[winner]["time"]
            ):
                return 1

                # current 1st place opponent will be active next round
            if self.obj.opponents[winner]["time"] + 1 &gt;= self.waittime:
                return 1

        else:
            if (
                    # everyone is waiting for their cooldown
                    all(values["time"] &lt; self.waittime for key, values in self.obj.opponents.items())
                    # and we're tracking ALL opponents
                    and len(self.obj.opponents) == self.lenBots - 1
                    # at least one person will be ready next turn
                    and any(values["time"] + 1 &gt;= self.waittime for key, values in self.obj.opponents.items())
            ):
                return 1

            if (
                    # opponent will be active next round
                    any( (values["time"] + 1 &gt;= self.waittime)
                         # current Reap value would allow opponent to win
                         and (values["points"] + Reap &gt;= self.win) for key, values in self.obj.opponents.items())
            ):
                return 1

            if (
                    # a 1st place exists
                    winner != ''
                    # current 1st place opponent will be active next round
                    and (self.obj.opponents[winner]["time"] + 1 &gt;= self.waittime)
                    # next round's Reap value will be above their average Reap
                    and (Reap * self.mult &gt;= self.obj.opponents[winner]["avg"])

            ):
                return 1

            # # at least half of opponents are waiting for their cooldown
            # if sum(values["time"] &lt; self.waittime for key, values in self.obj.opponents.items()) &gt;= (self.lenBots - 1) / 2:
            #     return 1

            # 25% of the time
            if self.getRandom() &lt;= .25:
                return 1

    # default return: do not snipe
    return 0


Bored

Just for fun, this bot was brought along by a friend and doesn't actually want to be here. They roll a d16 until they get a number in 1-9, then they attempt to reap anytime a number contains the chosen digit. (Going to look for a d10 would disrupt the game, which is rude, and 0 is just too easy!)

def Bored(self, Reap, prevReap):
    # if this is the first round, determine your fav number
    if not hasattr(self.obj, "fav_int"):
        r = 0

        while r == 0:
            # 4 bits are required to code 1-9 (0b1001)
            for i in range(0, 4):
                # flip a coin. Puts a 1 in this bit place 50% of the time
                if self.getRandom() &gt;= .50:
                    r += 2**i
            # if your random bit assigning has produced a number outside the range 1-9, try again
            if not (0 &lt; r &lt; 10):
                r = 0

        self.obj.fav_int = r

    # you are ready now
    if self.time &gt;= self.waittime:
        # current Reap is sufficient for you to win
        if self.points + Reap &gt;= self.win:
            return 1
        # do you like this value?
        if str(self.obj.fav_int) in str(Reap):
            return 1
        # do you like your wait time?
        if self.time % int(self.obj.fav_int) == 0:
            return 1

    # default return: do not reap
    return 0