diff --git a/discord-bot/src/discord_formatting.rs b/discord-bot/src/discord_formatting.rs index ecd777a..a15afb7 100644 --- a/discord-bot/src/discord_formatting.rs +++ b/discord-bot/src/discord_formatting.rs @@ -16,6 +16,10 @@ use twilight_util::builder::embed::{EmbedBuilder, EmbedFieldBuilder, EmbedFooter use crate::game::GameTopic; +const COLOR_INFO: u32 = 0x06b6d4; +const COLOR_WARN: u32 = 0xeab308; +const COLOR_ERROR: u32 = 0xef4444; + const NO_LOBBY_FOR_PRESS_HEADER: &str = "No Lobby for That"; const NO_LOBBY_FOR_PRESS_DESCRIPTION: &str = "You tried to press a button for a game but there isn't a running lobby for it.\nOr am I malfunctioning? Can you please let J Navith know about this bug if so?"; @@ -89,6 +93,8 @@ const REMAINING_TIME_HEADER: &str = "Time Remaining"; const RESPONSE_RECORDED_HEADER: &str = "Response Recorded"; const RESPONSE_RECORDED_DESCRIPTION: &str = "I've got your response. Hang tight while everyone submits theirs.\n(Oh, and you can still change your response while waiting!)"; +const YOUR_SUBMISSION_HEADER: &str = "Your Submission"; + const ALL_RESPONSES_IN_HEADER: &str = "All Responses In"; const ALL_RESPONSES_IN_DESCRIPTION: &str = "Amazing! Everyone submitted a response before time ran out!"; @@ -126,8 +132,6 @@ const ALL_GUESSES_AND_AWARDS_IN_HEADER: &str = "All Guesses & Awards In"; const ALL_GUESSES_AND_AWARDS_IN_DESCRIPTION: &str = "Hurray! Everyone's guessed and awarded a response!"; -const REJECTED_RESPONSE_HEADER: &str = "Your Submission"; - const TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_HEADER: &str = "Guessing & Awarding Time Over"; const TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_DESCRIPTION: &str = "Time ran out before everyone submitted a guess *and* award. It's their loss; let's just move on."; @@ -137,22 +141,22 @@ const NOT_TIME_TO_GUESS_DESCRIPTION: &str = "It's not the guessing phase anymore const NOT_TIME_TO_AWARD_HEADER: &str = "Can't Award Now"; const NOT_TIME_TO_AWARD_DESCRIPTION: &str = "It's not the guessing phase anymore (or yet), so you can't give out an award. In fact, I'm surprised you've even encountered this error!"; -const AWARD_FROM_HEADER: &str = "Award from"; -const GUESSED_BY_HEADER: &str = "Guessed by"; -const FOUND_BY_HEADER: &str = "Found by"; -const WRITTEN_BY_HEADER: &str = "Written by"; +const AWARD_FROM_HEADER: &str = "🏆 Award from"; +const GUESSED_BY_HEADER: &str = "🔘 Guessed by"; +const FOUND_BY_HEADER: &str = "🔎 Found by"; +const WRITTEN_BY_HEADER: &str = "✏️ Written by"; -const IMPERSONATION_TITLE: &str = "Impersonation!"; -const AUTHENTIC_TITLE: &str = "The Authentic Response!"; -const MISSED_TITLE: &str = "Missed!"; +const IMPERSONATION_TITLE: &str = "👹 Impersonation!"; +const MISSED_TITLE: &str = "😞 Missed!"; +const AUTHENTIC_TITLE: &str = "❇️ The Authentic Response!"; const COLOR_WRONG: u32 = 0xef4444; const COLOR_MISSED: u32 = 0xeab308; const COLOR_CORRECT: u32 = 0x10b981; -const MIDGAME_LEADERBOARD_TITLE: &str = "Current Standings"; +const MIDGAME_LEADERBOARD_TITLE: &str = "📈 Current Standings"; -const PREFIX_ENDGAME_HEADER: &str = "Final Placements"; +const PREFIX_ENDGAME_HEADER: &str = "📊 Final Placements"; const PREFIX_ENDGAME_DESCRIPTION: &str = "Let's all see everyone's final score and place since this is the end of the game!"; @@ -190,40 +194,48 @@ fn format_names( } } -pub fn embed_for_no_lobby_for_press() -> Embed { +fn basic_embed_with_color(color: u32, title: &str, description: &str) -> EmbedBuilder { EmbedBuilder::new() - .title(NO_LOBBY_FOR_PRESS_HEADER) - .description(NO_LOBBY_FOR_PRESS_DESCRIPTION) + .color(color) + .title(title) + .description(description) .validate() .unwrap() - .build() } + +fn info_embed(title: &str, description: &str) -> EmbedBuilder { + basic_embed_with_color(COLOR_INFO, title, description) +} +fn warn_embed(title: &str, description: &str) -> EmbedBuilder { + basic_embed_with_color(COLOR_WARN, title, description) +} +fn error_embed(title: &str, description: &str) -> EmbedBuilder { + basic_embed_with_color(COLOR_ERROR, title, description) +} + +pub fn embed_for_no_lobby_for_press() -> Embed { + error_embed(NO_LOBBY_FOR_PRESS_HEADER, NO_LOBBY_FOR_PRESS_DESCRIPTION).build() +} + pub fn embed_for_no_lobby_for_response(response: String) -> Embed { - EmbedBuilder::new() - .title(NO_LOBBY_FOR_RESPONSE_HEADER) - .description(NO_LOBBY_FOR_RESPONSE_DESCRIPTION) - .field(EmbedFieldBuilder::new(REJECTED_RESPONSE_HEADER, response).build()) - .validate() - .unwrap() - .build() + error_embed( + NO_LOBBY_FOR_RESPONSE_HEADER, + NO_LOBBY_FOR_RESPONSE_DESCRIPTION, + ) + .field(EmbedFieldBuilder::new(YOUR_SUBMISSION_HEADER, response).build()) + .build() } pub fn embed_for_unrecognized_button() -> Embed { - EmbedBuilder::new() - .title(UNRECOGNIZED_BUTTON_HEADER) - .description(UNRECOGNIZED_BUTTON_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(UNRECOGNIZED_BUTTON_HEADER, UNRECOGNIZED_BUTTON_DESCRIPTION).build() } pub fn embed_for_not_meeting_minimum_necessary_players() -> Embed { - EmbedBuilder::new() - .title(INSUFFICIENT_PLAYERS_HEADER) - .description(INSUFFICIENT_PLAYERS_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed( + INSUFFICIENT_PLAYERS_HEADER, + INSUFFICIENT_PLAYERS_DESCRIPTION, + ) + .build() } pub fn embed_for_private_lobby_controls() -> Embed { @@ -264,81 +276,44 @@ pub fn embed_for_main_lobby_message( } pub fn embed_for_autoclosing_lobby() -> Embed { - EmbedBuilder::new() - .title(AUTOCLOSING_LOBBY_HEADER) - .description(AUTOCLOSING_LOBBY_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(AUTOCLOSING_LOBBY_HEADER, AUTOCLOSING_LOBBY_DESCRIPTION).build() } pub fn embed_for_successful_abort() -> Embed { - EmbedBuilder::new() - .title(SUCCESSFUL_ABORT_HEADER) - .description(SUCCESSFUL_ABORT_DESCRIPTION) - .validate() - .unwrap() - .build() + info_embed(SUCCESSFUL_ABORT_HEADER, SUCCESSFUL_ABORT_DESCRIPTION).build() } pub fn embed_for_already_existing_game() -> Embed { - EmbedBuilder::new() - .title(ALREADY_EXISTING_GAME_HEADER) - .description(ALREADY_EXISTING_GAME_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed( + ALREADY_EXISTING_GAME_HEADER, + ALREADY_EXISTING_GAME_DESCRIPTION, + ) + .build() } pub fn embed_for_game_already_running() -> Embed { - EmbedBuilder::new() - .title(GAME_ALREADY_RUNNING_HEADER) - .description(GAME_ALREADY_RUNNING_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed( + GAME_ALREADY_RUNNING_HEADER, + GAME_ALREADY_RUNNING_DESCRIPTION, + ) + .build() } pub fn embed_to_say_hello_into_game() -> Embed { - EmbedBuilder::new() - .title(HELLO_TO_GAME_HEADER) - .description(HELLO_TO_GAME_DESCRIPTION) - .validate() - .unwrap() - .build() + info_embed(HELLO_TO_GAME_HEADER, HELLO_TO_GAME_DESCRIPTION).build() } pub fn embed_to_say_goodbye_from_game() -> Embed { - EmbedBuilder::new() - .title(GOODBYE_FROM_GAME_HEADER) - .description(GOODBYE_FROM_GAME_DESCRIPTION) - .validate() - .unwrap() - .build() + info_embed(GOODBYE_FROM_GAME_HEADER, GOODBYE_FROM_GAME_DESCRIPTION).build() } pub fn embed_to_say_already_in_game() -> Embed { - EmbedBuilder::new() - .title(ALREADY_IN_GAME_HEADER) - .description(ALREADY_IN_GAME_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(ALREADY_IN_GAME_HEADER, ALREADY_IN_GAME_DESCRIPTION).build() } pub fn embed_to_say_already_not_in_game() -> Embed { - EmbedBuilder::new() - .title(ALREADY_NOT_IN_GAME_HEADER) - .description(ALREADY_NOT_IN_GAME_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(ALREADY_NOT_IN_GAME_HEADER, ALREADY_NOT_IN_GAME_DESCRIPTION).build() } pub fn embed_for_may_not_abort() -> Embed { - EmbedBuilder::new() - .title(MAY_NOT_ABORT_HEADER) - .description(MAY_NOT_ABORT_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(MAY_NOT_ABORT_HEADER, MAY_NOT_ABORT_DESCRIPTION).build() } fn format_description_for_new_turn(subject_name: &str) -> String { @@ -358,11 +333,11 @@ pub fn embed_for_new_turn_about_player_and_topic(subject_name: &str) -> Embed { fn format_title_for_writing_phase(subject_name: &str, game_topic: &GameTopic) -> String { match game_topic { - GameTopic::Text(topic) => format!("{subject_name}: on the topic of {topic}"), + GameTopic::Text(topic) => format!("✏️ {subject_name}: on the topic of {topic}"), GameTopic::Player(other_player_name) => { - format!("{subject_name}: writing about fellow player {other_player_name}") + format!("✏️ {subject_name}: writing about fellow player {other_player_name}") } - GameTopic::None => format!("{subject_name}... without a topic?!"), + GameTopic::None => format!("✏️ {subject_name}... without a topic?!"), } } @@ -421,10 +396,9 @@ pub fn embed_for_writing_responses_phase( .build() } -pub fn embed_for_response_recorded() -> Embed { - EmbedBuilder::new() - .title(RESPONSE_RECORDED_HEADER) - .description(RESPONSE_RECORDED_DESCRIPTION) +pub fn embed_for_response_recorded(response: &str) -> Embed { + info_embed(RESPONSE_RECORDED_HEADER, RESPONSE_RECORDED_DESCRIPTION) + .field(EmbedFieldBuilder::new(YOUR_SUBMISSION_HEADER, response)) .validate() .unwrap() .build() @@ -439,21 +413,15 @@ pub fn embed_for_all_responses_in() -> Embed { .build() } pub fn embed_for_time_up_not_all_responses_in() -> Embed { - EmbedBuilder::new() - .title(TIME_UP_NOT_ALL_RESPONSES_IN_HEADER) - .description(TIME_UP_NOT_ALL_RESPONSES_IN_DESCRIPTION) - .validate() - .unwrap() - .build() + warn_embed( + TIME_UP_NOT_ALL_RESPONSES_IN_HEADER, + TIME_UP_NOT_ALL_RESPONSES_IN_DESCRIPTION, + ) + .build() } pub fn embed_for_no_responses() -> Embed { - EmbedBuilder::new() - .title(NO_RESPONSES_HEADER) - .description(NO_RESPONSES_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NO_RESPONSES_HEADER, NO_RESPONSES_DESCRIPTION).build() } fn format_description_for_prefix_guessing_phase( @@ -519,52 +487,25 @@ pub fn embed_for_postfix_guessing_phase( } pub fn embed_for_guess_recorded() -> Embed { - EmbedBuilder::new() - .title(GUESS_RECORDED_HEADER) - .description(GUESS_RECORDED_DESCRIPTION) - .validate() - .unwrap() - .build() + info_embed(GUESS_RECORDED_HEADER, GUESS_RECORDED_DESCRIPTION).build() } pub fn embed_for_award_recorded() -> Embed { - EmbedBuilder::new() - .title(AWARD_RECORDED_HEADER) - .description(AWARD_RECORDED_DESCRIPTION) - .validate() - .unwrap() - .build() + info_embed(AWARD_RECORDED_HEADER, AWARD_RECORDED_DESCRIPTION).build() } pub fn embed_for_no_self_awarding() -> Embed { - EmbedBuilder::new() - .title(NO_SELF_AWARDING_HEADER) - .description(NO_SELF_AWARDING_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NO_SELF_AWARDING_HEADER, NO_SELF_AWARDING_DESCRIPTION).build() } pub fn embed_for_no_self_guessing() -> Embed { - EmbedBuilder::new() - .title(NO_SELF_GUESSING_HEADER) - .description(NO_SELF_GUESSING_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NO_SELF_GUESSING_HEADER, NO_SELF_GUESSING_DESCRIPTION).build() } pub fn embed_for_no_subject_guessing() -> Embed { - EmbedBuilder::new() - .title(NO_SUBJECT_GUESSING_HEADER) - .description(NO_SUBJECT_GUESSING_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NO_SUBJECT_GUESSING_HEADER, NO_SUBJECT_GUESSING_DESCRIPTION).build() } pub fn embed_for_not_time_to_respond(response: String) -> Embed { - EmbedBuilder::new() - .title(NOT_TIME_TO_RESPOND_HEADER) - .description(NOT_TIME_TO_RESPOND_DESCRIPTION) - .field(EmbedFieldBuilder::new(REJECTED_RESPONSE_HEADER, response)) + error_embed(NOT_TIME_TO_RESPOND_HEADER, NOT_TIME_TO_RESPOND_DESCRIPTION) + .field(EmbedFieldBuilder::new(YOUR_SUBMISSION_HEADER, response)) .validate() .unwrap() .build() @@ -579,29 +520,18 @@ pub fn embed_for_all_guesses_and_awards_in() -> Embed { .build() } pub fn embed_for_time_up_not_all_guesses_or_awards_in() -> Embed { - EmbedBuilder::new() - .title(TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_HEADER) - .description(TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_DESCRIPTION) - .validate() - .unwrap() - .build() + warn_embed( + TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_HEADER, + TIME_UP_NOT_ALL_GUESSES_OR_AWARDS_IN_DESCRIPTION, + ) + .build() } pub fn embed_for_not_time_to_guess() -> Embed { - EmbedBuilder::new() - .title(NOT_TIME_TO_GUESS_HEADER) - .description(NOT_TIME_TO_GUESS_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NOT_TIME_TO_GUESS_HEADER, NOT_TIME_TO_GUESS_DESCRIPTION).build() } pub fn embed_for_not_time_to_award() -> Embed { - EmbedBuilder::new() - .title(NOT_TIME_TO_AWARD_HEADER) - .description(NOT_TIME_TO_AWARD_DESCRIPTION) - .validate() - .unwrap() - .build() + error_embed(NOT_TIME_TO_AWARD_HEADER, NOT_TIME_TO_AWARD_DESCRIPTION).build() } pub fn embed_for_focused_response( @@ -610,16 +540,19 @@ pub fn embed_for_focused_response( awarders: BTreeSet>, names: &BTreeMap, String>, ) -> Embed { - let guesser_names = format_names(guessers, names); - let awarder_names = format_names(awarders, names); + let mut embed = EmbedBuilder::new().description(response); - EmbedBuilder::new() - .description(response) - .field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) - .field(EmbedFieldBuilder::new(FOUND_BY_HEADER, guesser_names).build()) - .validate() - .unwrap() - .build() + if !awarders.is_empty() { + let awarder_names = format_names(awarders, names); + embed = embed.field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) + } + + if !guessers.is_empty() { + let guesser_names = format_names(guessers, names); + embed = embed.field(EmbedFieldBuilder::new(GUESSED_BY_HEADER, guesser_names).build()) + } + + embed.validate().unwrap().build() } pub fn embed_for_impersonation_reveal( @@ -629,16 +562,26 @@ pub fn embed_for_impersonation_reveal( awarders: BTreeSet>, names: &BTreeMap, String>, ) -> Embed { - let guesser_names = format_names(guessers, names); - let awarder_names = format_names(awarders, names); + let mut embed = EmbedBuilder::new().color(COLOR_WRONG).description(response); - EmbedBuilder::new() - .color(COLOR_WRONG) - .title(IMPERSONATION_TITLE) - .description(response) - .field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) - .field(EmbedFieldBuilder::new(GUESSED_BY_HEADER, guesser_names).build()) - .field(EmbedFieldBuilder::new(WRITTEN_BY_HEADER, fooler_name).build()) + if !awarders.is_empty() { + let awarder_names = format_names(awarders, names); + embed = embed.field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) + } + + if !guessers.is_empty() { + let guesser_names = format_names(guessers, names); + embed = embed.field(EmbedFieldBuilder::new(GUESSED_BY_HEADER, guesser_names).build()) + } + + embed + .field( + EmbedFieldBuilder::new( + IMPERSONATION_TITLE, + format!("{WRITTEN_BY_HEADER} {fooler_name}"), + ) + .build(), + ) .validate() .unwrap() .build() @@ -657,16 +600,20 @@ pub fn embed_for_subject_reveal( (COLOR_CORRECT, AUTHENTIC_TITLE) }; - let guesser_names = format_names(guessers, names); - let awarder_names = format_names(awarders, names); + let mut embed = EmbedBuilder::new().color(color).description(response); - EmbedBuilder::new() - .color(color) - .title(title) - .description(response) - .field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) - .field(EmbedFieldBuilder::new(FOUND_BY_HEADER, guesser_names).build()) - .field(EmbedFieldBuilder::new(WRITTEN_BY_HEADER, subject_name).build()) + if !awarders.is_empty() { + let awarder_names = format_names(awarders, names); + embed = embed.field(EmbedFieldBuilder::new(AWARD_FROM_HEADER, awarder_names).build()) + } + + if !guessers.is_empty() { + let guesser_names = format_names(guessers, names); + embed = embed.field(EmbedFieldBuilder::new(FOUND_BY_HEADER, guesser_names).build()) + } + + embed + .field(EmbedFieldBuilder::new(title, format!("{WRITTEN_BY_HEADER} {subject_name}")).build()) .validate() .unwrap() .build() @@ -769,8 +716,9 @@ pub fn embed_for_endgame_placement( "{player_names} with {score} points!\nJust one placement away from first! For that, you've each earned a silver medal: {silver_medals}\n\nNot to spoil your celebration, but wait, who actually got first?!" ) } else { - "{player_names} with {score} points!\nFor your impressive performance this game: 🥈" - .into() + format!( + "{player_names} with {score} points!\nFor your impressive performance this game: 🥈" + ) }, ), 3 => ( @@ -782,7 +730,7 @@ pub fn embed_for_endgame_placement( "{player_names} with {score} points!\nWe got bronze medals made just for you guys: {bronze_medals}" ) } else { - "{player_names} with {score} points!\nI think this belongs to you: 🥉".into() + format!("{player_names} with {score} points!\nI think this belongs to you: 🥉") }, ), more => ( diff --git a/discord-bot/src/game.rs b/discord-bot/src/game.rs index b3a9a61..6765f70 100644 --- a/discord-bot/src/game.rs +++ b/discord-bot/src/game.rs @@ -100,7 +100,7 @@ const AUTOCLOSE_MINS: u64 = 15; #[derive(Debug, Clone)] struct Settings { write_time: Duration, - guess_time: Duration, + guess_time_per_response: Duration, max_p: f64, @@ -113,8 +113,8 @@ struct Settings { impl Default for Settings { fn default() -> Self { Self { - write_time: Duration::from_secs(90), - guess_time: Duration::from_secs(90), + write_time: Duration::from_secs(70), + guess_time_per_response: Duration::from_secs(7), max_p: 2., @@ -545,19 +545,7 @@ pub async fn actor( // endregion: Join and settings time - let (initial_topics_sender, initial_topics_receiver) = oneshot::channel(); - - topic_picker - .send(topic_picker::Message::GiveTopics { - existing: vec![], - desired: performances.len(), - max_p: settings.max_p, - callback: initial_topics_sender, - }) - .await - .unwrap(); - - let mut topics = initial_topics_receiver.await.unwrap(); + let mut topics = Vec::new(); // region: Each player's turn @@ -569,6 +557,18 @@ pub async fn actor( while let Some(subject) = { random_remove_no_preserve_order(&mut needs_to_go, &mut rng()) } { already_gone.insert(subject); + let (new_topics_sender, new_topics_receiver) = oneshot::channel(); + topic_picker + .send(topic_picker::Message::GiveTopics { + existing: topics, + desired: performances.len(), + max_p: settings.max_p, + callback: new_topics_sender, + }) + .await + .unwrap(); + topics = new_topics_receiver.await.unwrap(); + let (_topic_id, topic) = topics[turn]; turn += 1; @@ -687,15 +687,6 @@ pub async fn actor( indexmap::map::Entry::Vacant(vacant_entry) => { vacant_entry.insert(Default::default()); - let (new_topics_sender, new_topics_receiver) = oneshot::channel(); - topic_picker.send(topic_picker::Message::GiveTopics { - existing: topics, - desired: performances.len(), - max_p: settings.max_p, - callback: new_topics_sender, - }).await.unwrap(); - topics = new_topics_receiver.await.unwrap(); - if !already_gone.contains(&user) { insert_into_vec_without_duplicating(&mut needs_to_go, user); } @@ -831,7 +822,7 @@ pub async fn actor( insert_into_vec_without_duplicating(&mut needs_to_go, user); } - responses.insert(user, response); + responses.insert(user, response.clone()); if let Err(error) = discord_client.interaction(application_id).create_response( interaction_id, @@ -839,7 +830,7 @@ pub async fn actor( &InteractionResponse { kind: InteractionResponseType::ChannelMessageWithSource, data: Some(InteractionResponseDataBuilder::new() - .embeds([embed_for_response_recorded()]) + .embeds([embed_for_response_recorded(&response)]) .flags(MessageFlags::EPHEMERAL) .build() ) @@ -1001,12 +992,17 @@ pub async fn actor( sleep(small_amount_of_reading_time).await; } + let responses_to_guess_on = responses.len(); + + let guess_time = + settings.guess_time_per_response * responses_to_guess_on.try_into().unwrap(); + let postfix_guessing_phase_message = match discord_client .create_message(channel_id) .embeds(&[embed_for_postfix_guessing_phase( &subject_name, &topic, - settings.guess_time, + guess_time, )]) .await { @@ -1037,11 +1033,11 @@ pub async fn actor( let mut guesses = BTreeMap::new(); let mut awards = BTreeMap::new(); - let mut time_shown_in_message = settings.guess_time; + let mut time_shown_in_message = guess_time; - let end_time = Instant::now() + settings.guess_time; + let end_time = Instant::now() + guess_time; - let guessing_time_up = sleep(settings.guess_time); + let guessing_time_up = sleep(guess_time); pin!(guessing_time_up); let mut about_every_half_second = interval(Duration::from_millis(500)); @@ -1086,15 +1082,6 @@ pub async fn actor( indexmap::map::Entry::Vacant(vacant_entry) => { vacant_entry.insert(Default::default()); - let (new_topics_sender, new_topics_receiver) = oneshot::channel(); - topic_picker.send(topic_picker::Message::GiveTopics { - existing: topics, - desired: performances.len(), - max_p: settings.max_p, - callback: new_topics_sender, - }).await.unwrap(); - topics = new_topics_receiver.await.unwrap(); - if let Err(error) = discord_client.interaction(application_id).create_response( interaction_id, interaction_token.expose_secret(), @@ -1262,7 +1249,6 @@ pub async fn actor( tracing::warn!(?error, ?user, "couldn't inform that their guess was recorded"); } - if guesses.len() == performances.len().saturating_sub(1) && awards.len() == performances.len() { if let Err(err) = discord_client.create_message(channel_id) .embeds(&[embed_for_all_guesses_and_awards_in()]) @@ -1455,7 +1441,7 @@ pub async fn actor( Ok(message_response) => match message_response.model().await { Ok(message) => { // Delay before showing the answer so people have time to read (and for drama) - let reading_time = Duration::from_secs(2); + let reading_time = Duration::from_secs(3); sleep(reading_time).await; if let Err(error) = discord_client @@ -1492,62 +1478,67 @@ pub async fn actor( } // Delay before showing the next one so people have time to read - sleep(Duration::from_millis(2500)).await; + let reading_time = Duration::from_secs(3); + sleep(reading_time).await; } // endregion: Responses that fooled someone // region: The subject's response - let response = responses.get(&subject).unwrap(); - let awarders = awarders_of_response.remove(&subject).unwrap_or_default(); + if let Some(response) = responses.get(&subject) { + let awarders = awarders_of_response.remove(&subject).unwrap_or_default(); - match discord_client - .create_message(channel_id) - .embeds(&[embed_for_focused_response( - response, - guessed_subject.clone(), - awarders.clone(), - &names, - )]) - .await - { - Ok(message_response) => match message_response.model().await { - Ok(message) => { - // Delay before showing the answer so people have time to read (and for drama) - let reading_time = Duration::from_secs(2); - sleep(reading_time).await; + match discord_client + .create_message(channel_id) + .embeds(&[embed_for_focused_response( + response, + guessed_subject.clone(), + awarders.clone(), + &names, + )]) + .await + { + Ok(message_response) => match message_response.model().await { + Ok(message) => { + // Delay before showing the answer so people have time to read (and for drama) + let reading_time = Duration::from_secs(3); + sleep(reading_time).await; - if let Err(error) = discord_client - .update_message(channel_id, message.id) - .embeds(Some(&[embed_for_subject_reveal( - &subject_name, - &response, - guessed_subject, - awarders, - &names, - )])) - .await - { + if let Err(error) = discord_client + .update_message(channel_id, message.id) + .embeds(Some(&[embed_for_subject_reveal( + &subject_name, + &response, + guessed_subject, + awarders, + &names, + )])) + .await + { + tracing::error!( + ?error, + "couldn't update the message to show who the actual author (the subject) was!" + ); + } + + let reading_time = Duration::from_secs(3); + sleep(reading_time).await; + } + Err(error) => { tracing::error!( ?error, - "couldn't update the message to show who the actual author (the subject) was!" + "couldn't deserialize the sent message to be able to update it with the actual author" ); } - } + }, Err(error) => { tracing::error!( ?error, - "couldn't deserialize the sent message to be able to update it with the actual author" + "couldn't send a message showing how this guess shook out; the points will still count but we have no choice but to move on to the next one" ); + continue; } - }, - Err(error) => { - tracing::error!( - ?error, - "couldn't send a message showing how this guess shook out; the points will still count but we have no choice but to move on to the next one" - ); - continue; } } // endregion: The subject's response @@ -1618,6 +1609,9 @@ pub async fn actor( tracing::warn!(?error, "failed to show a mid-game leaderboard"); } + let reading_time = Duration::from_secs(4); + sleep(reading_time).await; + // endregion: Show the leaderboard (currently) // endregion: Showing this turn's results @@ -1632,7 +1626,7 @@ pub async fn actor( tracing::warn!(?error, "failed to inform of the missing inverse round I have in mind"); } - let reading_time = Duration::from_secs(2); + let reading_time = Duration::from_secs(3); sleep(reading_time).await; if let Err(error) = discord_client @@ -1643,7 +1637,7 @@ pub async fn actor( tracing::warn!(?error, "failed to introduce the endgame placements"); } - let reading_time = Duration::from_secs(2); + let reading_time = Duration::from_secs(3); sleep(reading_time).await; let scores = BTreeMap::from_iter( @@ -1656,7 +1650,7 @@ pub async fn actor( let leaderboard = create_leaderboard(scores); for (score, (placement, players)) in leaderboard { - let drama = Duration::from_secs(5).div_f64(placement as f64); + let drama = Duration::from_secs(7).div_f64(placement as f64); sleep(drama).await; if let Err(error) = discord_client @@ -1675,7 +1669,7 @@ pub async fn actor( } } - let time_focus_on_winners_before_saying_end_of_game = Duration::from_secs(3); + let time_focus_on_winners_before_saying_end_of_game = Duration::from_secs(5); sleep(time_focus_on_winners_before_saying_end_of_game).await; if let Err(error) = discord_client diff --git a/discord-bot/src/main.rs b/discord-bot/src/main.rs index d688a7e..8a299ff 100644 --- a/discord-bot/src/main.rs +++ b/discord-bot/src/main.rs @@ -149,7 +149,7 @@ async fn main() -> Result<(), AppError> { Event::InteractionCreate(interaction_create) => { let InteractionCreate(interaction) = *interaction_create; - tracing::info!(?interaction); + tracing::debug!(?interaction); match &interaction.data { None => { diff --git a/discord-bot/src/topic_picker.rs b/discord-bot/src/topic_picker.rs index b749205..de6193c 100644 --- a/discord-bot/src/topic_picker.rs +++ b/discord-bot/src/topic_picker.rs @@ -59,13 +59,14 @@ pub async fn actor(operator: Operator, mut messages: mpsc::Receiver) { while let Some(message) = messages.recv().await { match message { Message::GiveTopics { - mut existing, + existing, desired, max_p, callback, } => { let difference = (desired as isize) - (existing.len() as isize); - match usize::try_from(difference) { + let topics = match usize::try_from(difference) { + Ok(0) => existing, Ok(_positive) => { let exclude = BTreeSet::from_iter(existing.into_iter().map(|(index, _topic)| index)); @@ -83,22 +84,26 @@ pub async fn actor(operator: Operator, mut messages: mpsc::Receiver) { occurrences[*topic_id] = occurrences[*topic_id].saturating_add(1); } - // if the other end dropped, so what? - let _ = callback.send(chosen_ids_and_topics); + chosen_ids_and_topics } Err(_negative_error) => { + let mut trimmed = existing; + let chop_off = difference.abs() as usize; for _ in 0..chop_off { - let (topic_id, _occurrence) = existing.pop().unwrap(); + let (topic_id, _occurrence) = trimmed.pop().unwrap(); occurrences[topic_id] = occurrences[topic_id].saturating_sub(1); } - // if the other end dropped, so what? - let _ = callback.send(existing); + trimmed } - } + }; + + tracing::info!(?topics, "going to give"); + // if the other end dropped, so what? + let _ = callback.send(topics); tracing::info!(?occurrences, "going to save");