1mod commands;
2mod constants;
3
4pub use commands::Commands;
5use frankenstein::{
6 client_reqwest::Bot, methods::{DeleteMyCommandsParams, SetMyCommandsParams, SetMyDefaultAdministratorRightsParams}, types::BotCommandScope, AsyncTelegramApi, Error
7};
8use rand::{Rng, seq::index::sample};
9use serde::Deserialize;
10use std::time::{SystemTime, UNIX_EPOCH};
11
12#[derive(Deserialize)]
14pub struct Config {
15 pub token: String,
17 #[serde(default)]
19 pub game: RouletteConfig,
20 #[serde(default)]
22 pub groups: Vec<GroupConfig>,
23}
24
25#[derive(Clone, Debug, Deserialize)]
27pub struct RouletteConfig {
28 #[serde(default = "constants::chambers")]
30 chambers: usize,
31 #[serde(default = "constants::bullets")]
33 bullets: usize,
34 #[serde(default = "constants::jam_probability")]
36 jam_probability: f64,
37 #[serde(default = "constants::min_mute_time")]
39 min_mute_time: u32,
40 #[serde(default = "constants::max_mute_time")]
42 max_mute_time: u32,
43}
44
45impl RouletteConfig {
46 pub fn start(self) -> Result<Roulette, &'static str> {
48 if self.chambers <= 0 {
50 return Err("Number of chambers must be greater than 0");
51 }
52 if self.bullets <= 0 {
53 return Err("Number of bullets must be greater than 0");
54 }
55 if self.bullets > self.chambers {
56 return Err("Number of bullets must be less than or equal to number of chambers");
57 }
58 if self.min_mute_time < 30 {
59 return Err("Minimum mute time must be greater than or equal to 30 seconds");
60 }
61 if self.max_mute_time > 3600 {
62 return Err("Maximum mute time must be less than or equal to 3600 seconds");
64 }
65 if self.min_mute_time > self.max_mute_time {
66 return Err("Minimum mute time must be less than or equal to maximum mute time");
67 }
68
69 let contents = vec![false; self.chambers];
71 let mut roulette = Roulette {
72 config: self,
73 contents,
74 position: 0,
75 };
76 roulette.reload();
77
78 Ok(roulette)
79 }
80
81 pub fn info(&self) -> (usize, usize) {
83 (self.bullets, self.chambers)
84 }
85
86 pub fn random_mute_until(&self) -> (u64, u64) {
88 let mut rng = rand::rng();
90 let duration: u64 = rng
91 .random_range(self.min_mute_time..=self.max_mute_time)
92 .into();
93 let now = SystemTime::now()
95 .duration_since(UNIX_EPOCH)
96 .expect("Time went backwards")
97 .as_secs();
98 (duration, now + duration)
99 }
100}
101
102impl Default for RouletteConfig {
103 fn default() -> Self {
104 Self {
105 chambers: constants::chambers(),
106 bullets: constants::bullets(),
107 jam_probability: constants::jam_probability(),
108 min_mute_time: constants::min_mute_time(),
109 max_mute_time: constants::max_mute_time(),
110 }
111 }
112}
113
114#[derive(Clone, Debug)]
116pub struct Roulette {
117 config: RouletteConfig,
119 contents: Vec<bool>,
121 position: usize,
123}
124
125impl Roulette {
126 pub fn reload(&mut self) {
128 self.position = 0;
129 self.contents.fill(false);
130
131 let mut rng = rand::rng();
133 let selected = sample(&mut rng, self.contents.len(), self.config.bullets);
134 for i in selected {
135 self.contents[i] = true;
136 }
137 }
138
139 pub fn info(&self) -> (usize, usize) {
141 self.config.info()
142 }
143
144 pub fn random_mute_until(&self) -> (u64, u64) {
146 self.config.random_mute_until()
147 }
148
149 pub fn fire(&mut self) -> FireResult {
155 if self.peek().0 == 0 {
156 return FireResult::NoBullets;
158 }
159
160 let jammed = rand::rng().random_bool(self.config.jam_probability);
162 if jammed {
163 return FireResult::Jammed;
164 }
165
166 let result = self.contents[self.position];
167 self.position += 1;
168
169 if result {
170 FireResult::Bullet
171 } else {
172 FireResult::Empty
173 }
174 }
175
176 pub fn peek(&self) -> (usize, usize) {
178 let filled = self
179 .contents[self.position..]
180 .iter()
181 .filter(|&&x| x)
182 .count();
183 let left = self.contents.len() - self.position;
184 (filled, left)
185 }
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum FireResult {
191 Empty,
193 Bullet,
195 Jammed,
197 NoBullets,
199}
200
201#[derive(Debug, Deserialize)]
203pub struct GroupConfig {
204 pub id: i64,
206 chambers: Option<usize>,
208 bullets: Option<usize>,
210 jam_probability: Option<f64>,
212 min_mute_time: Option<u32>,
214 max_mute_time: Option<u32>,
216}
217
218impl GroupConfig {
219 pub fn resolve(&self, default: &RouletteConfig) -> RouletteConfig {
221 let Self {
222 chambers,
223 bullets,
224 jam_probability,
225 min_mute_time,
226 max_mute_time,
227 ..
228 } = self;
229 let (chambers, bullets, jam_probability, min_mute_time, max_mute_time) = (
230 chambers.unwrap_or(default.chambers),
231 bullets.unwrap_or(default.bullets),
232 jam_probability.unwrap_or(default.jam_probability),
233 min_mute_time.unwrap_or(default.min_mute_time),
234 max_mute_time.unwrap_or(default.max_mute_time),
235 );
236 RouletteConfig {
237 chambers,
238 bullets,
239 jam_probability,
240 min_mute_time,
241 max_mute_time,
242 }
243 }
244}
245
246pub async fn init_commands_and_rights(bot: &Bot) -> Result<(), Error> {
248 let delete_param = DeleteMyCommandsParams::builder().build();
249 bot.delete_my_commands(&delete_param).await?;
250
251 let commands_param = SetMyCommandsParams::builder()
252 .commands(Commands::list())
253 .scope(BotCommandScope::AllGroupChats)
254 .build();
255 bot.set_my_commands(&commands_param).await?;
256
257 let rights_param = SetMyDefaultAdministratorRightsParams::builder()
258 .rights(constants::RECOMMENDED_ADMIN_RIGHTS)
259 .build();
260 bot.set_my_default_administrator_rights(&rights_param)
261 .await?;
262
263 Ok(())
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_fire() {
272 let config = RouletteConfig {
273 chambers: 3,
274 bullets: 1,
275 jam_probability: 0.0, min_mute_time: 60,
277 max_mute_time: 600,
278 };
279 let mut roulette = Roulette {
281 config,
282 contents: vec![false, true, false],
283 position: 0,
284 };
285
286 assert_eq!(roulette.fire(), FireResult::Empty);
287 assert_eq!(roulette.fire(), FireResult::Bullet);
288 assert_eq!(roulette.fire(), FireResult::NoBullets);
289 assert_eq!(roulette.fire(), FireResult::NoBullets);
290 }
291
292 #[test]
293 fn test_restart() {
294 let mut roulette = RouletteConfig::default().start().unwrap();
295
296 for _ in 0..10 {
297 roulette.reload();
298 }
299
300 assert_eq!(roulette.contents.len(), 6);
301 assert_eq!(roulette.peek().0, 2);
302 assert_eq!(roulette.position, 0);
303 }
304}