@@ -988,18 +988,16 @@ class Harrington(Player):
988
988
In Normal and Fair-weather modes, the strategy begins by:
989
989
990
990
- Update history
991
- - Detects random if turn is multiple of 15 and >=30.
991
+ - Try to detect random opponent if turn is multiple of 15 and >=30.
992
992
- Check if `burned` flag should be raised.
993
993
- Check for Fair-weather opponent if turn is 38.
994
994
995
995
Updating history means to increment the correct cell of the `move_history`.
996
996
`move_history` is a matrix where the columns are the opponent's previous
997
- move and rows are indexed by the combo of this player and the opponent's
998
- moves two turns ago*. [The upper-left cell must be all cooperations, but
999
- otherwise order doesn't matter.] * If the player is exiting Defect mode,
1000
- then the history to determine the row is taken from before the turn that
1001
- the player entered Defect mode. (That is, the turn that started in Normal
1002
- mode, but ended in Defect mode.)
997
+ move and the rows are indexed by the combo of this player's and the
998
+ opponent's moves two turns ago. [The upper-left cell must be all
999
+ Cooperations, but otherwise order doesn't matter.] After we enter Defect
1000
+ mode, `move_history` won't be used again.
1003
1001
1004
1002
If the turn is a multiple of 15 and >=30, then attempt to detect random.
1005
1003
If random is detected, enter Defect mode and defect immediately. If the
@@ -1046,10 +1044,10 @@ class Harrington(Player):
1046
1044
parity streak that we're pointing to. If the parity streak that we're
1047
1045
pointing to is then greater than `parity_limit` then reset the streak and
1048
1046
cooperate immediately. `parity_limit` is initially set to five, but after
1049
- its been hit eight times, it decreases to three. The parity streak that
1050
- we're pointing to also gets incremented if in normal mode and WE defect but
1051
- not on turn 38, unless the result of a defect streak. Note that the parity
1052
- streaks reset but the defect streak doesn't.
1047
+ it has been hit eight times, it decreases to three. The parity streak that
1048
+ we're pointing to also gets incremented if in normal mode and we defect but
1049
+ not on turn 38, unless we are defecting as the result of a defect streak.
1050
+ Note that the parity streaks resets but the defect streak doesn't.
1053
1051
1054
1052
If `more_coop` >= 1, then we cooperate and lower that flag here, in Normal
1055
1053
mode after checking streaks. Still lower this flag if cooperating as the
@@ -1086,33 +1084,46 @@ class Harrington(Player):
1086
1084
def __init__ (self ):
1087
1085
super ().__init__ ()
1088
1086
self .mode = "Normal"
1089
- self .recorded_defects = 0
1090
- self .exit_defect_meter = 0
1091
- self .coops_in_first_36 = None
1092
- self .was_defective = False
1087
+ self .recorded_defects = 0 # Count opponent defects after turn 1
1088
+ self .exit_defect_meter = 0 # When >= 11, then exit defect mode.
1089
+ self .coops_in_first_36 = None # On turn 37, count cooperations in first 36
1090
+ self .was_defective = False # Previously in Defect mode
1093
1091
1094
- self .prob = 0.25
1092
+ self .prob = 0.25 # After turn 37, probability that we'll defect
1095
1093
1096
1094
self .move_history = np .zeros ([4 , 2 ])
1095
+ # Will cache value only for testing purposes, not used otherwise
1097
1096
self .chi_squared = None
1098
1097
self .history_row = 0
1099
1098
1100
- self .more_coop = 0
1101
- self .generous_n_turns_ago = 3
1099
+ self .more_coop = 0 # This schedules cooperation for future turns
1100
+ # Initial last_generous_n_turns_ago to 3 because this counts up and
1101
+ # triggers a strategy change at 2.
1102
+ self .last_generous_n_turns_ago = 3 # How many tuns ago was a "generous" move
1102
1103
self .burned = False
1103
1104
1104
1105
self .defect_streak = 0
1105
- self .parity_streak = [0 , 0 ]
1106
- self .parity_bit = 0
1107
- self .parity_limit = 5
1108
- self .parity_hits = 0
1106
+ self .parity_streak = [0 , 0 ] # Counters that get (almost) alternatively incremented.
1107
+ self .parity_bit = 0 # Which parity_streak to increment
1108
+ self .parity_limit = 5 # When a parity streak hits this limit, alter strategy.
1109
+ self .parity_hits = 0 # Counts how many times a parity_limit was hit.
1110
+ # After hitting parity_hits 8 times, lower parity_limit to 3.
1109
1111
1110
1112
def try_return (self , to_return , lower_flags = True , inc_parity = False ):
1113
+ """
1114
+ This will return to_return, with some end-of-turn logic.
1115
+ """
1116
+
1111
1117
if lower_flags and to_return == C :
1118
+ # In most cases when Cooperating, we want to reduce the number that
1119
+ # are scheduled.
1112
1120
self .more_coop -= 1
1113
- self .generous_n_turns_ago += 1
1121
+ self .last_generous_n_turns_ago += 1
1114
1122
1115
1123
if inc_parity and to_return == D :
1124
+ # In some cases we increment the `parity_streak` that we're on when
1125
+ # we return a Defection. In detect_parity_streak, `parity_streak`
1126
+ # counts opponent's Defections.
1116
1127
self .parity_streak [self .parity_bit ] += 1
1117
1128
1118
1129
return to_return
@@ -1131,9 +1142,6 @@ def detect_random(self, turn):
1131
1142
We say this is modified because it differs from a usual Chi-Squared
1132
1143
test in that:
1133
1144
1134
- - It divides by turns minus 2 to get expected, whereas usually we'd
1135
- divide by matrix total. Total equals turns minus 1, unless Defect
1136
- mode has been entered at any point.
1137
1145
- Terms where expected counts are less than 1 get excluded.
1138
1146
- There's a check at the beginning on the first cell of the matrix.
1139
1147
- There's a check at the beginning for the recorded number of defects.
@@ -1146,25 +1154,29 @@ def detect_random(self, turn):
1146
1154
if self .recorded_defects / denom < 0.25 or self .recorded_defects / denom > 0.75 :
1147
1155
return False
1148
1156
1149
- expected_matrix = np .outer (self .move_history .sum (axis = 1 ), \
1150
- self .move_history .sum (axis = 0 ))
1157
+ # In each cell, we expect (for an independent distribution) the total
1158
+ # number of recorded turns times the portion in that row times the
1159
+ # portion in that column
1160
+ expected_matrix = np .outer (self .move_history .sum (axis = 1 ),
1161
+ self .move_history .sum (axis = 0 )) / denom
1151
1162
1152
1163
chi_squared = 0.0
1153
1164
for i in range (4 ):
1154
1165
for j in range (2 ):
1155
- expct = expected_matrix [i , j ] / denom
1156
- if expct > 1.0 :
1157
- chi_squared += (expct - self .move_history [i , j ]) ** 2 / expct
1166
+ expect = expected_matrix [i , j ]
1167
+ if expect > 1.0 :
1168
+ chi_squared += (expect - self .move_history [i , j ]) ** 2 / expect
1158
1169
1159
- self .chi_squared = round (chi_squared , 3 ) # For testing
1170
+ # Caching value only for testing purposes, not used otherwise
1171
+ self .chi_squared = round (chi_squared , 3 )
1160
1172
1161
1173
if chi_squared > 3 :
1162
1174
return False
1163
1175
return True
1164
1176
1165
1177
def detect_streak (self , last_move ):
1166
1178
"""
1167
- Return if and only if the opponent's last twenty moves are defects.
1179
+ Return true if and only if the opponent's last twenty moves are defects.
1168
1180
"""
1169
1181
1170
1182
if last_move == D :
@@ -1176,7 +1188,17 @@ def detect_streak(self, last_move):
1176
1188
return False
1177
1189
1178
1190
def detect_parity_streak (self , last_move ):
1179
- self .parity_bit = 1 - self .parity_bit # Flip bit
1191
+ """
1192
+ Switch which `parity_streak` we're pointing to and incerement if the
1193
+ opponent's last move was a Defection. Otherwise reset the flag. Then
1194
+ return true if and only if the `parity_streak` is at least
1195
+ `parity_limit`.
1196
+
1197
+ This is similar to detect_streak with alternating streaks, except that
1198
+ these streaks get incremented elsewhere as well.
1199
+ """
1200
+
1201
+ self .parity_bit = 1 - self .parity_bit # Flip bit
1180
1202
if last_move == D :
1181
1203
self .parity_streak [self .parity_bit ] += 1
1182
1204
else :
@@ -1191,10 +1213,12 @@ def strategy(self, opponent: Player) -> Action:
1191
1213
return C
1192
1214
1193
1215
if self .mode == "Defect" :
1216
+ # There's a chance to exit Defect mode.
1194
1217
if opponent .history [- 1 ] == D :
1195
1218
self .exit_defect_meter += 1
1196
1219
else :
1197
1220
self .exit_defect_meter -= 3
1221
+ # If opponent has been mostly defecting.
1198
1222
if self .exit_defect_meter >= 11 :
1199
1223
self .mode = "Normal"
1200
1224
self .was_defective = True
@@ -1226,19 +1250,21 @@ def strategy(self, opponent: Player) -> Action:
1226
1250
if self .history [- 1 ] == D :
1227
1251
self .history_row += 2
1228
1252
1229
- # If generous 2 turn ago and opponent defected last turn
1230
- if self .generous_n_turns_ago == 2 and opponent .history [- 1 ] == D :
1253
+ # If generous 2 turns ago and opponent defected last turn
1254
+ if self .last_generous_n_turns_ago == 2 and opponent .history [- 1 ] == D :
1231
1255
self .burned = True
1232
1256
1257
+ # Only enter Fair-weather mode if the opponent Cooperated the first 37
1258
+ # turns then Defected on the 38th.
1233
1259
if turn == 38 and opponent .history [- 1 ] == D and opponent .cooperations == 36 :
1234
1260
self .mode = "Fair-weather"
1235
1261
return self .try_return (to_return = C , lower_flags = False )
1236
1262
1237
1263
1238
1264
if self .mode == "Fair-weather" :
1239
1265
if opponent .history [- 1 ] == D :
1240
- self .mode = "Normal" # Post-Defect is not possible
1241
- #Continue below
1266
+ self .mode = "Normal" # Post-Defect is not possible
1267
+ # Proceed with Normal mode this turn.
1242
1268
else :
1243
1269
# Never defect against a fair-weather opponent
1244
1270
return self .try_return (C )
@@ -1249,23 +1275,28 @@ def strategy(self, opponent: Player) -> Action:
1249
1275
if self .detect_streak (opponent .history [- 1 ]):
1250
1276
return self .try_return (D , inc_parity = True )
1251
1277
if self .detect_parity_streak (opponent .history [- 1 ]):
1252
- self .parity_streak [self .parity_bit ] = 0
1253
- self .parity_hits += 1
1254
- if self .parity_hits >= 8 :
1278
+ self .parity_streak [self .parity_bit ] = 0 # Reset `parity_streak` when we hit the limit.
1279
+ self .parity_hits += 1 # Keep track of how many times we hit the limit.
1280
+ if self .parity_hits >= 8 : # After 8 times, lower the limit.
1255
1281
self .parity_limit = 3
1256
- return self .try_return (C , inc_parity = True ) # Inc parity won't get used here.
1282
+ return self .try_return (C , inc_parity = True ) # Inc parity won't get used here.
1257
1283
1284
+ # If we have Cooperations scheduled, then Cooperate here.
1258
1285
if self .more_coop >= 1 :
1259
- return self .try_return (C , inc_parity = True )
1286
+ return self .try_return (C , lower_flags = True , inc_parity = True )
1260
1287
1261
1288
if turn < 37 :
1289
+ # Tit-for-Tat
1262
1290
return self .try_return (opponent .history [- 1 ], inc_parity = True )
1263
1291
if turn == 37 :
1264
- self .more_coop , self .generous_n_turns_ago = 2 , 1
1292
+ # Defect once on turn 37 (if no streaks)
1293
+ self .more_coop , self .last_generous_n_turns_ago = 2 , 1
1265
1294
return self .try_return (D , lower_flags = False )
1266
1295
if self .burned or random .random () > self .prob :
1296
+ # Tit-for-Tat with probability 1-`prob`
1267
1297
return self .try_return (opponent .history [- 1 ], inc_parity = True )
1268
1298
else :
1299
+ # Otherwise Defect, Cooperate, Cooperate, and increase `prob`
1269
1300
self .prob += 0.05
1270
- self .more_coop , self .generous_n_turns_ago = 2 , 1
1301
+ self .more_coop , self .last_generous_n_turns_ago = 2 , 1
1271
1302
return self .try_return (D , lower_flags = False )
0 commit comments