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}