Disqualifying players for things they obviously wouldn’t do if they knew the rules of the game seems pretty cruel. I hope isusr just deletes that line for you.
Taleuntum
The links you posted do not work for me.(Nevermind)Wow, you are really confident in you winning. There are 10 players in the clique, so even if there are no players outside the clique (a dubious assumption) a priori there is 10% chance. If I had money I would bet with you.
I also think there is a good chance that a CloneBot wins. 10 possible member is a good number imo. i would say 80%.
I would say 70% for the (possibly accidental) betrayal.
Without seeing your jailbreak.py I can’t say how likely that others are able to simulate you.
What does “act out” mean in this context?
Yes, I feared that some might think my friend is in the clique. However I couldn’t just say that they are not in the clique, because that would have been too obvious. (like my other lie: “Yeah, I totally have another method for detecting being in a simulation even if the simulation runs in a separate process, but unfortunately I can’t reveal it.”) So I tried to imply it by speaking about him as if he is not in the conversation and him not commenting after I mentioned him. I hoped in case someone was planning to submit a simulator outside the clique they would try to sneakily inquire about whether my friend is in the clique or not and then I would have asked a random, not competing lesswronger to play the part of my friend.
Good to know. I’m a C++ guy which has a “one definition rule” not only for the translation unit, but for the whole program, so I incorrectly assumed that python is the same even though the languages are obviously very different.
Maybe it’s a little cheap to say this after you’ve revealed it, but it did actually occur to me that you might have deliberately made this weakness. Had I known that in Python you can redefine methods, I might have reported it, but the exploit with __new__() seemed pretty obscure (even though I didn’t know the other way and I did know this). The possibility of this being a test was also the reason I went with the “Oh I’m so busy, I didn’t have time to review the code..” excuse. I’m also curious whether Larion calculated with you deliberately planting the mistake or they had in-game ethics. Also, before you posted the list of the members publicly, you were the center of the clique and could control the information the clique members got. I was really paranoid about this and I feel you could have used this somehow. Have you thought along these lines?
About your second point, It’s nice I could make someone believe that I had an ally outside the clique.
Explanation of my strategy and thought process in chronological order
After seeing Vanilla_Cabs’s comment I lied to them about wanting to join the clique. I was undecided, but I figured seeing the code of the clique can be a great advantage if I can exploit some coding mistake and I can still decide to join later anyway if I want.
The first versions of CloneBot (the name of the program for our clique) did actually contain a mistake I could exploit (by defining the __new__() method of the class after the payload) and so this was my plan until Vanilla_Cabs fixed this mistake. After they fixed it, I didn’t notice any way I can take advantage, so I joined the clique in spirit.
Initially my plan for my bot was a simulator which simulates between 100 and 1000 turns of the opponent against a few candidate bots (ZviBot, TiTforTat, return 3, return 2, etc..) and then depending on the round number either chooses the one with the maximum point for me or the one which gets more points than the opponent. There were unfortunately two problems with this plan:
Bots who can detect if they are in a simulation can get me into an infinite loop which would disqualify my program, so I had to research the ways this might happen to know how to prevent it. I started by inspecting the code for AbstractTreeSpyBot and I did notice some ways to do this:
The code always instantiates a new object, so by storing past moves, I can detect if I’m in a simulation if my move() method’s argument is not None and I have not stored previous moves. This defect however is really specific to this program and other simulators can easily avoid it by storing the instantiated class object between rounds.
The code simply calls the simulated bot’s move() method which can be detected by inspecting the call stack and finding a local variable named ‘self’ with a method named ‘move’ of a class type not named the simulated bot’s classname. This method of detection can still be tricked by running the simulation in a different process (eg by calling the python interpreter from the shell, this has the advantage of having an easy and robust way to redefine get_opponent_source()), but still I expect this would DQ many submitted simulators. (I attached the code of a bot using this type of detection.)
Isusr posted a very unforgiving time limit. Unfortunately, I could not make simulator bot work in this timelimit. In fact, on my laptop even AbstractSpyTreeBot does not run in 0.05s (1 init + 100 moves) and to avoid detection I would have to call the python interpreter from the os which would again cost a lot of time. It does not even matter what the time limit is just that it is not a vague ‘don’t be too slow’ like it was initially, because I planned to simulate (100-1000 moves+init)*(number of programs I want to try), so if the number of programs I want to try is greater than 1 I would go over the limit if my opponent uses even just half the limit.
After this, seeing that I can’t make my simulator work, I abandoned the idea of me submitting a simulator. However seeing that some other types of simulator can still be pretty strong, I decided to disincentivize them, so when Larian cautioned in the clique-chat that simulator crashing bots weaken our clique (as isusr restarts the whole tournament in the event of DQ), so we should not use them, I lied that I’ve already submitted one program detecting and crashing simulators. Of course Larian’s point was valid, so obviously I did not even plan to do so. Some time later It occured to me that my claim that there is a simulator crashing bot in the clique might incentivize those who wanted to submit a simulator to leave the clique, so I added another lie that the method of detecting simulators was my friend’s idea (hopefully suggesting that there is another contestant with the same method outside the clique). I’m curious how believable my lies were, I felt them to be pretty weak, hopefully it’s only because of my inside view.
After this I had to come up with an actual program. My intrigue and life didn’t leave me with much time, so I finally submitted a ZviBot (a bot similar to the one described in the Darwin Game series) as a payload (for those not in our clique: payload is the part of the CloneBot which activates in the endgame against other clique members). While I did not have time to run simulations, I had three reasons for this submission:
ZviBot has a good track record in a similar competition.
Given that bots in this competition know the round number, I expect that many will get meaner (less cooperating) in the later rounds on the grounds that if they survived for this many rounds they are probably a significant portion of the pool, so they can try to kill others. However because the programs will use different rates of becoming uncooperating, they will get eliminated from the pool one by one as the others are still cooperatating. By not getting meaner my plan was to stay in the final equilibrium state with others.
I was curious how such a simple bot would perform.
Bot detecting simulators:
class MatrixCrashingBot(): def __init__(self, round=1): import inspect from time import sleep sf = inspect.stack() simulated = False enemy = "" for f in sf: if ('self' not in f.frame.f_locals): continue fo = f.frame.f_locals['self'] fod = dict(inspect.getmembers(fo)) if 'MatrixCrashingBot' != fo.__class__.__name__ and 'move' in fod: simulated = True enemy = fo.__class__.__name__ break if simulated: while 1: print("{} tried to simulate me :O >:\[ So I sent him to an infinite cycle!\nNote to the tournament organizer: The infinite cycle is run by {} not by the MatrixCrashingBot! ".format(enemy, enemy)) sleep(3) def move(self, previous=None): return 3
Bot I finally submitted: (everything exept the payload is written by Vanilla_cabs. The core idea (alternating 3s and 2s) of the payload is from Zvi)
class CloneBot(): def __init__(self, round=0): import math import random import extra self.math = math self.random = random self.extra = extra self.showdownRound = 90 # after this round, your personal program takes over self.round = round # the current round self.myMoves = [] # all the moves you've made, first to last self.opponentMoves = [] # all the moves your opponent has made, first to last my_source_raw = extra.__getattribute__(''.join(['ge','t_','my','_s','ou','rce']))(self) opponent_source_raw = extra.__getattribute__(''.join(['ge','t_','op','po','ne','nt','_s','ou','rce']))(self) my_source = "\n".join([" ".join(line.split('\t')).rstrip() for line in my_source_raw.splitlines()]) opponent_source = "\n".join([" ".join(line.split('\t')).rstrip() for line in opponent_source_raw.splitlines()]) if not 'def payload(self) :' in opponent_source : self.is_opponent_clone = False else : my_common_code, my_payload = my_source.rsplit('def payload(self) :', 1) opponent_common_code, opponent_payload = opponent_source.rsplit('def payload(self) :', 1) if my_common_code != opponent_common_code : self.is_opponent_clone = False else : self.is_opponent_clone = True for line in opponent_payload.split("\n") : # checks that no common method or property is overwritten after the payload # allows the innocuous command "foo = 'bar'" by member's demand if line.lstrip() != "" and line != "foo = 'bar'" and line[0:8] != " " : self.is_opponent_clone = False break if self.is_opponent_clone : payload_length_difference = len(my_payload) - len(opponent_payload) if my_payload != opponent_payload : # compares payloads without reading them # fair way to decide who starts with 3 between two clones # for 100% protection against ties, personalize your payload with a comment self.high_first = (my_payload < opponent_payload) == ((payload_length_difference+round) % 2 == 1) def move(self, previous=None) : self.turn = len(self.myMoves) # the current turn # pseudorandom to allow simulators to collaborate self.random.seed((self.round+1) * (self.turn+1) * (7 if previous==None else (previous+1))) if previous != None : self.opponentMoves.append(previous) if self.is_opponent_clone : if self.round < self.showdownRound : output = self.cooperateWithClone() else : output = self.payload() else : output = self.default() self.myMoves.append(output) return output def defaultCooperation(self) : # factor influencing behaviour with non-clones, 1 at round 0, 0 at round 60 return max(0.0, float(self.showdownRound - (self.round*1.5)) / self.showdownRound) def cooperateWithClone(self) : if self.turn == 0 : if self.high_first : return 3 else : return 2 else : return self.opponentMoves[-1] def default(self) : if self.turn == 0 : if self.random.random() < 0.5 * self.defaultCooperation() : return 2 else : return 3 elif self.myMoves[-1] + self.opponentMoves[-1] == 5 : if self.myMoves[-1] == 2 : return 3 # tit for tat elif self.myMoves[-1] == 3 : if self.turn >= 2 : if self.myMoves[-2] == 3 and self.opponentMoves[-2] == 2 : return 3 # stable 3 against 2 if self.random.random() < self.defaultCooperation() * 1.2 : return 2 # cooperation else : return 3 # maintain 3 against 2 else : return self.myMoves[-1] # free candy elif self.myMoves[-1] + self.opponentMoves[-1] < 5 : return 5 - self.opponentMoves[-1] else : # sum > 5 if self.random.random() < self.defaultCooperation() * max(0, 50-self.turn) / 100.0 : return 2 # back down else : return 3 # maintain def payload(self) : # put a personal word here to guarantee no tie during cooperation: in a pretty body no one can see your rotten soul # put what you want to play for the showdown # no line after 'def payload(self)' should have less than 8 whitespaces at the beginning, # unless it's an empty or only whitespace line # # Idea of solution: most program will gradually get meaner as rounds # progress, but because they do so at different rates # they will get eliminated as other, still cooperating bots outcompete their # meanness. By not getting meaner I plan to stay in the final equilibrium state. # Otherwise it's a ZviBot. if self.turn == 0: self.switched = False return 3 else: # against very submissive bots if self.turn >= 3 and self.opponentMoves[-1] < 3 and self.opponentMoves[-2] < 3 and self.opponentMoves[-3] < 3: return 5-self.opponentMoves[-1] # against bots who are very bad at detecting patterns if self.turn >= 5 and self.opponentMoves[-1] == 3 and self.opponentMoves[-2] == 2 and self.opponentMoves[-3] == 3 and \ self.myMoves[-1] == 3 and self.myMoves[-2] == 2 and self.myMoves[-3] == 3 and not self.switched: self.switched = True return 3; if self.myMoves[-1] == 2: return 3 elif self.myMoves[-1] == 3: return 2
In what order do programs get disqualified? For example, if I submit a program with an infinite loop, every other program using simulation will also go into infinite loop when meeting with my program as detecting infinite loops generally isn’t theoretically feasible. Is my program disqualified before the others? What is the general principle?
EDIT: An unrelated question: Do round numbers start from 0 or 1? In the post you write “Unlike Zvi’s original game, you do get to know what round it is. Rounds are indexed starting at 0.”, but also: “Your class must have an __init__(self, round=1) [..]”. Why not have the default initializer also use 0 if the round numbers start from zero?
You should also check whether ‘exec’ is in the program code string, because someone could call getopponentsource with exec and caesar-encryption, otherwise you will be DQ’d if someone submits a program like that. (However, rebinding getopponentsource is probably more elegant than this type of static analysis.)
I don’t have much time, so I’ve only checked the first study. The numbers come from this one: https://ultrasuninternational.com/wp-content/uploads/raharusun-et-al-2020_patterns_of_covid-19_mortality_and_vitamin_d_an_indonesian_study.pdf
I looked a bit more and found this: https://www.cambridge.org/core/journals/british-journal-of-nutrition/article/covid19-and-misinformation-how-an-infodemic-fueled-the-prominence-of-vitamin-d/8AC1297F0D6F4196938FB13A85A817A3
It seems to be misinformation.
I couldn’t find the second study, though I haven’t looked that hard tbh.
Third study: Vitamin D Supplementation Could Possibly Improve Clinical Outcomes of Patients Infected with Coronavirus-2019 (COVID-2019) from Mark Alipio
Your betrayal of the clique is very nice, hats off to you. I also liked your idea of getting others not that interested in the game to submit bots helping you, It’s a pity it did not occur to me.
However, I think you are, too, overconfident in you winning. I’ve run a simulation of the whole tournament till the 160th round with 8 bots (MatrixCrashingBot, TitforTatBot, PasswordBot1, PasswordBot2, EmptyCloneBot, earlybird, incomprehensiblebot, CliqueZviBot) and in the final equilibrium state there are three bots: earlybird, incomprehensiblebot and CliqueZviBot with roughly equal populations. While PasswordBots do help you at the start your lead seems to disappear later when the dumb bots and non-clique members die (which is nice, because your bot’s output when simulating is pretty annoying). Sure, it’s just one run of the tournament with a low number of participants (it’s slow to do the tournament on my laptop), but it’s something.