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}