use t4t::*;
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Move {
Cooperate,
Defect,
}
impl Move {
pub fn opposite(&self) -> Self {
match self {
Move::Cooperate => Move::Defect,
Move::Defect => Move::Cooperate,
}
}
pub fn to_char(&self) -> char {
match self {
Move::Cooperate => 'C',
Move::Defect => 'D',
}
}
}
pub const C: Move = Move::Cooperate;
pub const D: Move = Move::Defect;
pub struct Dilemma {
game: Normal<Move, i32, 2>,
utils: [i32; 4],
}
impl Dilemma {
pub fn new(utils: [i32; 4]) -> Self {
let game = Normal::symmetric(vec![C, D], Vec::from(utils)).unwrap();
Dilemma { game, utils }
}
pub fn as_normal(&self) -> &Normal<Move, i32, 2> {
&self.game
}
pub fn prisoners_dilemma() -> Self {
Dilemma::new([2, 0, 3, 1])
}
pub fn friend_or_foe(&self) -> Self {
Dilemma::new([1, 0, 2, 0])
}
pub fn stag_hunt() -> Self {
Dilemma::new([3, 0, 2, 1])
}
pub fn assurance_game() -> Self {
Dilemma::new([2, 0, 1, 1])
}
pub fn hawk_dove(half_value: i32, half_cost: i32) -> Self {
Dilemma::new([half_value, 0, half_value * 2, half_value - half_cost])
}
pub fn chicken(crash: i32) -> Self {
Dilemma::new([0, -1, 1, -crash])
}
pub fn snowdrift() -> Self {
Dilemma::new([2, 1, 3, 0])
}
pub fn is_prisoners_dilemma(&self) -> bool {
let [cc, cd, dc, dd] = self.utils;
dc > cc && cc > dd && dd > cd
}
pub fn is_iterated_prisoners_dilemma(&self) -> bool {
let [cc, cd, dc, _dd] = self.utils;
self.is_prisoners_dilemma() && 2 * cc > cd + dc
}
pub fn is_stag_hunt(&self) -> bool {
let [cc, cd, dc, dd] = self.utils;
cc > dc && dc >= dd && dd > cd
}
pub fn is_assurance_game(&self) -> bool {
let [cc, cd, dc, dd] = self.utils;
cc > dc && dc == dd && dd > cd
}
pub fn is_chicken(&self) -> bool {
let [tie, lose, win, crash] = self.utils;
win > tie && tie > lose && lose > crash
}
}
impl Game<2> for Dilemma {
type Move = Move;
type Utility = i32;
type Outcome = SimultaneousOutcome<Move, i32, 2>;
type State = ();
type View = ();
fn first_turn(&self) -> Turn<(), Move, SimultaneousOutcome<Move, i32, 2>, 2> {
self.as_normal().first_turn()
}
fn state_view(&self, _state: &(), _player: PlayerIndex<2>) {}
fn is_valid_move(&self, _state: &(), _player: PlayerIndex<2>, _the_move: Move) -> bool {
true
}
}
pub type DilemmaPlayer = Player<Repeated<Dilemma, 2>, 2>;
pub type DilemmaContext = Context<RepeatedState<Dilemma, 2>, 2>;
pub fn cooperator() -> DilemmaPlayer {
Player::new("Cooperator".to_string(), Strategy::pure(C))
}
pub fn defector() -> DilemmaPlayer {
Player::new("Defector".to_string(), Strategy::pure(D))
}
pub fn periodic(moves: &[Move]) -> DilemmaPlayer {
let name = format!(
"Periodic ({:?})*",
String::from_iter(moves.iter().map(|m| m.to_char()))
);
Player::new(name, Strategy::periodic_pure(moves))
}
pub fn random() -> DilemmaPlayer {
Player::new(
"Random".to_string(),
Strategy::mixed_flat(vec![C, D]).unwrap(),
)
}
pub fn tit_for_tat() -> DilemmaPlayer {
Player::new(
"Tit for Tat".to_string(),
Strategy::new(|context: &DilemmaContext| {
context
.state_view()
.history()
.moves_for_player(context.their_index())
.last()
.unwrap_or(C)
}),
)
}
pub fn suspicious_tit_for_tat() -> DilemmaPlayer {
Player::new(
"Suspicious Tit for Tat".to_string(),
Strategy::new(|context: &DilemmaContext| {
context
.state_view()
.history()
.moves_for_player(context.their_index())
.last()
.unwrap_or(D)
}),
)
}
pub fn tit_for_n_tats(n: usize) -> DilemmaPlayer {
Player::new(
format!("Tit for {:?} Tats", n),
Strategy::new(move |context: &DilemmaContext| {
let last_n: Vec<Move> = context
.state_view()
.history()
.moves_for_player(context.their_index())
.rev()
.take(n)
.collect();
if last_n.len() == n && last_n.iter().all(|m| *m == D) {
D
} else {
C
}
}),
)
}
pub fn n_tits_for_tat(n: usize) -> DilemmaPlayer {
Player::new(
format!("{:?} Tits for Tat", n),
Strategy::new(move |context: &DilemmaContext| {
let last_n: Vec<Move> = context
.state_view()
.history()
.moves_for_player(context.their_index())
.rev()
.take(n)
.collect();
if last_n.contains(&D) {
D
} else {
C
}
}),
)
}
pub fn probabilistic_tit_for_tat(
on_cooperate: Distribution<Move>,
on_defect: Distribution<Move>,
name_suffix: &str,
) -> DilemmaPlayer {
Player::new(
format!("Probabilistic Tit for Tat {:?}", name_suffix),
Strategy::conditional(
|context: &DilemmaContext| {
context
.state_view()
.history()
.moves_for_player(context.their_index())
.last()
.map_or(false, |m| m == D)
},
Strategy::mixed(on_defect),
Strategy::mixed(on_cooperate),
),
)
}
pub fn generous_tit_for_tat() -> DilemmaPlayer {
probabilistic_tit_for_tat(
Distribution::singleton(C),
Distribution::new(vec![(D, 0.9), (C, 0.1)]).unwrap(),
"- Generous (10%)",
)
}
pub fn probing_tit_for_tat() -> DilemmaPlayer {
probabilistic_tit_for_tat(
Distribution::new(vec![(C, 0.9), (D, 0.1)]).unwrap(),
Distribution::singleton(D),
"- Probing (10%)",
)
}
pub fn firm_but_fair() -> DilemmaPlayer {
Player::new(
"Firm but Fair".to_string(),
Strategy::new(|context: &DilemmaContext| {
context
.state_view()
.history()
.profiles()
.last()
.map_or(C, |profile| {
if profile[context.my_index()] == C && profile[context.their_index()] == D {
D
} else {
C
}
})
}),
)
}
pub fn pavlov() -> DilemmaPlayer {
Player::new(
"Pavlov".to_string(),
Strategy::new(|context: &DilemmaContext| {
context
.state_view()
.history()
.profiles()
.last()
.map_or(C, |profile| {
let my_move = profile[context.my_index()];
match profile[context.their_index()] {
Move::Cooperate => my_move,
Move::Defect => my_move.opposite(),
}
})
}),
)
}
pub fn grim_trigger() -> DilemmaPlayer {
Player::new(
"Grim Trigger".to_string(),
Strategy::trigger(
|context: &DilemmaContext| {
context
.state_view()
.history()
.moves_for_player(context.their_index())
.last()
.map_or(false, |m| m == D)
},
Strategy::pure(C),
Strategy::pure(D),
),
)
}
#[cfg(test)]
mod tests {
use super::*;
use std::rc::Rc;
#[test]
fn defector_vs_cooperator() {
let g = Repeated::new(Rc::new(Dilemma::prisoners_dilemma()), 100);
let mut players = PerPlayer::new([defector(), cooperator()]);
let history = g.play(&mut players).unwrap();
assert_eq!(history.score(), &Payoff::from([300, 0]));
}
#[test]
fn defector_vs_tit_for_tat() {
let g = Repeated::new(Rc::new(Dilemma::prisoners_dilemma()), 100);
let mut players = PerPlayer::new([defector(), tit_for_tat()]);
let history = g.play(&mut players).unwrap();
assert_eq!(history.score(), &Payoff::from([102, 99]));
}
#[test]
fn tit_for_tat_vs_tit_for_tat() {
let g = Repeated::new(Rc::new(Dilemma::prisoners_dilemma()), 100);
let mut players = PerPlayer::new([tit_for_tat(), tit_for_tat()]);
let history = g.play(&mut players).unwrap();
assert_eq!(history.score(), &Payoff::from([200, 200]));
}
#[test]
fn tit_for_tat_vs_suspicious_tit_for_tat() {
let g = Repeated::new(Rc::new(Dilemma::prisoners_dilemma()), 100);
let mut players = PerPlayer::new([tit_for_tat(), suspicious_tit_for_tat()]);
let history = g.play(&mut players).unwrap();
assert_eq!(history.score(), &Payoff::from([150, 150]));
}
#[test]
fn tit_for_two_tats_vs_suspicious_tit_for_tat() {
let g = Repeated::new(Rc::new(Dilemma::prisoners_dilemma()), 100);
let mut players = PerPlayer::new([tit_for_n_tats(2), suspicious_tit_for_tat()]);
let history = g.play(&mut players).unwrap();
assert_eq!(history.score(), &Payoff::from([198, 201]));
}
}