social_tournament/
lib.rs

1//!
2//! This is a library for creating tournament schedules for the sport I love.
3//!
4//! 🏓  table tennis 🏓
5//!
6//! The focus is on meeting as many opponents and teammates as possible during the tournament. One can draw a single or double tournament.
7//! After the games are drawn, you can decide whether you also want to use the automatic table distribution algorithm.
8use crate::double::{DrawOption, draw_doubles};
9use crate::table::{DistributionOption, Table, distribute_tables};
10use crate::single::draw_singles;
11use crate::Error::DefaultError;
12use thiserror::Error;
13use crate::pdf::language::Language;
14use crate::pdf::route_card::create_route_cards_for_double;
15
16#[macro_use]
17extern crate lazy_static;
18
19pub mod single;
20pub mod double;
21pub mod table;
22pub mod pdf;
23
24type Result<T> = std::result::Result<T, Error>;
25
26/// Default [enum@Error] for this crate
27#[derive(Error, Debug)]
28pub enum Error {
29    #[error("default social tournament error")]
30    DefaultError(),
31}
32
33/// This enum describes all relevant parameters for the tournament. Choose between a single
34/// and a double tournament. How many players and rounds do you have? If you are playing doubles,
35/// what is your preferred [DrawOption] if there are not enough players for "full" double pairs.
36/// How many tables do you have? Specify a [TableConfig] to determine how many tables you can set up
37/// in your room or gym and choose a [DistributionOption] if you have more matches in a round than
38/// tables available.
39#[derive(PartialEq, Clone, Debug)]
40pub enum TournamentConfig {
41    Single {
42        number_of_players: usize,
43        number_of_rounds: usize,
44        table_config: TableConfig,
45    },
46    Double {
47        number_of_players: usize,
48        number_of_rounds: usize,
49        draw_option: Option<DrawOption>,
50        table_config: TableConfig,
51    },
52}
53
54/// Specify a [TableConfig] to determine how many tables you can set up
55/// in your room or gym and choose a [DistributionOption] if you have more matches in a round than
56/// tables available.
57#[derive(PartialEq, Clone, Debug)]
58pub struct TableConfig {
59    pub available_tables: usize,
60    pub distribution_option: Option<DistributionOption>,
61}
62
63/// Enum that represent one match.
64#[derive(PartialEq, Clone, Debug)]
65pub enum Match {
66    /// Struct that represent a single match. It holds the opponents `a` and `b`.
67    /// The unique numbers is the id of the corresponding player.
68    SingleMatch { a: usize, b: usize },
69    /// Struct that represent a double match. It holds the pairs `double_a` and `double_b`.
70    /// The tuple contains two numbers, the unique player ids.
71    DoubleMatch { double_a: (usize, usize), double_b: (usize, usize) },
72}
73
74/// Struct for a tournament that represent one round. It holds the `round_number`
75/// and the `matches` that take place in this round. Matches are a list of [Match].
76#[derive(Debug, Clone)]
77pub struct Round {
78    pub round_number: usize,
79    pub matches: Vec<Match>,
80}
81
82/// This struct provides the interface to `draw` the tournament, `distribute` the tables and
83/// `create_route_cards_pdf`. All results are stored in this instance and can be read out
84/// by accessing the corresponding field.
85/// `rounds` contains the drawn rounds, `tables` contains the distributed tables and `pdf` contains
86/// a pdf as a byte array.
87#[derive(Debug, Clone)]
88pub struct SocialTournament {
89    config: TournamentConfig,
90    pub rounds: Option<Vec<Round>>,
91    pub tables: Option<Vec<Vec<Table>>>,
92    pub pdf: Option<Vec<u8>>,
93}
94
95impl SocialTournament {
96
97    /// This method is used to get a new instance of [SocialTournament]. You have to provide a
98    /// [TournamentConfig].
99    pub fn new(config: TournamentConfig) -> SocialTournament {
100        SocialTournament { config, rounds: None, tables: None, pdf: None }
101    }
102
103    /// Interface to "create" a tournament.
104    ///
105    /// For a given `number_of_players` and `number_of_rounds` in [TournamentConfig] it returns a
106    /// schedule of Rounds with the corresponding matches.
107    ///
108    /// # Example Single Tournament
109    ///
110    /// For `number_of_rounds` < `number_of_players` the round
111    /// robin algorithm ensures, that one does not face an opponents twice. For
112    /// `number_of_rounds` >= `number_of_players` the round robin is calculated one more round.
113    /// For an odd number of players, the algorithm calculates with `number_of_players` + 1.
114    /// So you have to make sure that the player who plays against the highest number has a bye.
115    /// ```
116    /// use social_tournament::{Round, SocialTournament, TournamentConfig, TableConfig};
117    ///
118    /// let mut tournament = SocialTournament::new(TournamentConfig::Single {
119    ///     number_of_players: 12,
120    ///     number_of_rounds: 2,
121    ///     table_config: TableConfig { available_tables: 10, distribution_option: None }
122    /// });
123    ///
124    /// tournament.draw().unwrap();
125    /// let rounds: Vec<Round> = tournament.rounds.unwrap();
126    /// /*
127    /// Creates:
128    /// Round number: 0
129    /// SingleMatch { a: 0, b: 9 }
130    /// SingleMatch { a: 1, b: 8 }
131    /// SingleMatch { a: 2, b: 7 }
132    /// SingleMatch { a: 3, b: 6 }
133    /// SingleMatch { a: 4, b: 5 }
134    /// --------------
135    /// Round number: 1
136    /// SingleMatch { a: 1, b: 9 }
137    /// SingleMatch { a: 2, b: 0 }
138    /// SingleMatch { a: 3, b: 8 }
139    /// SingleMatch { a: 4, b: 7 }
140    /// SingleMatch { a: 5, b: 6 }
141    /// --------------
142    /// ...
143    /// */
144    /// ```
145    /// # Example Double Tournament
146    ///
147    /// For number of players that are not completely divisible by 4 you can choose between three
148    /// [DrawOption].
149    /// Depending on the selected option you can have doubles with only 3 players, single matches or
150    /// player with byes. You have to make sure that the player ids >= `number_of_players` in the
151    /// schedule post processed correctly. So that you can mark them as byes for example.
152    ///
153    /// ```
154    /// use social_tournament::{Round, SocialTournament, TournamentConfig, TableConfig};
155    ///
156    /// let mut tournament = SocialTournament::new(TournamentConfig::Double {
157    ///     number_of_players: 24,
158    ///     number_of_rounds: 2,
159    ///     draw_option: None,
160    ///     table_config: TableConfig { available_tables: 10, distribution_option: None }
161    /// });
162    ///
163    /// tournament.draw().unwrap();
164    /// let rounds: Vec<Round> = tournament.rounds.unwrap();
165    /// /*
166    /// Creates:
167    /// Round number: 0
168    /// DoubleMatch { double_a: (2, 37), double_b: (1, 38) }
169    /// DoubleMatch { double_a: (3, 36), double_b: (4, 35) }
170    /// DoubleMatch { double_a: (5, 34), double_b: (6, 33) }
171    /// DoubleMatch { double_a: (7, 32), double_b: (8, 31) }
172    /// DoubleMatch { double_a: (9, 30), double_b: (10, 29) }
173    /// DoubleMatch { double_a: (11, 28), double_b: (12, 27) }
174    /// DoubleMatch { double_a: (13, 26), double_b: (14, 25) }
175    /// DoubleMatch { double_a: (15, 24), double_b: (16, 23) }
176    /// DoubleMatch { double_a: (17, 22), double_b: (18, 21) }
177    /// --------------
178    /// Round number: 1
179    /// DoubleMatch { double_a: (20, 21), double_b: (2, 0) }
180    /// DoubleMatch { double_a: (3, 38), double_b: (7, 34) }
181    /// DoubleMatch { double_a: (4, 37), double_b: (6, 35) }
182    /// DoubleMatch { double_a: (5, 36), double_b: (9, 32) }
183    /// DoubleMatch { double_a: (8, 33), double_b: (10, 31) }
184    /// DoubleMatch { double_a: (11, 30), double_b: (15, 26) }
185    /// DoubleMatch { double_a: (12, 29), double_b: (14, 27) }
186    /// DoubleMatch { double_a: (13, 28), double_b: (17, 24) }
187    /// DoubleMatch { double_a: (16, 25), double_b: (18, 23) }
188    /// --------------
189    /// ...
190    /// */
191    /// ```
192    pub fn draw(&mut self) -> Result<()> {
193        match self.config.clone() {
194            TournamentConfig::Single { number_of_players, number_of_rounds, .. } => {
195                self.rounds = Some(draw_singles(number_of_players, number_of_rounds))
196            }
197            TournamentConfig::Double { number_of_players, number_of_rounds, draw_option, .. } => {
198                self.rounds = Some(draw_doubles(number_of_players, number_of_rounds, draw_option))
199            }
200        }
201        Ok(())
202    }
203
204
205    /// Public interface to distribute the tables in a room or gym for the drawn tournament.
206    ///
207    /// Provides an interface to pass the drawn tournament [Round] and the number of your
208    /// `available_tables`.
209    /// Sometimes you may have more tables, but not enough space. Specify how
210    /// many [Table] you can provide for the tournament in your room or gym. The algorithm ensures
211    /// that enough sub-rounds are formed. You can specify the forming method by providing the
212    /// [DistributionOption]. Depending on the option you choose, can have as many matches as possible
213    /// in a sub-round or mainly even matches in each sub-round.
214    /// Make sure to call the draw method first.
215    ///
216    /// # Example Single Tournament
217    /// ```
218    /// use social_tournament::{Round, SocialTournament, TournamentConfig, TableConfig};
219    /// use social_tournament::table::Table;
220    ///
221    /// let mut tournament = SocialTournament::new(TournamentConfig::Single {
222    ///     number_of_players: 12,
223    ///     number_of_rounds: 2,
224    ///     table_config: TableConfig { available_tables: 10, distribution_option: None }
225    /// });
226    ///
227    /// tournament.draw().unwrap();
228    /// tournament.distribute().unwrap();
229    ///
230    /// let tables: Vec<Vec<Table>> = tournament.tables.unwrap();
231    /// /*
232    /// Creates:
233    /// Table { table_number: 0, occupied_number: 0 }
234    /// Table { table_number: 1, occupied_number: 0 }
235    /// Table { table_number: 2, occupied_number: 0 }
236    /// Table { table_number: 3, occupied_number: 0 }
237    /// Table { table_number: 0, occupied_number: 1 }
238    /// Table { table_number: 1, occupied_number: 1 }
239    /// --------------
240    /// Table { table_number: 0, occupied_number: 0 }
241    /// Table { table_number: 1, occupied_number: 0 }
242    /// Table { table_number: 2, occupied_number: 0 }
243    /// Table { table_number: 3, occupied_number: 0 }
244    /// Table { table_number: 0, occupied_number: 1 }
245    /// Table { table_number: 1, occupied_number: 1 }
246    /// --------------
247    /// */
248    /// ```
249    /// # Example Double Tournament
250    /// ```
251    /// use social_tournament::{Round, SocialTournament, TournamentConfig, TableConfig};
252    /// use social_tournament::table::Table;
253    ///
254    /// let mut tournament = SocialTournament::new(TournamentConfig::Double {
255    ///     number_of_players: 24,
256    ///     number_of_rounds: 2,
257    ///     draw_option: None,
258    ///     table_config: TableConfig { available_tables: 10, distribution_option: None }
259    /// });
260    ///
261    /// tournament.draw().unwrap();
262    /// tournament.distribute().unwrap();
263    ///
264    /// let tables: Vec<Vec<Table>> = tournament.tables.unwrap();
265    /// /*
266    /// Creates:
267    /// Table { table_number: 0, occupied_number: 0 }
268    /// Table { table_number: 1, occupied_number: 0 }
269    /// Table { table_number: 2, occupied_number: 0 }
270    /// Table { table_number: 3, occupied_number: 0 }
271    /// Table { table_number: 0, occupied_number: 1 }
272    /// Table { table_number: 1, occupied_number: 1 }
273    /// --------------
274    /// Table { table_number: 0, occupied_number: 0 }
275    /// Table { table_number: 1, occupied_number: 0 }
276    /// Table { table_number: 2, occupied_number: 0 }
277    /// Table { table_number: 3, occupied_number: 0 }
278    /// Table { table_number: 0, occupied_number: 1 }
279    /// Table { table_number: 1, occupied_number: 1 }
280    /// --------------
281    /// */
282    /// ```
283    ///
284    pub fn distribute(&mut self) -> Result<()> {
285        match self.rounds.clone() {
286            None => {
287                Err(DefaultError())
288            }
289            Some(rounds) => {
290                match self.config.clone() {
291                    TournamentConfig::Single { table_config, .. } => {
292                        self.tables = Some(distribute_tables(&rounds, table_config.available_tables, table_config.distribution_option))
293                    }
294                    TournamentConfig::Double { table_config, .. } => {
295                        self.tables = Some(distribute_tables(&rounds, table_config.available_tables, table_config.distribution_option))
296                    }
297                }
298                Ok(())
299            }
300        }
301    }
302
303    /// Public interface to generate route cards for a drawn tournament with distributed tables.
304    ///
305    /// Available [Language] are English and German. Make sure you have drawn and distributed before
306    /// calling this method.
307    ///
308    /// # Example Double Tournament
309    /// ```
310    /// use social_tournament::{Round, SocialTournament, TournamentConfig, TableConfig};
311    /// use social_tournament::table::Table;
312    /// use social_tournament::pdf::language::Language;
313    ///
314    /// let mut tournament = SocialTournament::new(TournamentConfig::Double {
315    ///     number_of_players: 24,
316    ///     number_of_rounds: 2,
317    ///     draw_option: None,
318    ///     table_config: TableConfig { available_tables: 10, distribution_option: None }
319    /// });
320    ///
321    /// tournament.draw().unwrap();
322    /// tournament.distribute().unwrap();
323    /// tournament.create_route_cards_pdf(Language::EN).unwrap();
324    ///
325    /// let pdf: Vec<u8> = tournament.pdf.unwrap();
326    pub fn create_route_cards_pdf(&mut self, language: Language) -> Result<()> {
327        match (self.rounds.clone(), self.tables.clone()) {
328            (Some(rounds), Some(tables)) => {
329                match self.config.clone() {
330                    TournamentConfig::Single { .. } => {
331                        //TODO
332                    }
333                    TournamentConfig::Double { number_of_players, draw_option, table_config, .. } => {
334                        let draw_o = draw_option.unwrap_or(DrawOption::AllInAction);
335                        self.pdf = Some(create_route_cards_for_double(&rounds, &tables, number_of_players, draw_o, table_config.available_tables, language))
336                    }
337                }
338                Ok(())
339            }
340            _ => Err(DefaultError())
341        }
342    }
343}