1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
use std::rc::Rc;
use crate::{
ErrorKind, Game, Move, Payoff, PerPlayer, PlayerIndex, Profile, Record, SimultaneousOutcome,
Turn, Utility,
};
/// A game in which each player plays a single move without knowledge of the other players' moves.
///
/// This is the most general form of
/// [simultaneous game](https://en.wikipedia.org/wiki/Simultaneous_game).
/// It is defined by two functions:
/// 1. A predicate that recognizes valid moves for each player.
/// 2. A function that yields the payoff given the moves played by each player.
///
/// This representation is best used for games with non-finite domains of moves. For games with
/// finite domains of moves, use [`Normal`](crate::Normal).
///
/// # Type variables
///
/// - `M` -- The type of moves played during the game.
/// - `U` -- The type of utility value awarded to each player in a payoff.
/// - `P` -- The number of players that play the game.
///
/// # Example
///
/// A simple game where two players pick each other's score. Player `P0` must pick an even score
/// for player `P1`, while `P1` must pick an odd score for `P0`.
///
/// ```
/// use t4t::*;
///
/// let valid_move = |p, n: i32| {
/// if p == for2::P0 {
/// n.rem_euclid(2) == 0 // P0 must pick an even number
/// } else {
/// n.rem_euclid(2) == 1 // P1 must pick an odd number
/// }
/// };
/// let payoff = |profile: Profile<i32, 2>| {
/// Payoff::from([profile[for2::P1], profile[for2::P0]])
/// };
/// let pick_em = Simultaneous::from_payoff_fn(valid_move, payoff);
///
/// assert_eq!(pick_em.num_players(), 2);
///
/// assert!(pick_em.is_valid_move_for_player(for2::P0, 2));
/// assert!(pick_em.is_valid_move_for_player(for2::P1, -3));
/// assert!(!pick_em.is_valid_move_for_player(for2::P0, 5));
/// assert!(!pick_em.is_valid_move_for_player(for2::P1, -4));
///
/// assert!(pick_em.is_valid_profile(Profile::new([-2, 3])));
/// assert!(!pick_em.is_valid_profile(Profile::new([-2, 4])));
/// assert!(!pick_em.is_valid_profile(Profile::new([-3, 5])));
///
/// assert_eq!(pick_em.payoff(Profile::new([-4, 7])), Payoff::from([7, -4]));
/// assert_eq!(pick_em.payoff(Profile::new([0, -1])), Payoff::from([-1, 0]));
/// ```
pub struct Simultaneous<M, U, const P: usize> {
move_fn: Box<dyn Fn(PlayerIndex<P>, M) -> bool>,
payoff_fn: Rc<dyn Fn(Profile<M, P>) -> Payoff<U, P>>,
}
impl<M: Move, U: Utility, const P: usize> Game<P> for Simultaneous<M, U, P> {
type Move = M;
type Utility = U;
type Outcome = SimultaneousOutcome<M, U, P>;
type State = ();
type View = ();
fn first_turn(&self) -> Turn<(), M, SimultaneousOutcome<M, U, P>, P> {
let state = Rc::new(());
Turn::all_players(state.clone(), move |_, profile| {
for ply in profile.plies() {
let player = ply.player.unwrap();
if !self.is_valid_move_for_player(player, ply.the_move) {
return Err(ErrorKind::InvalidMove(player, ply.the_move));
}
}
Ok(Turn::end(
state,
SimultaneousOutcome::new(profile, self.payoff(profile)),
))
})
}
fn state_view(&self, _state: &(), _player: PlayerIndex<P>) {}
fn is_valid_move(&self, _state: &(), player: PlayerIndex<P>, the_move: M) -> bool {
self.is_valid_move_for_player(player, the_move)
}
}
impl<M: Move, U: Utility, const P: usize> Simultaneous<M, U, P> {
/// Construct a new simultaneous move game given (1) a predicate that determines if a move is
/// valid for a given player, and (2) a function that yields the payoff given a profile
/// containing a valid move played by each player.
pub fn from_payoff_fn<MoveFn, PayoffFn>(move_fn: MoveFn, payoff_fn: PayoffFn) -> Self
where
MoveFn: Fn(PlayerIndex<P>, M) -> bool + 'static,
PayoffFn: Fn(Profile<M, P>) -> Payoff<U, P> + 'static,
{
Simultaneous {
move_fn: Box::new(move_fn),
payoff_fn: Rc::new(payoff_fn),
}
}
/// Construct a new simultaneous move game given (1) a predicate that determines if a move is
/// valid for a given player, and (2) a utility function for each player.
pub fn from_utility_fns<MoveFn, UtilFn>(move_fn: MoveFn, util_fns: PerPlayer<UtilFn, P>) -> Self
where
MoveFn: Fn(PlayerIndex<P>, M) -> bool + 'static,
UtilFn: Fn(M) -> U + 'static,
{
let payoff_fn = move |profile: Profile<M, P>| {
Payoff::new(PerPlayer::generate(|player| {
util_fns[player](profile[player])
}))
};
Self::from_payoff_fn(move_fn, payoff_fn)
}
/// Is this a valid move for the given player?
pub fn is_valid_move_for_player(&self, player: PlayerIndex<P>, the_move: M) -> bool {
(*self.move_fn)(player, the_move)
}
/// Is this a valid strategy profile? A profile is valid if each move is valid for the
/// corresponding player.
pub fn is_valid_profile(&self, profile: Profile<M, P>) -> bool {
PlayerIndex::all().all(|player| self.is_valid_move_for_player(player, profile[player]))
}
/// Get the payoff for the given strategy profile.
///
/// This method may return an arbitrary payoff if given an
/// [invalid profile](crate::Simultaneous::is_valid_profile).
pub fn payoff(&self, profile: Profile<M, P>) -> Payoff<U, P> {
(*self.payoff_fn)(profile)
}
}