t4t/
outcome.rs

1use std::fmt::Debug;
2use std::sync::Arc;
3
4use crate::{
5    Move, Payoff, PlayerIndex, PossibleProfiles, Profile, Record, State, Transcript, Utility,
6};
7
8/// A (potential) outcome of a game. A payoff combined with a record of the moves that produced it.
9///
10/// The outcomes for some built-in game types are:
11/// - [`SimultaneousOutcome`] for [simultaneous](crate::Simultaneous) and
12///   [normal-form](crate::Normal) games.
13/// - [`SequentialOutcome`] for sequential games.
14/// - [`History`](crate::History) for [repeated](crate::Repeated) games.
15pub trait Outcome<M: Move, U: Utility, const P: usize>:
16    Clone + Debug + PartialEq + Send + Sync + 'static
17{
18    /// A type for capturing the record of moves that produced (or would produce) this outcome.
19    ///
20    /// For simultaneous games, this will be a [profile](Profile) containing one move for each
21    /// player. For sequential games, it will be a [transcript](Transcript) of all moves played in
22    /// the game.
23    type Record: Record<M, P>;
24
25    /// A record of the moves that produced this outcome.
26    fn record(&self) -> &Self::Record;
27
28    /// The payoff associated with this outcome.
29    fn payoff(&self) -> &Payoff<U, P>;
30}
31
32/// A (potential) outcome of a simultaneous game.
33///
34/// The profile of moves played by each player and the resulting payoff.
35///
36/// For normal-form games, an outcome corresponds to a cell in the payoff table. The profile is the
37/// address of the cell, the payoff is its value.
38#[derive(Clone, Debug, PartialEq, Hash)]
39pub struct SimultaneousOutcome<M: Move, U: Utility, const P: usize> {
40    profile: Profile<M, P>,
41    payoff: Payoff<U, P>,
42}
43
44impl<M: Move, U: Utility, const P: usize> SimultaneousOutcome<M, U, P> {
45    /// Construct a new simultaneous game outcome.
46    pub fn new(profile: Profile<M, P>, payoff: Payoff<U, P>) -> Self {
47        SimultaneousOutcome { profile, payoff }
48    }
49
50    /// The move profile associated with this outcome.
51    pub fn profile(&self) -> &Profile<M, P> {
52        &self.profile
53    }
54}
55
56impl<M: Move, U: Utility, const P: usize> Outcome<M, U, P> for SimultaneousOutcome<M, U, P> {
57    type Record = Profile<M, P>;
58
59    fn record(&self) -> &Profile<M, P> {
60        &self.profile
61    }
62
63    fn payoff(&self) -> &Payoff<U, P> {
64        &self.payoff
65    }
66}
67
68/// A (potential) outcome of a sequential game.
69///
70/// A transcript of moves played by all players and the resulting payoff.
71///
72/// For extensive-form games, an outcome corresponds to a path through the game tree.
73#[derive(Clone, Debug, PartialEq, Hash)]
74pub struct SequentialOutcome<S: State, M: Move, U: Utility, const P: usize> {
75    final_state: S,
76    transcript: Transcript<M, P>,
77    payoff: Payoff<U, P>,
78}
79
80impl<S: State, M: Move, U: Utility, const P: usize> SequentialOutcome<S, M, U, P> {
81    /// Construct a new sequential game outcome.
82    pub fn new(final_state: S, transcript: Transcript<M, P>, payoff: Payoff<U, P>) -> Self {
83        SequentialOutcome {
84            final_state,
85            transcript,
86            payoff,
87        }
88    }
89
90    /// The final state when the game ended.
91    pub fn final_state(&self) -> &S {
92        &self.final_state
93    }
94
95    /// The move transcript associated with this outcome.
96    pub fn transcript(&self) -> &Transcript<M, P> {
97        &self.transcript
98    }
99}
100
101impl<S: State, M: Move, U: Utility, const P: usize> Outcome<M, U, P>
102    for SequentialOutcome<S, M, U, P>
103{
104    type Record = Transcript<M, P>;
105
106    fn record(&self) -> &Transcript<M, P> {
107        &self.transcript
108    }
109
110    fn payoff(&self) -> &Payoff<U, P> {
111        &self.payoff
112    }
113}
114
115/// An iterator over all possible outcomes of a [normal-form game](crate::Normal).
116///
117/// This enumerates the cells of the payoff table in
118/// [row-major order](https://en.wikipedia.org/wiki/Row-_and_column-major_order).
119#[derive(Clone)]
120pub struct PossibleOutcomes<'g, M: Move, U: Utility, const P: usize> {
121    profile_iter: PossibleProfiles<'g, M, P>,
122    payoff_fn: Arc<dyn Fn(Profile<M, P>) -> Payoff<U, P> + Send + Sync + 'g>,
123}
124
125impl<'g, M: Move, U: Utility, const P: usize> PossibleOutcomes<'g, M, U, P> {
126    /// Construct a new outcome iterator given an iterator over profiles and a payoff function.
127    pub fn new(
128        profile_iter: PossibleProfiles<'g, M, P>,
129        payoff_fn: Arc<dyn Fn(Profile<M, P>) -> Payoff<U, P> + Send + Sync + 'g>,
130    ) -> Self {
131        PossibleOutcomes {
132            profile_iter,
133            payoff_fn,
134        }
135    }
136
137    /// Constrain the iterator to enumerate only those cells where the given player plays a
138    /// specific move.
139    ///
140    /// If the move is not a valid move for that player, then the resulting iterator will not
141    /// generate any profiles.
142    ///
143    /// Multiple invocations of [`include`](PossibleOutcomes::include) and
144    /// [`exclude`](PossibleOutcomes::exclude) can be chained together to add several constraints to
145    /// the iterator.
146    ///
147    /// See the documentation for [`ProfileIter::include`](PossibleProfiles::include) for
148    /// examples and more info.
149    pub fn include(self, player: PlayerIndex<P>, the_move: M) -> Self {
150        PossibleOutcomes {
151            profile_iter: self.profile_iter.include(player, the_move),
152            ..self
153        }
154    }
155
156    /// Constrain the iterator to enumerate only those cells where the given player *does not* play
157    /// a specific move.
158    ///
159    /// If the move is not a valid move for that player, then this method will have no effect.
160    ///
161    /// Multiple invocations of [`include`](PossibleOutcomes::include) and
162    /// [`exclude`](PossibleOutcomes::exclude) can be chained together to add several constraints to
163    /// the iterator.
164    ///
165    /// See the documentation for [`ProfileIter::exclude`](PossibleProfiles::exclude) for
166    /// examples and more info.
167    pub fn exclude(self, player: PlayerIndex<P>, the_move: M) -> Self {
168        PossibleOutcomes {
169            profile_iter: self.profile_iter.exclude(player, the_move),
170            ..self
171        }
172    }
173
174    /// Constrain the iterator to generate only cells that correspond to "adjacent" profiles of the
175    /// given profile for a given player.
176    ///
177    /// An adjacent profile is one where the given player plays a different move, but all other
178    /// players play the move specified in the profile.
179    ///
180    /// Note that this doesn't correspond to adjacency in the payoff table, but rather an entire
181    /// row or column, minus the provided profile.
182    ///
183    /// See the documentation for [`ProfileIter::adjacent`](PossibleProfiles::adjacent)
184    /// for examples and more info.
185    pub fn adjacent(self, player: PlayerIndex<P>, profile: Profile<M, P>) -> Self {
186        PossibleOutcomes {
187            profile_iter: self.profile_iter.adjacent(player, profile),
188            ..self
189        }
190    }
191}
192
193impl<'g, M: Move, U: Utility, const P: usize> Iterator for PossibleOutcomes<'g, M, U, P> {
194    type Item = SimultaneousOutcome<M, U, P>;
195    fn next(&mut self) -> Option<Self::Item> {
196        self.profile_iter.next().map(|profile| {
197            let payoff = (*self.payoff_fn)(profile);
198            SimultaneousOutcome::new(profile, payoff)
199        })
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use super::*;
206    use impls::impls;
207    use test_log::test;
208
209    #[test]
210    fn possible_outcomes_is_send_sync() {
211        assert!(impls!(PossibleOutcomes<'_, (), u8, 2>: Send & Sync));
212    }
213}