m_ipd/lib.rs
1//! This crate provides the necessary framework to simulate the iterated prisoner's dilemma in
2//! cases where the history is not always entirely available.
3//!
4//! The mechanisms to provide one simulation are provided by the `simulate` function, and the full
5//! details of how this works are provided by the associated documentation. If one is interested in
6//! comparing many strategies against each other, the `tournament` function provides the necessary
7//! interface.
8
9pub use Choice::*;
10
11/// Represents one of two possible choices that an actor can make.
12#[derive(Debug, Clone, Copy, Eq, PartialEq)]
13pub enum Choice {
14 Cooperate,
15 Defect,
16}
17
18/// Represents a choice that the other player made in the last round.
19#[derive(Debug, Clone, Copy, Eq, PartialEq)]
20pub enum Round {
21 /// Contains the choice that the other player made
22 Shown(Choice),
23 /// Indicates that the information from the last round has been hidden by the revealing policy
24 Hidden,
25 /// Indicates that this is the first round of the game
26 First,
27}
28
29/// Represents a playing strategy
30///
31/// This function will be invoked one time every time two players play the IPD. The closure that is
32/// returned by the function will be repeatedly invoked with values of the `Round` enum; each
33/// invocation of the closure corresponds to one round of gameplay. The closure is told the
34/// other player's choice in the previous round, and asked to return the next round's choice.
35///
36/// A very simple strategy might be to always `Cooperate`. It could be written like this:
37/// ```rust
38/// use m_ipd::*;
39///
40/// fn always_cooperate() -> Box<dyn FnMut(Round) -> Choice> {
41/// Box::new(|_| Cooperate)
42/// }
43///
44/// let s: Strategy = always_cooperate;
45/// ```
46pub type Strategy = fn() -> Box<dyn FnMut(Round) -> Choice>;
47
48/// Represents a policy that decides how history is revealed.
49///
50/// This type functions much like the `Strategy` type; the function is invoked once for each time
51/// IPD is played. The returned closure is invoked once for each round of the game, with the
52/// choices of the players; it should return true of false indicating whether the decisions from
53/// that round should be revealed to the players.
54pub type RevealPolicy = fn() -> Box<dyn FnMut(Choice, Choice) -> bool>;
55
56// The standard reward function
57fn reward(one: Choice, two: Choice) -> (u32, u32) {
58 match (one, two) {
59 (Cooperate, Cooperate) => (3, 3),
60 (Cooperate, Defect) => (0, 5),
61 (Defect, Cooperate) => (5, 0),
62 (Defect, Defect) => (1, 1),
63 }
64}
65
66// Simulates a match between two players.
67fn simulate(
68 one: Strategy,
69 two: Strategy,
70 policy: RevealPolicy,
71) -> (u32, u32, u32) {
72 let mut s1 = 0;
73 let mut s2 = 0;
74 let mut rounds = 0;
75 for _ in 0..100 {
76 let mut last = None;
77 let mut one = one();
78 let mut two = two();
79 let mut policy = policy();
80
81 while rand::random::<f32>() < 1f32 - 1f32 / 200f32 {
82 let c1;
83 let c2;
84
85 if let Some((prev_one, prev_two, rev)) = last {
86 c1 = one(if rev {
87 Round::Shown(prev_two)
88 } else {
89 Round::Hidden
90 });
91 c2 = two(if rev {
92 Round::Shown(prev_one)
93 } else {
94 Round::Hidden
95 });
96 } else {
97 c1 = one(Round::First);
98 c2 = two(Round::First);
99 }
100
101 let (r1, r2) = reward(c1, c2);
102 s1 += r1;
103 s2 += r2;
104 rounds += 1;
105
106 let show = policy(c1, c2);
107 last = Some((c1, c2, show));
108 }
109 }
110
111 (s1, s2, rounds)
112}
113
114/// Runs a tournament between a set of strategies.
115///
116/// This is a round-robin tournament; each strategy plays IPD against each other strategy 100
117/// times. The output of the function is the average number of points each strategy earned per
118/// round.
119///
120/// The reward function used is as follows:
121/// - 1 point for mutual defection.
122/// - 3 points for mutual cooperation.
123/// - 5 points for successful betreyal.
124pub fn tournament(
125 strategies: &[Strategy],
126 policy: RevealPolicy,
127) -> Vec<f32> {
128 let n = strategies.len();
129 let mut points = vec![(0, 0); n];
130
131 for i in 0..n - 1 {
132 for j in i+1..n {
133 let (p1, p2, rounds) = simulate(strategies[i], strategies[j], policy);
134 points[i] = ((points[i].0 + p1), (points[i].1 + rounds));
135 points[j] = ((points[j].0 + p2), (points[j].1 + rounds));
136 }
137 }
138 points.into_iter().map(|(p, r)| p as f32 / r as f32).collect()
139}
140
141pub mod strategies {
142 use crate::*;
143
144 pub mod axelrod {
145 use crate::*;
146
147 /// Copies the opponents last shown move.
148 pub fn tit_for_tat() -> Box<dyn FnMut(Round) -> Choice> {
149 let mut old = Cooperate;
150
151 Box::new(move |c| match c {
152 Round::Shown(n) => {
153 old = n;
154 n
155 }
156 _ => old,
157 })
158 }
159
160 /// Copies the opponents last move if it was shown, cooperating otherwise.
161 pub fn optimistic_tit_for_tat() -> Box<dyn FnMut(Round) -> Choice> {
162 Box::new(move |c| match c {
163 Round::Shown(o) => o,
164 _ => Cooperate
165 })
166 }
167
168 /// Punishes the other player by one turn more each time the other player
169 /// engages in a streak of defections.
170 pub fn punishing_tit_for_tat() -> Box<dyn FnMut(Round) -> Choice> {
171 let mut reset = 0u32;
172 let mut retaliate = 0;
173 let mut last = Cooperate;
174
175 Box::new(move |c| match c {
176 Round::Shown(Cooperate) => {
177 if last == Defect {
178 reset = retaliate;
179 retaliate += 1;
180 }
181 last = Cooperate;
182
183 if reset > 0 {
184 reset -= 1;
185 Defect
186 } else {
187 Cooperate
188 }
189 }
190 Round::Shown(Defect) => Defect,
191 Round::Hidden => {
192 if reset > 0 {
193 reset -= 1;
194 Defect
195 } else {
196 Cooperate
197 }
198 }
199 Round::First => Cooperate
200 })
201 }
202
203 /// Cooperates unless one betrayer betrayed the other in the last shown round, in which
204 /// case it only cooperates with low probability.
205 ///
206 /// Adapted from a strategy created by Grofman
207 pub fn betray_defect() -> Box<dyn FnMut(Round) -> Choice> {
208 let mut last = Cooperate;
209 let mut betray = false;
210 Box::new(move |c| match c {
211 Round::Shown(o) => {
212 let n = if last == o {
213 betray = false;
214 Cooperate
215 } else {
216 betray = true;
217 if rand::random::<f32>() < 2f32/7f32 { Cooperate } else { Defect }
218 };
219 last = n;
220 n
221 }
222 Round::Hidden => {
223 let n = if betray {
224 if rand::random::<f32>() < 2f32/7f32 { Cooperate } else { Defect }
225 } else {
226 Cooperate
227 };
228 last = n;
229 n
230 }
231 Round::First => Cooperate
232 })
233 }
234
235 /// Defects in streaks increasing by length 1.
236 ///
237 /// Original strategy by Shubik.
238 pub fn defect_streaks() -> Box<dyn FnMut(Round) -> Choice> {
239 let mut next = 0;
240 let mut current = 0;
241 Box::new(move |c| match c {
242 Round::Shown(o) => {
243 if current > 0 {
244 current -= 1;
245 Defect
246 } else {
247 if o == Defect {
248 current = next;
249 next += 1;
250 Defect
251 } else {
252 Cooperate
253 }
254 }
255 },
256 Round::Hidden => {
257 if current > 0 {
258 current -= 1;
259 Defect
260 } else {
261 Cooperate
262 }
263 }
264 Round::First => Cooperate
265 })
266 }
267
268 /// Cooperates until the opponent defects even once.
269 ///
270 /// Adapted from an original strategy by Friedman.
271 pub fn grudge_holder() -> Box<dyn FnMut(Round) -> Choice> {
272 let mut action = Cooperate;
273
274 Box::new(move |c: Round| match c {
275 Round::Shown(Defect) => {
276 action = Defect;
277
278 action
279 }
280 _ => action,
281 })
282 }
283
284 /// Plays tit for tat but cooperates with decreasing probability each turn
285 ///
286 /// Adapted from an original strategy by Feld
287 pub fn decreasing_tit_for_tat() -> Box<dyn FnMut(Round) -> Choice> {
288 let mut last = Cooperate;
289 let mut prob = 1f32;
290 Box::new(move |c| {
291 prob *= 0.99654; // 200th root of 0.5
292 if let Round::Shown(c) = c {
293 last = c;
294 }
295
296 if last == Cooperate {
297 if rand::random::<f32>() < prob { Cooperate } else { Defect }
298 } else {
299 Defect
300 }
301 })
302 }
303
304 /// Copies the other's cooperation with probability 90%, otherwise defects.
305 ///
306 /// Adapted from a strategy by Joss.
307 pub fn random_tit_for_tat() -> Box<dyn FnMut(Round) -> Choice> {
308 let mut old = Cooperate;
309
310 Box::new(move |c| {
311 if let Round::Shown(o) = c {
312 old = o;
313 }
314
315 if old == Cooperate {
316 if rand::random::<f32>() < 0.9 { Cooperate } else { Defect }
317 } else {
318 Defect
319 }
320 })
321 }
322
323 /// Plays randomly with probability `p`.
324 ///
325 /// This must be used like `|| random(0.5)` instead of `random` since the `Strategy` type
326 /// does not allow for taking a parameter.
327 pub fn random(p: f32) -> Box<dyn FnMut(Round) -> Choice> {
328 Box::new(move |_| {
329 if rand::random::<f32>() < p {
330 Cooperate
331 } else {
332 Defect
333 }
334 })
335 }
336 }
337
338 /// Always cooperates
339 pub fn always_cooperate() -> Box<dyn FnMut(Round) -> Choice> {
340 Box::new(|_| Cooperate)
341 }
342
343 /// Always defects
344 pub fn always_defect() -> Box<dyn FnMut(Round) -> Choice> {
345 Box::new(|_| Defect)
346 }
347
348 /// Plays tit for tat, but does not defect until the other player has defected twice
349 /// in a row.
350 pub fn tit_for_two_tats() -> Box<dyn FnMut(Round) -> Choice> {
351 let mut prev = Cooperate;
352 let mut next = Cooperate;
353
354 Box::new(move |c| {
355 if let Round::Shown(c) = c {
356 match c {
357 Cooperate => {
358 prev = Cooperate;
359 next = Cooperate;
360 }
361 Defect => {
362 next = prev;
363 prev = Defect;
364 }
365 }
366 }
367
368 next
369 })
370 }
371
372 /// Plays tit for tat, but if the last two rounds were both hidden, defects.
373 pub fn opportunistic() -> Box<dyn FnMut(Round) -> Choice> {
374 let mut last = Cooperate;
375 let mut hidden = 0;
376
377 Box::new(move |c| {
378 match c {
379 Round::Shown(o) => {
380 hidden = 0;
381 last = o;
382 last
383 },
384 Round::Hidden => {
385 hidden += 1;
386 if hidden > 2 { Defect } else { last }
387 }
388 Round::First => Cooperate
389 }
390 })
391 }
392
393 /// Plays the inverse of the other player's move.
394 pub fn inverse() -> Box<dyn FnMut(Round) -> Choice> {
395 let mut next = Cooperate;
396
397 Box::new(move |c| match c {
398 Round::Shown(n) => {
399 next = match n { Cooperate => Defect, Defect => Cooperate };
400 n
401 }
402 _ => next,
403 })
404 }
405
406 /// Plays the other player's majority move.
407 pub fn majority() -> Box<dyn FnMut(Round) -> Choice> {
408 let mut coop = 0;
409 let mut def = 0;
410 Box::new(move |c| {
411 if let Round::Shown(n) = c {
412 if n == Cooperate {
413 coop += 1;
414 } else {
415 def += 1;
416 }
417 }
418
419 if coop > def { Cooperate } else { Defect }
420 })
421 }
422
423 /// Plays the other player's minority move.
424 pub fn minority() -> Box<dyn FnMut(Round) -> Choice> {
425 let mut coop = 0;
426 let mut def = 0;
427 Box::new(move |c| {
428 if let Round::Shown(n) = c {
429 if n == Cooperate {
430 coop += 1;
431 } else {
432 def += 1;
433 }
434 }
435
436 if coop < def { Cooperate } else { Defect }
437 })
438 }
439}
440
441pub mod policies {
442 use crate::*;
443
444 pub fn always_show() -> Box<dyn FnMut(Choice, Choice) -> bool> {
445 Box::new(|_, _| true)
446 }
447
448 pub fn never_show() -> Box<dyn FnMut(Choice, Choice) -> bool> {
449 Box::new(|_, _| false)
450 }
451
452 pub fn random(reveal_chance: f32) -> Box<dyn FnMut(Choice, Choice) -> bool> {
453 Box::new(move |_, _| rand::random::<f32>() < reveal_chance)
454 }
455
456 pub fn show_cooperate(init: f32) -> Box<dyn FnMut(Choice, Choice) -> bool> {
457 let mut coop = init;
458 let mut total = 2.0 * init;
459 Box::new(move |c1, c2| {
460 if c1 == Cooperate && c2 == Cooperate {
461 coop += 1.0;
462 }
463 total += 1.0;
464
465 rand::random::<f32>() < coop / total
466 })
467 }
468}