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
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.
Little did you know that I was aware of this weakness from the beginning, and left it as a test to find whom I could trust to search for the weaknesses I didn’t know. Of the 3 (I think) to whom I showed the code early, only Lanrian reported it.
I’m curious how believable my lies were, I felt them to be pretty weak, hopefully it’s only because of my inside view.
I didn’t play a simulator so I didn’t care about the first.
About the second, I can tell you that another member alerted me that you seemed to have a hidden ally. They feared you had made an ally outside the clique, or just given the code to join the clique to a player know only to you. Which I thought was a possibility. Actually, I hoped for a few stowaways to boost our numbers.
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.
Oh, that was me I think. I had simply thought your comment meant you were preparing code with someone else. Whether he was inside the clique, outside it, or a non player helping you out I wasn’t sure, but I still recommended caution.
I did think it was weird that you’d let slip such information, but couldn’t see any reason for making people think you had allies, so I just thought that the most likely explanation was that a non player was helping you. Still, being cautious wouldn’t hurt.
I have to say I didn’t made the connection about simulation crashing software being outside the clique, likely because I wasn’t playing a simulator so I didn’t thought much about it.
All in all… I think it’s a lie that would work best on the people it wouldn’t need to work on. If I had thought to change a plan I had going based on the information you provided, I would have wondered a bit more about why you did that, perhaps getting suspicious.
But I still think it wouldn’t really be obvious as a lie to anyone.
On a side note, I really love this site. I can’t really recall any other game I’ve been in getting this tangled.
I didn’t know about __new__(), I only knew about redifining methods, so based on what you knew, your reasoning was correct.
I knew no one before starting the clique. Lanrian joined the same way as the others. If anything, Lanrian was suspicious because they insisted we put the random.seed() inside move() and make it pseudorandom so that simulators can accurately emulate our behaviour. The reason they gave was to better collaborate, and have the simulators play 2 against 3 instead of 3 against 3. I was mildly convinced and I still am suspicious of that move. They only late in the week reported the weakness, after you and philh passed on the chance to do so. But they did so soon after I showed them the code.
I was really paranoid about this and I feel you could have used this somehow.
The secrecy on the members was used to:
prevent members and potential members from worrying if there were too few current members. That was the purpose I had in mind when I made that choice. A few days before the end I still was not sure we’d be enough. I was also worried some members would drop if we were too little. So the 2 members who joined in the last 2 days really helped.
avoid any collusion between members that would not include me. And more generally receive any valuable information that members would like to share.
So I used that advantage only in a defensive way. But I did receive an offer that did inform me on more offensive uses, and impacted my payload, which I will elaborate on if the sender allows it.
I didn’t think about reporting the bug as making a sub-optimal but ethical choice – I just wanted to be part of a clique that worked instead of a clique where people defected. My aversion to lying might have affected my intuitions about what the correct choice was, though, idk ¯\_(ツ)_/¯
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.
I lied that I’ve already submitted one program detecting and crashing simulators. … 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.
I believed both of these lies, though if I’d come to rely on them at all I might have questioned them. But I assumed your friend was in the clique.
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.
I believed all lies! And I might’ve submitted a simulator if you hadn’t told the first, and would definitely have tried harder to simulator-proof my bot, so you did change my behaviour. Leaving the clique wouldn’t have been worth it, though. Even knowing that you lied about the 2nd thing, I assign decent probability to someone crashing all the simulators outside the clique. (I think this is incorrect, though – if you can figure out that you’re in a simulation, it’s way better to claim that you’ll be submitting 3 to scare the simulator into playing 2.)
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:
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)
Little did you know that I was aware of this weakness from the beginning, and left it as a test to find whom I could trust to search for the weaknesses I didn’t know. Of the 3 (I think) to whom I showed the code early, only Lanrian reported it.
I didn’t play a simulator so I didn’t care about the first.
About the second, I can tell you that another member alerted me that you seemed to have a hidden ally. They feared you had made an ally outside the clique, or just given the code to join the clique to a player know only to you. Which I thought was a possibility. Actually, I hoped for a few stowaways to boost our numbers.
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.
Oh, that was me I think. I had simply thought your comment meant you were preparing code with someone else. Whether he was inside the clique, outside it, or a non player helping you out I wasn’t sure, but I still recommended caution.
I did think it was weird that you’d let slip such information, but couldn’t see any reason for making people think you had allies, so I just thought that the most likely explanation was that a non player was helping you. Still, being cautious wouldn’t hurt.
I have to say I didn’t made the connection about simulation crashing software being outside the clique, likely because I wasn’t playing a simulator so I didn’t thought much about it.
All in all… I think it’s a lie that would work best on the people it wouldn’t need to work on. If I had thought to change a plan I had going based on the information you provided, I would have wondered a bit more about why you did that, perhaps getting suspicious.
But I still think it wouldn’t really be obvious as a lie to anyone.
On a side note, I really love this site. I can’t really recall any other game I’ve been in getting this tangled.
I didn’t know about __new__(), I only knew about redifining methods, so based on what you knew, your reasoning was correct.
I knew no one before starting the clique. Lanrian joined the same way as the others. If anything, Lanrian was suspicious because they insisted we put the random.seed() inside move() and make it pseudorandom so that simulators can accurately emulate our behaviour. The reason they gave was to better collaborate, and have the simulators play 2 against 3 instead of 3 against 3. I was mildly convinced and I still am suspicious of that move. They only late in the week reported the weakness, after you and philh passed on the chance to do so. But they did so soon after I showed them the code.
The secrecy on the members was used to:
prevent members and potential members from worrying if there were too few current members. That was the purpose I had in mind when I made that choice. A few days before the end I still was not sure we’d be enough. I was also worried some members would drop if we were too little. So the 2 members who joined in the last 2 days really helped.
avoid any collusion between members that would not include me. And more generally receive any valuable information that members would like to share.
So I used that advantage only in a defensive way. But I did receive an offer that did inform me on more offensive uses, and impacted my payload, which I will elaborate on if the sender allows it.
I stand by my reasoning! As long as we don’t yield to bullying, simulators are our friends, ensuring that the maximum payout is always payed out.
I didn’t think about reporting the bug as making a sub-optimal but ethical choice – I just wanted to be part of a clique that worked instead of a clique where people defected. My aversion to lying might have affected my intuitions about what the correct choice was, though, idk ¯\_(ツ)_/¯
Incidentally, you could also just redefine existing methods, which was how I planned to do it. Like,
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.
I believed both of these lies, though if I’d come to rely on them at all I might have questioned them. But I assumed your friend was in the clique.
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.
I believed all lies! And I might’ve submitted a simulator if you hadn’t told the first, and would definitely have tried harder to simulator-proof my bot, so you did change my behaviour. Leaving the clique wouldn’t have been worth it, though. Even knowing that you lied about the 2nd thing, I assign decent probability to someone crashing all the simulators outside the clique. (I think this is incorrect, though – if you can figure out that you’re in a simulation, it’s way better to claim that you’ll be submitting 3 to scare the simulator into playing 2.)