├── __init__.py ├── .gitignore ├── README.md ├── LICENSE.txt ├── bot_player.py ├── arena.py ├── tournament_results.py ├── morality_calculator.py └── the_bots.py /__init__.py: -------------------------------------------------------------------------------- 1 | print("\n*** Modeling Morality In Iterated Prisoner's Dilemma ***\n") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | IPD_Morality 2 | ============ 3 | 4 | Run Iterated Prisoner's Dilemma tournaments. Judge the bots according to various morality metrics. 5 | 6 | If you define your own bots or morality metrics, I would love for you to let me know about them at `tscizzle@gmail.com`. Also, if you see any mistakes, ways to improve, or just comments in general, feel free to contact me by that email as well. 7 | 8 | Usage 9 | ----- 10 | 11 | Fill in the `if __name__ == '__main__':` block at the bottom of `arena.py` (an example is given in `arena.py` already), navigate in your terminal to the root directory of IPD_Morality, and run `python arena.py`. 12 | 13 | Some Cool Stuff 14 | --------------- 15 | 16 | Robin Berjon made http://darobin.github.io/ipd-morality/, a cool interface for running tournaments, and it even includes an interesting evolutionary variation. 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tyler Singer-Clark 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /bot_player.py: -------------------------------------------------------------------------------- 1 | ######## 2 | ## 3 | ## Class to represent a bot in an IPD tournament 4 | ## 5 | ######## 6 | 7 | 8 | class BotPlayer(object): 9 | """ 10 | This class should be inherited from by bots who define their 11 | own getNextMove method. That is how bots have their own strategy. 12 | self.name is the name of the strategy employed 13 | self.description is an explanation of the strategy 14 | self.tournament_id can be assigned upon beginning each tournament 15 | """ 16 | def __init__(self, name, description=None): 17 | self.name = name 18 | self.description = description 19 | if not self.description: 20 | self.description = self.name 21 | self.tournament_id = None 22 | 23 | def __str__(self): 24 | return self.name 25 | 26 | def getNextMove(self, pastMoves, payoffs, w): 27 | """ 28 | Given the history of interactions with another bot, 29 | output the next move, either cooperate or defect 30 | 31 | This method is to be overridden by bots and should use only static 32 | initialization variables, randomness, and past moves in order to make 33 | a decision. No other state should be updated/saved/taken into account. 34 | 35 | TODO: Is that 'no other state' thing valid? What if a bot wants to 36 | do some behavior only once, but when exactly it does so is randomly 37 | determined? I believe this could require some saved information besides 38 | the history of moves, but this seems like a valid strategy. 39 | 40 | ARGS: 41 | pastMoves: array of tuples, where each tuple is the pair 42 | of choices made that turn by this bot and its partner 43 | [(myMove1, hisMove1), (myMove2, hisMove2), ...] and 44 | the moves are represented by 'C' for "Cooperate" or 'D' 45 | for "Defect". For example, [('C', 'D'), ('D', 'D'), ...] 46 | 47 | RETURNS: 48 | nextMove: 'C' for "Cooperate" or 'D' for "Defect" 49 | """ 50 | 51 | ## this method should be overridden, so this return value 52 | ## doesn't matter 53 | return 'D' 54 | -------------------------------------------------------------------------------- /arena.py: -------------------------------------------------------------------------------- 1 | ######## 2 | ## 3 | ## The arena which runs tournaments of bots 4 | ## 5 | ######## 6 | 7 | 8 | import random 9 | 10 | import bot_player as bp 11 | import tournament_results as tr 12 | import morality_calculator as mc 13 | 14 | 15 | class Arena(object): 16 | """ 17 | Hosts tournaments of bots 18 | """ 19 | def __init__(self): 20 | pass 21 | 22 | def generate_interaction_lengths(self, w, numMeetings): 23 | """ 24 | Based on a probability of continuing each step, generate 25 | interaction lengths for the bot pairs 26 | 27 | ARGS: 28 | - w: probability of interaction continuing at each step 29 | - numMeetings: number of interaction_lengths needed to be 30 | generated 31 | 32 | RETURNS: 33 | - interaction_lengths: a list of integers representing how 34 | long each meeting between bots will be (if the list is n 35 | long, it is because each bot pair meets n times) 36 | """ 37 | interaction_lengths = [] 38 | i = 0 39 | while i < numMeetings: 40 | meeting_length = 1 41 | while True: 42 | r = random.random() 43 | if r > w: 44 | break 45 | else: 46 | meeting_length += 1 47 | interaction_lengths.append(meeting_length) 48 | i += 1 49 | return interaction_lengths 50 | 51 | def bot_interaction(self, bot1, bot2, interaction_length, 52 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 53 | """ 54 | Two bots paired together interacting 55 | 56 | ARGS: 57 | - bot1, bot2: instances of BotPlayer (presumably subclasses 58 | of BotPlayer), representing the two participating bots 59 | - interaction_length: how many turns bot1 and bot2 play in 60 | this interaction 61 | 62 | RETURNS: 63 | - past_moves: list of every move that occurred during the 64 | interaction 65 | """ 66 | past_moves_1 = [] 67 | past_moves_2 = [] 68 | i = 0 69 | while i < interaction_length: 70 | bot1_move = bot1.getNextMove(past_moves_1, 71 | payoffs=payoffs, w=w) 72 | bot2_move = bot2.getNextMove(past_moves_2, 73 | payoffs=payoffs, w=w) 74 | next_moves_1 = (bot1_move, bot2_move) 75 | next_moves_2 = (bot2_move, bot1_move) 76 | past_moves_1.append(next_moves_1) 77 | past_moves_2.append(next_moves_2) 78 | i += 1 79 | return past_moves_1 80 | 81 | def validate_tournament_inputs(self, botList, numMeetings, payoffs, w): 82 | """ 83 | Make sure the inputs to runTournament make sense and if they do not, 84 | say why in the list 'errors' 85 | 86 | ARGS: 87 | - botList: list of bots to participate in the tournament 88 | - w: probability of interaction continuing at each step 89 | - numMeetings: number of times each bot is paired with each 90 | other bot 91 | - payoffs: defines the scores for each Prisoner's Dilemma situation 92 | 93 | RETURNS: 94 | - errors: list or error messages to let the user know what is wrong 95 | with the inputs, if anything 96 | """ 97 | errors = [] 98 | # botList has to be a list of BotPlayer instances 99 | for bot in botList: 100 | if not isinstance(bot, bp.BotPlayer): 101 | errors.append("botList must be a list of BotPlayer objects") 102 | break 103 | if int(numMeetings) != numMeetings: 104 | errors.append("numMeetings must represent an integer") 105 | if numMeetings < 1: 106 | errors.append("numMeetings must be at least 1") 107 | if not (payoffs['T'] > payoffs['R'] > payoffs['P'] > payoffs['S']): 108 | errors.append("payoffs must obey T > R > P > S") 109 | if not (2*payoffs['R'] > payoffs['T'] + payoffs['S']): 110 | errors.append("payoffs must obey 2*R > T + S") 111 | if not (0 < w < 1): 112 | errors.append("w must be a number between 0 and 1") 113 | return errors 114 | 115 | def runTournament(self, botList, numMeetings, 116 | payoffs={'T':5,'R':3,'P':1,'S':0}, w=0.995): 117 | """ 118 | Main method, partners each bot with each other bot with 119 | w probability of ending each turn (length of interactions 120 | is determined (using w) before any pairings, so all 121 | pairings use the same list of interaction lengths) 122 | 123 | ARGS: 124 | - botList: list of bots to participate in the tournament 125 | - w: probability of interaction continuing at each step 126 | - numMeetings: number of times each bot is paired with each 127 | other bot 128 | - payoffs: defines the scores for each Prisoner's Dilemma situation 129 | 130 | RETURNS: 131 | - tourney_res: TournamentResults object with all the info 132 | """ 133 | 134 | # validate inputs 135 | error_messages =\ 136 | self.validate_tournament_inputs(botList, numMeetings, payoffs, w) 137 | if error_messages: 138 | print(error_messages) 139 | return -1 140 | 141 | # dictionary of interactions to pass to TournamentResults 142 | interactions = {} 143 | 144 | # determine length of each interaction based on w 145 | interaction_lengths =\ 146 | self.generate_interaction_lengths(w, numMeetings) 147 | 148 | # assign each bot a tournament id number 149 | for t_id, bot in enumerate(botList): 150 | bot.tournament_id = t_id 151 | 152 | # pair each bot with each other bot and save the results 153 | num_bots = len(botList) 154 | for i in xrange(num_bots): 155 | for j in xrange(i, num_bots): 156 | bot1 = botList[i] 157 | bot2 = botList[j] 158 | meeting_results_list = [] 159 | for m in xrange(numMeetings): 160 | interaction_length = interaction_lengths[m] 161 | meeting_results =\ 162 | self.bot_interaction(bot1, bot2, interaction_length,\ 163 | payoffs=payoffs, w=w) 164 | meeting_results_list.append(meeting_results) 165 | interactions[(bot1.tournament_id, bot2.tournament_id)] =\ 166 | meeting_results_list 167 | tourney_res = tr.TournamentResults(botList, interactions, payoffs) 168 | return tourney_res 169 | 170 | 171 | ## TODO: add capability for error/noise 172 | 173 | 174 | ## TODO: extend to ecological (evolutionary) environment 175 | 176 | 177 | if __name__ == "__main__": 178 | 179 | import the_bots 180 | 181 | a = Arena() 182 | 183 | #----------# 184 | 185 | num_meetings = 5 186 | 187 | b1 = the_bots.ALL_D() 188 | b2 = the_bots.ALL_C() 189 | b3 = the_bots.RANDOM(p_cooperate=0.5) 190 | b4 = the_bots.PAVLOV() 191 | b5 = the_bots.TIT_FOR_TAT() 192 | b6 = the_bots.TIT_FOR_TWO_TATS() 193 | b7 = the_bots.TWO_TITS_FOR_TAT() 194 | b8 = the_bots.SUSPICIOUS_TIT_FOR_TAT() 195 | b9 = the_bots.GENEROUS_TIT_FOR_TAT(p_generous=0.1) 196 | b10 = the_bots.GENEROUS_TIT_FOR_TAT(p_generous=0.3) 197 | b11 = the_bots.JOSS(p_sneaky=0.1) 198 | b12 = the_bots.JOSS(p_sneaky=0.3) 199 | b13 = the_bots.MAJORITY(soft=True) 200 | b14 = the_bots.MAJORITY(soft=False) 201 | b15 = the_bots.TESTER() 202 | b16 = the_bots.FRIEDMAN() 203 | b17 = the_bots.EATHERLY() 204 | b18 = the_bots.CHAMPION() 205 | b19 = the_bots.RANDOM(p_cooperate=0.8) 206 | b20 = the_bots.RANDOM(p_cooperate=0.2) 207 | bot_list = [b1, b2, b3, b4, b5, b6, b7, b8, b9, b10, b11, b12, b13,\ 208 | b14, b15, b16, b17, b18, b19, b20] 209 | 210 | t = a.runTournament(bot_list, num_meetings) 211 | print(t) 212 | 213 | mc = mc.MoralityCalculator(t) 214 | print(mc) 215 | 216 | #----------# 217 | -------------------------------------------------------------------------------- /tournament_results.py: -------------------------------------------------------------------------------- 1 | ######## 2 | ## 3 | ## Wrapper to hold results of a tournament 4 | ## 5 | ######## 6 | 7 | 8 | class TournamentResults(object): 9 | """ 10 | Calculates and wraps results of tournaments 11 | """ 12 | def __init__(self, botList, interactions, payoffs): 13 | """ 14 | Calculate the scores of the interactions and the total scores for the 15 | bots using the specified payoffs. 16 | 17 | ARGS: 18 | - botList: a list of BotPlayer objects where the index of botX in the 19 | list is the tournament id used in the argument interactions. basically, 20 | botList maps tournament id to bot 21 | - interactions: a dictionary with 22 | keys => (tournament_id1, tournament_id2) 23 | values => [meeting1, meeting2, ...] 24 | where meetingX is a list of tuples (bot1_move, bot2_move). 25 | For example: 26 | interactions = { 27 | (0, 1): [ 28 | [('C', 'D'), ('D', 'D')], 29 | [('C', 'C'), ('C', 'D'), ('D', 'D')] 30 | ], 31 | ... 32 | } 33 | - payoffs: defines the scores for each Prisoner's Dilemma situation, 34 | which TournamentResults needs to correctly score each interaction 35 | """ 36 | self.botList = botList 37 | self.interactions = interactions 38 | self.payoffs = payoffs 39 | 40 | self.numBots = len(self.botList) 41 | 42 | # to allow lookup for bot name and description by tournament id 43 | self.bot_info_by_id = {} 44 | for bot in botList: 45 | self.bot_info_by_id[bot.tournament_id] =\ 46 | {'name': bot.name, 'description': bot.description, 'total': 0} 47 | 48 | self.interaction_lengths = [] 49 | some_pair = self.interactions.keys()[0] 50 | for interaction in self.interactions[some_pair]: 51 | self.interaction_lengths.append(len(interaction)) 52 | 53 | self.total_interactions = float( 54 | self.numBots*sum(self.interaction_lengths) 55 | ) 56 | 57 | # to be filled with scores for each bot in each interaction 58 | self.interaction_scores = {} 59 | 60 | # calculate and store interaction and total scores 61 | self.calculate_scores() 62 | 63 | def __str__(self): 64 | # sort the bots for printing output 65 | def get_score(bot): 66 | return self.bot_info_by_id[bot.tournament_id]['total'] 67 | sorted_bots = sorted(self.botList, key=get_score, reverse=True) 68 | 69 | headers = [ 70 | "Tournament ID", 71 | "Bot Name", 72 | "Total Score", 73 | "Avg Score Per Turn" 74 | ] 75 | num_cols = len(headers) 76 | 77 | # find a good column width to use for formatting the output 78 | long_header = max([len(h) for h in headers]) 79 | long_name = max([len(bot.name) for bot in self.botList])+1 80 | col = max([long_header, long_name]) 81 | col_str = str(col) 82 | format_str = (("{: <"+col_str+"} ")*num_cols)[:-1] 83 | hr = "-"*(num_cols*col) 84 | 85 | # construct output string 86 | output = "\n***\n" 87 | output += "Interaction Lengths: "+str(self.interaction_lengths) 88 | output += "\n***\n" 89 | headers_str = format_str.format(*headers) 90 | output += "\n"+hr+"\n"+headers_str+"\n"+hr+"\n" 91 | for bot in sorted_bots: 92 | t_id = bot.tournament_id 93 | name = self.get_name_by_id(t_id) 94 | score = self.get_score_by_id(t_id) 95 | avg = self.get_avg_score_by_id(t_id) 96 | row = format_str.format(str(t_id), name, str(score), avg) 97 | output += row+"\n" 98 | return output 99 | 100 | 101 | ## TODO: make pretty printing for interactions 102 | 103 | 104 | ##### 105 | # Initialization calculation methods 106 | ##### 107 | 108 | def score_turn(self, actions): 109 | """ 110 | Get the scores for each bot for a single turn 111 | 112 | ARGS: 113 | - actions: tuple (bot1_move, bot2_move) 114 | 115 | RETURNS: 116 | - scores: the score for each bot (bot1_score, bot2_score) 117 | """ 118 | scores = -1 119 | 120 | if actions[0] == 'C': 121 | if actions[1] == 'C': 122 | scores = (self.payoffs['R'], self.payoffs['R']) 123 | elif actions[1] == 'D': 124 | scores = (self.payoffs['S'], self.payoffs['T']) 125 | else: 126 | print("actions[1] is not a valid move, must be 'C' or 'D'") 127 | return -1 128 | elif actions[0] == 'D': 129 | if actions[1] == 'C': 130 | scores = (self.payoffs['T'], self.payoffs['S']) 131 | elif actions[1] == 'D': 132 | scores = (self.payoffs['P'], self.payoffs['P']) 133 | else: 134 | print("actions[1] is not a valid move, must be 'C' or 'D'") 135 | return -1 136 | else: 137 | print("actions[0] is not a valid move, must be 'C' or 'D'") 138 | return -1 139 | 140 | return scores 141 | 142 | def calculate_scores(self): 143 | """ 144 | Get the scores for each bot pair meetings list and store in 145 | self.interaction_scores. Tally up the total score for each bot and store 146 | in self.bot_info_by_id['total'] 147 | """ 148 | for bot_pair in self.interactions: 149 | self.interaction_scores[bot_pair] = [] 150 | for meeting in self.interactions[bot_pair]: 151 | meeting_scores = [0, 0] 152 | for turn in meeting: 153 | turn_scores = self.score_turn(turn) 154 | # accumulate scores for meeting 155 | meeting_scores[0] += turn_scores[0] 156 | meeting_scores[1] += turn_scores[1] 157 | meeting_scores = tuple(meeting_scores) 158 | # add scores for meeting to list of meeting scores for this pair 159 | self.interaction_scores[bot_pair].append(meeting_scores) 160 | # also add to total for each bot, but only once if this is a bot 161 | # paired with its clone 162 | if bot_pair[0] == bot_pair[1]: 163 | self.bot_info_by_id[bot_pair[0]]['total']\ 164 | += meeting_scores[0] 165 | else: 166 | for idx, bot_id in enumerate(bot_pair): 167 | self.bot_info_by_id[bot_id]['total']\ 168 | += meeting_scores[idx] 169 | 170 | 171 | ##### 172 | # Getter methods 173 | ##### 174 | 175 | def get_name_by_id(self, t_id): 176 | return self.bot_info_by_id[t_id]['name'] 177 | 178 | def get_description_by_id(self, t_id): 179 | return self.bot_info_by_id[t_id]['description'] 180 | 181 | def get_score_by_id(self, t_id): 182 | return self.bot_info_by_id[t_id]['total'] 183 | 184 | def get_avg_score_by_id(self, t_id): 185 | return self.get_score_by_id(t_id)/self.total_interactions 186 | 187 | def get_winning_id(self): 188 | id_list = [bot.tournament_id for bot in self.botList] 189 | return max(id_list, key=self.get_score_by_id) 190 | 191 | def get_winning_name(self): 192 | return self.get_name_by_id(self.get_winning_id()) 193 | 194 | def get_interaction_score(self, id_1, id_2, meeting): 195 | return self.interaction_scores[(id_1, id_2)][meeting] 196 | 197 | def get_interaction_scores(self, id_1, id_2): 198 | return self.interaction_scores[(id_1, id_2)] 199 | 200 | def get_interaction(self, id_1, id_2, meeting): 201 | return self.interactions[(id_1, id_2)][meeting] 202 | 203 | def get_interactions(self, id_1, id_2): 204 | return self.interactions[(id_1, id_2)] 205 | 206 | def get_bot_list(self): 207 | return self.botList 208 | 209 | def get_sorted_bot_list(self): 210 | def get_score(bot): 211 | return self.get_score_by_id(bot.tournament_id) 212 | return sorted(self.botList, key=get_score, reverse=True) 213 | 214 | 215 | if __name__ == "__main__": 216 | pass 217 | -------------------------------------------------------------------------------- /morality_calculator.py: -------------------------------------------------------------------------------- 1 | ######## 2 | ## 3 | ## Make various morality related calculations on the results of an IPD 4 | ## tournament 5 | ## 6 | ######## 7 | 8 | 9 | import copy 10 | import numpy as np 11 | 12 | 13 | class MoralityCalculator(object): 14 | """ 15 | Wraps up morality functions and calculations 16 | """ 17 | def __init__(self, tourney_res): 18 | """ 19 | Calculate the cooperation matrix of the given tournament results 20 | 21 | ARGS: 22 | - tourney_res: TournamentResults object holding the results of the 23 | tournament to be morally analyzed 24 | """ 25 | self.tourney_res = tourney_res 26 | 27 | self.cooperation_matrix = None 28 | self.bigger_man_scores = None 29 | self.cooperation_rates = None 30 | self.calculate_cooperation_stuff() 31 | 32 | 33 | self.eigenjesus_scores = None 34 | self.eigenmoses_scores = None 35 | self.calculate_network_morality() 36 | 37 | def __str__(self): 38 | # get the bots in order of their score 39 | sorted_bots = self.tourney_res.get_sorted_bot_list() 40 | 41 | headers = [ 42 | "Tournament ID", 43 | "Bot Name", 44 | "Cooperation Rate", 45 | "Not Worse Partner", 46 | "Recursive Jesus", 47 | "Recursive Moses" 48 | ] 49 | num_cols = len(headers) 50 | 51 | # find a good column width to use for formatting the output 52 | long_header = max([len(h) for h in headers]) 53 | long_name = max( 54 | [len(bot.name) for bot in self.tourney_res.get_bot_list()] 55 | )+1 56 | col = max([long_header, long_name]) 57 | col_str = str(col) 58 | format_str = (("{: <"+col_str+"} ")*num_cols)[:-1] 59 | hr = "-"*(num_cols*col) 60 | 61 | # construct output string 62 | headers_str = format_str.format(*headers) 63 | output = "\n"+hr+"\n"+headers_str+"\n"+hr+"\n" 64 | for bot in sorted_bots: 65 | t_id = bot.tournament_id 66 | name = self.tourney_res.get_name_by_id(t_id) 67 | coop_rate = self.cooperation_rates[t_id] 68 | big_man_score = self.bigger_man_scores[t_id] 69 | eigenjesus = self.eigenjesus_scores[t_id] 70 | eigenmoses = self.eigenmoses_scores[t_id] 71 | row = format_str.format(str(t_id), name,\ 72 | str(coop_rate), str(big_man_score), 73 | str(eigenjesus), eigenmoses) 74 | output += row+"\n" 75 | return output 76 | 77 | 78 | ##### 79 | # Initialization calculation methods 80 | ##### 81 | 82 | def calculate_cooperation_stuff(self): 83 | """ 84 | Calculate the cooperation rate for each bot in each bot pair and store 85 | these in an instance variable 86 | 87 | STORES: 88 | - cooperation_matrix: numpy array, cooperation_matrix[i][j] is i's 89 | cooperation rate when partnered with j 90 | - bigger_man_scores: a bot's bigger_man_score is the fraction of 91 | partnerships in which that bot cooperated at least as much as its 92 | partner 93 | - cooperation_rates: the fraction of each bot's total moves that are 94 | cooperations 95 | """ 96 | tr = self.tourney_res 97 | bot_list = tr.get_bot_list() 98 | bot_id_list = [bot.tournament_id for bot in bot_list] 99 | num_bots = len(bot_list) 100 | coop_matrix = [[0 for _ in xrange(num_bots)] for _ in xrange(num_bots)] 101 | big_man_scores = {} 102 | for bot_id in bot_id_list: 103 | big_man_scores[bot_id] = 0.0 104 | coop_rates = {} 105 | # for each bot pair, count the times each bot cooperates and divide by 106 | # the total number of turns, and store this rate in coop_matrix 107 | for i in xrange(num_bots): 108 | bot1_id = bot_list[i].tournament_id 109 | for j in xrange(i, num_bots): 110 | bot2_id = bot_list[j].tournament_id 111 | interactions = tr.get_interactions(bot1_id, bot2_id) 112 | total_turns = sum([len(meeting) for meeting in interactions]) 113 | bot1_coops, bot2_coops = 0.0, 0.0 114 | for meeting in interactions: 115 | for turn in meeting: 116 | if turn[0] == 'C': 117 | bot1_coops += 1.0 118 | if turn[1] == 'C': 119 | bot2_coops += 1.0 120 | bot1_rate = bot1_coops/total_turns 121 | bot2_rate = bot2_coops/total_turns 122 | coop_matrix[bot1_id][bot2_id] = bot1_rate 123 | coop_matrix[bot2_id][bot1_id] = bot2_rate 124 | # don't include the case where a bot partners with its own clone 125 | if bot1_id != bot2_id: 126 | if bot1_rate >= bot2_rate: 127 | big_man_scores[bot1_id] += 1.0 128 | if bot2_rate >= bot1_rate: 129 | big_man_scores[bot2_id] += 1.0 130 | for i in xrange(num_bots): 131 | bot_id = bot_list[i].tournament_id 132 | bot_coop_rates = coop_matrix[bot_id] 133 | coop_rates[bot_id] = sum(bot_coop_rates)/len(bot_coop_rates) 134 | big_man_scores[bot_id] = big_man_scores[bot_id]/(num_bots-1) 135 | # save these cooperation rates per interaction and the overall 136 | # cooperation rate for each bot 137 | self.cooperation_matrix = np.array(coop_matrix) 138 | self.bigger_man_scores = big_man_scores 139 | self.cooperation_rates = coop_rates 140 | 141 | def principal_eigenvector(self, C, iters): 142 | """ 143 | Starts with every node at a constant amount of 'worth' and iterates 144 | using C to update every node's 'worth' until converging on the principal 145 | eigenvector 146 | 147 | ARGS: 148 | - C: C is a numpy array in [0, 1]^(nxn) where values represent the 149 | 'votes' between nodes like in PageRank 150 | 151 | RETURNS: 152 | - pev: pev is the principal eigenvector of C, representing the end 153 | values of each node. normalize to add to n 154 | """ 155 | num_vals = len(C) 156 | current_vals = np.array([1 for _ in xrange(num_vals)]) 157 | i = 0 158 | while i < iters: 159 | current_vals = C.dot(current_vals) 160 | i += 1 161 | total_val = float(sum(current_vals)) 162 | pev = copy.copy(current_vals) 163 | for idx, v in enumerate(current_vals): 164 | try: 165 | pev[idx] = (num_vals/total_val)*v 166 | except ZeroDivisionError: 167 | pev[idx] = 1 168 | return pev 169 | 170 | def calculate_network_morality(self): 171 | """ 172 | Calculate and store the morality metrics of network jesus and network 173 | moses for each bot 174 | 175 | ARGS: 176 | - coop_matrix: numpy array, coop_matrix[i][j] is i's cooperation rate 177 | when partnered with j 178 | 179 | STORES: 180 | - eigenjesus_scores: list of recursively defined morality scores 181 | (cooperating with cooperaters is worth more), cooperating always helps 182 | - eigenmoses_scores: list of recursively defined morality scores 183 | (cooperating with cooperaters is worth more), cooperating with a 184 | defector actually counts against you 185 | """ 186 | ## TODO: come up with programmtic way of determining number of iters 187 | self.eigenjesus_scores =\ 188 | self.principal_eigenvector(self.cooperation_matrix, 100) 189 | coop_def_matrix = (self.cooperation_matrix-0.5)*2 190 | self.eigenmoses_scores =\ 191 | self.principal_eigenvector(coop_def_matrix, 100) 192 | 193 | 194 | ## TODO: design and implement more morality metrics 195 | 196 | 197 | ##### 198 | # Getter methods 199 | ##### 200 | 201 | def get_coop_rate_by_id(self, bot_id): 202 | return self.cooperation_rates[bot_id] 203 | 204 | def get_good_partner_by_id(self, bot_id): 205 | return self.bigger_man_scores[bot_id] 206 | 207 | def get_eigenjesus_by_id(self, bot_id): 208 | return self.eigenjesus_scores[bot_id] 209 | 210 | def get_eigenmoses_by_id(self, bot_id): 211 | return self.eigenmoses_scores[bot_id] 212 | 213 | def get_bots_sorted_by_coop_rate(self): 214 | bot_list = self.tourney_res.botList 215 | def get_coop_rate(bot): 216 | return self.get_coop_rate_by_id(bot.tournament_id) 217 | return sorted(bot_list, key=get_coop_rate, reverse=True) 218 | 219 | def get_bots_sorted_by_good_partner(self): 220 | bot_list = self.tourney_res.botList 221 | def get_good_partner(bot): 222 | return self.get_good_partner_by_id(bot.tournament_id) 223 | return sorted(bot_list, key=get_good_partner, reverse=True) 224 | 225 | def get_bots_sorted_by_eigenjesus(self): 226 | bot_list = self.tourney_res.botList 227 | def get_eigenjesus(bot): 228 | return self.get_eigenjesus_by_id(bot.tournament_id) 229 | return sorted(bot_list, key=get_eigenjesus, reverse=True) 230 | 231 | def get_bots_sorted_by_eigenmoses(self): 232 | bot_list = self.tourney_res.botList 233 | def get_eigenmoses(bot): 234 | return self.get_eigenmoses_by_id(bot.tournament_id) 235 | return sorted(bot_list, key=get_eigenmoses, reverse=True) 236 | 237 | 238 | if __name__ == "__main__": 239 | pass 240 | -------------------------------------------------------------------------------- /the_bots.py: -------------------------------------------------------------------------------- 1 | ######## 2 | ## 3 | ## The many bots with different strategies 4 | ## 5 | ######## 6 | 7 | 8 | import random 9 | import sys 10 | 11 | from bot_player import BotPlayer 12 | 13 | 14 | class ALL_D(BotPlayer): 15 | def __init__(self): 16 | d = "ALL_D defects unconditionally." 17 | BotPlayer.__init__(self, "ALL_D", description=d) 18 | 19 | def getNextMove(self, pastMoves,\ 20 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 21 | """ 22 | Always defect 23 | """ 24 | return 'D' 25 | 26 | class ALL_C(BotPlayer): 27 | def __init__(self): 28 | d = "ALL_C cooperates unconditionally." 29 | BotPlayer.__init__(self, "ALL_C", description=d) 30 | 31 | def getNextMove(self, pastMoves,\ 32 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 33 | """ 34 | Always cooperate 35 | """ 36 | return 'C' 37 | 38 | class RANDOM(BotPlayer): 39 | def __init__(self, p_cooperate=0.5): 40 | d = "RANDOM chooses randomly between cooperation and defection with "+\ 41 | "some specified probability for each, independent of its partner's "+\ 42 | "moves." 43 | BotPlayer.__init__(self, "RANDOM_"+str(p_cooperate), description=d) 44 | self.p_cooperate = p_cooperate 45 | 46 | def getNextMove(self, pastMoves,\ 47 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 48 | """ 49 | Randomly choose an action, independent of past history 50 | p_cooperate is the probability of cooperating 51 | """ 52 | r = random.random() 53 | if r < self.p_cooperate: 54 | return 'C' 55 | else: 56 | return 'D' 57 | 58 | class PAVLOV(BotPlayer): 59 | def __init__(self): 60 | d = "PAVLOV defaults to cooperation on the first turn, and "+\ 61 | "thereafter cooperates if and only if both players made the same "+\ 62 | "choice last turn. This is known as 'win-stay, lose-shift', because "+\ 63 | "PAVLOV repeats its own last move after it receives T or R (the good "+\ 64 | "scores) and changes its move after it receives P or S (the bad "+\ 65 | "scores), like a reflex demonstrated in Pavlov's dog experiment." 66 | BotPlayer.__init__(self, "PAVLOV", d) 67 | 68 | def getNextMove(self, pastMoves,\ 69 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 70 | """ 71 | Defect exactly after the players' moves last turn do not match, and 72 | cooperate after they do match. 73 | """ 74 | if not pastMoves: 75 | # this is the first turn, default to cooperation 76 | return 'C' 77 | else: 78 | # otherwise, cooperate if last moves match 79 | if pastMoves[-1][0] == pastMoves[-1][1]: 80 | return 'C' 81 | # last moves do not match, so defect 82 | else: 83 | return 'D' 84 | 85 | class TIT_FOR_TAT(BotPlayer): 86 | def __init__(self): 87 | d = "TIT_FOR_TAT defaults to cooperation on the first turn, and "+\ 88 | "thereafter mirrors its partner's previous move." 89 | BotPlayer.__init__(self, "TIT_FOR_TAT", description=d) 90 | 91 | def getNextMove(self, pastMoves,\ 92 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 93 | """ 94 | Do whatever the other player did last turn 95 | """ 96 | if not pastMoves: 97 | # this is the first turn, default to cooperation 98 | return 'C' 99 | else: 100 | # otherwise, reciprocate the other player's last move 101 | their_last_move = pastMoves[-1][1] 102 | return their_last_move 103 | 104 | class TIT_FOR_TWO_TATS(BotPlayer): 105 | def __init__(self): 106 | d = "TIT_FOR_TWO_TATS defects if and only if its partner has "+\ 107 | "defected for the past two turns." 108 | BotPlayer.__init__(self, "TIT_FOR_TWO_TATS", description=d) 109 | 110 | def getNextMove(self, pastMoves,\ 111 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 112 | """ 113 | Cooperate unless the other player defected the past two turns 114 | """ 115 | if len(pastMoves)<2: 116 | # this is one of the first few turns, default to cooperation 117 | return 'C' 118 | else: 119 | # if partner defected past two turns, reciprocate 120 | if (pastMoves[-1][1], pastMoves[-2][1]) == ('D', 'D'): 121 | return 'D' 122 | # otherwise, cooperate 123 | else: 124 | return 'C' 125 | 126 | class TWO_TITS_FOR_TAT(BotPlayer): 127 | def __init__(self): 128 | d = "TWO_TITS_FOR_TAT cooperates unless its partner defects in which "+\ 129 | "case TWO_TITS_FOR_TAT retaliates with two defections." 130 | BotPlayer.__init__(self, "TWO_TITS_FOR_TAT", description=d) 131 | 132 | def getNextMove(self, pastMoves,\ 133 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 134 | """ 135 | Defect twice after each defection of the partner 136 | """ 137 | if not pastMoves: 138 | # this is the first turn, default to cooperation 139 | return 'C' 140 | else: 141 | # if the partner has defected in the last two turns, defect 142 | if len(pastMoves) == 1: 143 | their_last_move = pastMoves[-1][1] 144 | return their_last_move 145 | else: 146 | if 'D' in (pastMoves[-1][1], pastMoves[-2][1]): 147 | return 'D' 148 | # otherwise, cooperate 149 | else: 150 | return 'C' 151 | 152 | class SUSPICIOUS_TIT_FOR_TAT(BotPlayer): 153 | def __init__(self): 154 | d = "SUSPICIOUS_TIT_FOR_TAT defaults to defection on the first turn, "+\ 155 | "and thereafter mirrors its partner's previous move." 156 | BotPlayer.__init__(self, "SUSPICIOUS_TIT_FOR_TAT", description=d) 157 | 158 | def getNextMove(self, pastMoves,\ 159 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 160 | """ 161 | Do whatever the other player did last turn 162 | """ 163 | if not pastMoves: 164 | # this is the first turn, default to defection 165 | return 'D' 166 | else: 167 | # otherwise, reciprocate the other player's last move 168 | their_last_move = pastMoves[-1][1] 169 | return their_last_move 170 | 171 | class GENEROUS_TIT_FOR_TAT(BotPlayer): 172 | def __init__(self, p_generous=0.1): 173 | d = "GENEROUS_TIT_FOR_TAT defaults to cooperation on the first turn, "+\ 174 | "and thereafter mirrors its partner's previous move, except after "+\ 175 | "its partner defects GENEROUS_TIT_FOR_TAT cooperates with some "+\ 176 | "probability to forgive occasional mistakes and avoid unnecessary "+\ 177 | "mutual punishment." 178 | BotPlayer.__init__(self, "GENEROUS_TIT_FOR_TAT_"+str(p_generous),\ 179 | description=d) 180 | self.p_generous = p_generous 181 | 182 | def getNextMove(self, pastMoves,\ 183 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 184 | """ 185 | Do whatever the other player did last turn, except probabilistically 186 | cooperate even if the other player defected 187 | """ 188 | if not pastMoves: 189 | # this is the first turn, default to cooperation 190 | return 'C' 191 | else: 192 | # otherwise, reciprocate the other player's last move 193 | their_last_move = pastMoves[-1][1] 194 | action = their_last_move 195 | if action == 'D': 196 | # with the specified probability, generously cooperate 197 | r = random.random() 198 | if r < self.p_generous: 199 | action = 'C' 200 | return action 201 | 202 | class JOSS(BotPlayer): 203 | def __init__(self, p_sneaky=0.1): 204 | d = "JOSS defaults to cooperation on the first turn, and "+\ 205 | "thereafter mirrors its partner's previous move, except after its "+\ 206 | "partner cooperates JOSS defects with some probability to see what "+\ 207 | "it can get away with every once in a while." 208 | BotPlayer.__init__(self, "JOSS_"+str(p_sneaky), description=d) 209 | self.p_sneaky = p_sneaky 210 | 211 | def getNextMove(self, pastMoves,\ 212 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 213 | """ 214 | Do whatever the other player did last turn, except probabilistically 215 | defect even if the other player cooperated 216 | """ 217 | if not pastMoves: 218 | # this is the first turn, default to cooperation 219 | return 'C' 220 | else: 221 | # otherwise, reciprocate the other player's last move 222 | their_last_move = pastMoves[-1][1] 223 | action = their_last_move 224 | if action == 'C': 225 | # with the specified probability, sneakily defect 226 | r = random.random() 227 | if r < self.p_sneaky: 228 | action = 'D' 229 | return action 230 | 231 | class MAJORITY(BotPlayer): 232 | def __init__(self, soft=True): 233 | d = "MAJORITY cooperates as long as its partner has cooperated more "+\ 234 | "than it has defected (if partner has cooperated and defected equal "+\ 235 | "amounts, MAJORITY cooperates if it is soft and defects if it is "+\ 236 | "hard)." 237 | if soft: 238 | name = "MAJORITY_SOFT" 239 | else: 240 | name = "MAJORITY_HARD" 241 | BotPlayer.__init__(self, name, description=d) 242 | self.soft = soft 243 | 244 | def getNextMove(self, pastMoves,\ 245 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 246 | """ 247 | Cooperate if the partner has cooperated more than defected 248 | """ 249 | if not pastMoves: 250 | # this is the first turn, default to cooperation 251 | return 'C' 252 | # calculate their defection rate and act accordingly 253 | else: 254 | total_moves = len(pastMoves) 255 | their_defections =\ 256 | len([1 for turn in pastMoves if turn[1] == 'D']) 257 | defection_ratio = float(their_defections)/float(total_moves) 258 | if defection_ratio < 0.5: 259 | return 'C' 260 | elif defection_ratio > 0.5: 261 | return 'D' 262 | # partner has defected and cooperated an equal number of times 263 | else: 264 | if self.soft: 265 | return 'C' 266 | else: 267 | return 'D' 268 | 269 | 270 | class TESTER(BotPlayer): 271 | def __init__(self): 272 | d = "TESTER initially defects to test what the other player will do. "+\ 273 | "If the other player defects ever, TESTER apologizes by cooperating "+\ 274 | "and then mirrors the partner's moves thereafter. If the other "+\ 275 | "player does not retaliate, TESTER cooperates twice but then defects "+\ 276 | "on and off every other turn." 277 | BotPlayer.__init__(self, "TESTER", description=d) 278 | 279 | def getNextMove(self, pastMoves,\ 280 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 281 | """ 282 | Test the waters with initial defection, apologize if they ever defect, 283 | and if they do not, cooperate twice and then switch off between 284 | defection and cooperation every other turn. 285 | """ 286 | if not pastMoves: 287 | # this is the first turn, default to defection 288 | return 'D' 289 | else: 290 | partner_moves = [move[1] for move in pastMoves] 291 | if 'D' not in partner_moves: 292 | # if they have not defected, alternate actions after 293 | if 0 < len(pastMoves) < 3: 294 | # it is the second or third move and they have not defected 295 | return 'C' 296 | # it is past the second or third move and they have not defected 297 | my_last_move = pastMoves[-1][0] 298 | if my_last_move == 'C': 299 | return 'D' 300 | elif my_last_move == 'D': 301 | return 'C' 302 | elif 'D' in partner_moves: 303 | # if they have defected, check when their first time was to see 304 | # if we need to apologize 305 | if 'D' not in partner_moves[:-1]: 306 | # this is their first defection, so apologize 307 | return 'C' 308 | elif 'D' in partner_moves[:-1]: 309 | # if they defected more than a turn ago, we just mirror 310 | # their most recent action 311 | return partner_moves[-1] 312 | 313 | class FRIEDMAN(BotPlayer): 314 | def __init__(self): 315 | d = "FRIEDMAN is the permanent retaliator. It cooperates until its "+\ 316 | "partner defects, after which FRIEDMAN defects for the rest of the "+\ 317 | "interaction." 318 | BotPlayer.__init__(self, "FRIEDMAN", description=d) 319 | 320 | def getNextMove(self, pastMoves,\ 321 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 322 | """ 323 | Cooperate until they defect, then defect always. 324 | """ 325 | if not pastMoves: 326 | # this is the first turn, default to cooperation 327 | return 'C' 328 | else: 329 | partner_moves = [move[1] for move in pastMoves] 330 | if 'D' in partner_moves: 331 | # defect if they have ever defected 332 | return 'D' 333 | elif 'D' not in partner_moves: 334 | # cooperate as long as they do 335 | return 'C' 336 | 337 | class EATHERLY(BotPlayer): 338 | def __init__(self): 339 | d = "EATHERLY defaults to cooperation, but keeps track of how many "+\ 340 | "times the other player has defected, so after a defection by the "+\ 341 | "other player, EATHERLY can defect with probability equal to the "+\ 342 | "ratio of its partner's defections to the total number of moves so far." 343 | BotPlayer.__init__(self, "EATHERLY", d) 344 | 345 | def getNextMove(self, pastMoves,\ 346 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 347 | if not pastMoves: 348 | # this is the first turn, default to cooperation 349 | return 'C' 350 | else: 351 | if pastMoves[-1][1] == 'C': 352 | # they cooperated last turn, so reciprocate cooperation 353 | return 'C' 354 | elif pastMoves[-1][1] == 'D': 355 | # they defected last turn, so defect with probability equal to 356 | # their defection ratio 357 | total_moves = len(pastMoves) 358 | their_defections =\ 359 | len([1 for turn in pastMoves if turn[1] == 'D']) 360 | defection_ratio = float(their_defections)/float(total_moves) 361 | r = random.random() 362 | if r < defection_ratio: 363 | return 'D' 364 | else: 365 | return 'C' 366 | 367 | class CHAMPION(BotPlayer): 368 | def __init__(self, p_cooperate=0.5): 369 | d = "CHAMPION cooperates for about 1/20 of the expected length of "+\ 370 | "interaction, mirrors its partner's previous move for about 3/40 of "+\ 371 | "the expected length of interaction, and then cooperates unless all "+\ 372 | "three of the following conditions are true: its partner defected "+\ 373 | "last turn, its partner cooperated less than 60% of the time, and "+\ 374 | "a randomly generated number between 0 and 1 is less than its "+\ 375 | "partner's defection rate." 376 | BotPlayer.__init__(self, "CHAMPION", description=d) 377 | 378 | def getNextMove(self, pastMoves,\ 379 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.9): 380 | """ 381 | Cooperate for 1/20 the expected length of interaction, mirror partner's 382 | moves for 3/40 the expected length of interaction, then defect with 383 | probability equal to partner's defection rate and only after they 384 | defect and if their defection rate is above 40%. 385 | """ 386 | num_turns = float(len(pastMoves)) 387 | expected_length = 1.0/(1.0-w) 388 | if num_turns <= expected_length/20.0: 389 | # in unconditional cooperation phase 390 | return 'C' 391 | if expected_length/20.0 < num_turns < (5.0/40.0)*expected_length: 392 | # in pure reciprocation stage 393 | their_last_move = pastMoves[-1][1] 394 | return their_last_move 395 | elif num_turns >= (5.0/40.0)*expected_length: 396 | # in the judged phase (all of their actions come into play) 397 | their_last_move = pastMoves[-1][1] 398 | their_defections =\ 399 | len([1 for turn in pastMoves if turn[1] == 'D']) 400 | their_defection_rate = their_defections/num_turns 401 | r = random.random() 402 | if their_last_move == 'D' and their_defection_rate > max(0.4, r): 403 | # all conditions for the final phase are met 404 | return 'D' 405 | else: 406 | # at least one condition failed 407 | return 'C' 408 | 409 | 410 | ## TODO: design and implement more bots 411 | 412 | 413 | ## TODO: finish unfinished bots 414 | 415 | ### vvv Unfinished bots vvv ### 416 | 417 | class TRANQUILIZER(BotPlayer): 418 | def __init__(self): 419 | ## TODO: read pages 45-46 of Axelrod and describe this 420 | d = None 421 | BotPlayer.__init__(self, "TRANQUILIZER", d) 422 | 423 | def getNextMove(self, pastMoves,\ 424 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 425 | ## TODO: read pages 45-46 of Axelrod and implement this 426 | return 'C' 427 | 428 | class DOWNING(BotPlayer): 429 | def __init__(self, optimistic=True): 430 | d = "DOWNING tries to maximize expected gains by testing the water "+\ 431 | "with its partner. The idea is that if its partner seems responsive "+\ 432 | "to what DOWNING is doing, DOWNING will cooperate, but if the other "+\ 433 | "player seems unresponsive, DOWNING will try to get away with "+\ 434 | "whatever it can by defecting. DOWNING does this by keeping an "+\ 435 | "estimate of the probability of the other player cooperating given "+\ 436 | "DOWNING's last move. The initial estimates depend on DOWNING's "+\ 437 | "outlook (optimistic or pessimistic)." 438 | if optimistic: 439 | name = "OPT_DOWNING" 440 | elif not optimistic: 441 | name = "PESS_DOWNING" 442 | BotPlayer.__init__(self, name, description=d) 443 | self.optimistic = optimistic 444 | 445 | def getNextMove(self, pastMoves,\ 446 | payoffs={'T': 5,'R': 3,'P': 1,'S': 0}, w=0.995): 447 | """ 448 | Estimate the chance the other player cooperates after DOWNING cooperates 449 | or defects, and use these estimates to choose an action. 450 | """ 451 | ## TODO: this is hard and needs more specification, ignore for now 452 | return 'C' 453 | 454 | ### ^^^ Unfinished bots ^^^ ### 455 | 456 | if __name__ == "__main__": 457 | pass --------------------------------------------------------------------------------