[
  {
    "path": ".gitignore",
    "content": "/target\n.env\nmain.exe\nmain.pdb"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"rust\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html\n\n[dependencies]\ntokio = { version = \"1.21.2\", features = [\"macros\", \"rt-multi-thread\"] }\nanyhow = \"1.0.69\"\nrand = \"0.8.5\"\n\n[dependencies.serenity]\ndefault-features = false\nfeatures = [\"builder\", \"cache\", \"client\", \"gateway\", \"http\", \"model\", \"utils\", \"rustls_backend\"]\nversion = \"0.11\"\n\n[[bin]]\nname = \"rust\"\npath = \"src/main.rs\"\n"
  },
  {
    "path": "README.md",
    "content": "# Whac-A-Hole\n\nA blazingly fast implementation of the classic **arcade game** What-A-Hole inside Discord, made in 12 hours, with no experience in Rust (🚀🚀🚀). 🚀!\n\n# 🚀 Installation\n\n- 🚀) Install [Rust](https://www.rust-lang.org/tools/install)\n- 🚀🚀) Clone/download the repository.\n- 🚀🚀🚀) On line **256** change the empty string (`\"\"`) to your [Discord token](https://discord.com/developers/applications).\n- 🚀🚀🚀🚀) Run `cargo run --release` inside the repository folder.\n- 🚀🚀🚀🚀🚀) Go to a server where the bot is in, and run `/whac-a-hole`!\n\n# 🚀 Contributions\n> Contributions 🚀 are 🚀 appreciated 🚀, 🚀 however, 🚀 do 🚀 not 🚀 heavily 🚀 modify 🚀 the 🚀 code 🚀 structure 🚀 (modify 🚀 the 🚀 game's 🚀 logic 🚀 or 🚀 rules 🚀)\n\n# 🚀 Known issues\n\n- 🚀 Anyone can interact with anyone's game.\n- 🚀 The game does not stop getting input when it stops, if the user clicks the buttons and the bot tries to edit the message with the loss, the game will continue with 0 score.\n- 🚀 nothing else, rust!!! 🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀\n\n# Credits\n🚀 [YouTube](https://youtube.com/FaceDevStuff) here ;)\n"
  },
  {
    "path": "src/main.rs",
    "content": "use anyhow::Result;\n\nuse rand::thread_rng;\nuse rand::Rng;\n\nuse serenity::model::prelude::interaction::message_component::MessageComponentInteraction;\n\nuse std::collections::HashMap;\nuse std::sync::Arc;\nuse std::time::{Duration, Instant};\n\nuse serenity::async_trait;\nuse serenity::builder::{\n    CreateActionRow, CreateApplicationCommand, CreateButton, CreateComponents,\n};\nuse serenity::model::application::command::Command;\nuse serenity::model::application::component::ButtonStyle;\nuse serenity::model::application::interaction::{Interaction, InteractionResponseType};\nuse serenity::model::gateway::Ready;\nuse serenity::model::prelude::ReactionType;\nuse serenity::prelude::*;\n\nstatic SCORE_TIMEOUT: u64 = 2000; // milliseconds\n\nstruct Handler {\n    game_map: Arc<RwLock<HashMap<String, (u32, Instant)>>>,\n    // active_map: Arc<RwLock<HashMap<String, String, u64>>>,\n}\n\npub fn generate_map(components: &mut CreateComponents) {\n    let mut seed = thread_rng();\n    let random_x = seed.gen_range(0..5);\n    let random_y = seed.gen_range(0..5);\n\n    for x in 0..5 {\n        let mut action_row = CreateActionRow::default();\n\n        for y in 0..5 {\n            if x == random_x && y == random_y {\n                action_row.add_button(create_button(ReactionType::Unicode(\"🦑\".to_string())));\n            } else {\n                action_row.add_button(create_button(ReactionType::Unicode(\"⬜\".to_string())));\n            }\n        }\n\n        components.add_action_row(action_row);\n    }\n}\n\nasync fn remove_item(game_map: &mut Arc<RwLock<HashMap<String, (u32, Instant)>>>, command: &MessageComponentInteraction) {\n    game_map.write().await.remove(&command.user.id.to_string());\n}\n\n// async fn exists_item(game_map: &mut tokio::sync::RwLockWriteGuard<'_, HashMap<std::string::String, (u32, std::time::Instant)>>, command: &MessageComponentInteraction) {\n//     game_map.get(&command.user.id.to_string());\n// }\n\nasync fn get_item(\n    game_map: &Arc<tokio::sync::RwLock<HashMap<std::string::String, (u32, std::time::Instant)>>>,\n    user_id: &str,\n) -> Option<(u32, Instant)> {\n    let game_map_guard = game_map.read().await;\n\n    game_map_guard.get(user_id).cloned()\n}\n\nfn increase_score(game_map: &mut HashMap<String, (u32, Instant)>, user_id: &str) {\n    let score = game_map\n        .entry(user_id.to_string())\n        .or_insert((0, Instant::now()));\n    score.0 += 1;\n    score.1 = Instant::now();\n}\n\n// define a function to create a single button\nfn create_button(emoji: ReactionType) -> CreateButton {\n    let mut button = CreateButton::default();\n    button.style(ButtonStyle::Secondary);\n    button.label(\"\\u{200B}\");\n    button.emoji(emoji.clone());\n    button.custom_id(format!(\n        \"{}_{}\",\n        thread_rng().gen::<u64>(),\n        if &emoji == &ReactionType::Unicode(\"🦑\".to_string()) {\n            \"_win\"\n        } else {\n            \"\"\n        }\n    ));\n    button\n}\n\nasync fn check_for_game_end(\n    ctx: &Context,\n    interaction: &Interaction,\n    game_map: &Arc<tokio::sync::RwLock<HashMap<String, (u32, std::time::Instant)>>>,\n    command: &MessageComponentInteraction,\n) {\n    let game_map_inner = Arc::clone(game_map);\n    let command_clone = command.clone();\n    let ctx_clone = ctx.clone();\n    let interaction_clone = interaction.clone();\n    let mut game_map_cloned = game_map.clone();\n    let command_cloned = command.clone();\n\n    tokio::spawn(async move {\n        tokio::time::sleep(Duration::from_secs(2)).await;\n\n        let score = get_item(&game_map_inner, &command_clone.user.id.to_string()).await;\n\n        if let Some((final_score, timestamp)) = score {\n            let difference = Instant::now().duration_since(timestamp);\n\n            if difference.as_millis() > SCORE_TIMEOUT.into() {\n                if let Err(reason) = interaction_clone\n                    .message_component()\n                    .unwrap()\n                    .message\n                    .edit(ctx_clone.http.clone(), |message| {\n                        message.content(format!(\n                            \"💀 Too slow! No score within `{:?}` ms! Score: **{:?}** :star:\",\n                            SCORE_TIMEOUT, final_score\n                        ));\n                        message.components(|components| components);\n                        message\n                    })\n                    .await\n                {\n                    println!(\"{:?}\", reason)\n                } else {\n                    remove_item(&mut game_map_cloned, &command_cloned).await;\n                };\n            }\n        }\n    });\n}\n\nasync fn interaction_handler(\n    ctx: Context,\n    interaction: Interaction,\n    handler: Arc<&Handler>,\n) -> Result<()> {\n    match interaction.clone() {\n        Interaction::MessageComponent(command) => {\n            // let message_id = interaction.id();\n\n            // let mut game_map = handler.game_map.write().await;\n            // if let data_exists = exists_item(&mut game_map, &command) {\n            //     //\n            // } else {\n            //     return Ok(());\n            // }\n            command\n                .create_interaction_response(&ctx.http, |response| {\n                    response.kind(InteractionResponseType::DeferredUpdateMessage)\n                })\n                .await?;\n\n            let custom_id = &command.data.custom_id;\n            if custom_id.ends_with(\"_win\") {\n                let mut game_map = handler.game_map.write().await;\n                increase_score(&mut game_map, &command.user.id.to_string());\n            } else {\n                // no need to regenerate\n                return Ok(());\n            }\n\n            let game_map_tokio = handler.game_map.clone();\n\n            let game_map = Arc::clone(&handler.game_map);\n\n            check_for_game_end(&ctx, &interaction, &game_map, &command).await;\n\n            let data = get_item(&game_map_tokio, &command.user.id.to_string()).await;\n\n            interaction\n                .message_component()\n                .unwrap()\n                .message\n                .edit(ctx.http.clone(), |message| {\n                    if let Some(score) = data {\n                        message.content(format!(\"Your score: **{:?}** :star:\", score.0));\n                    }\n\n                    message.components(|components| {\n                        generate_map(components);\n                        components\n                    });\n                    message\n                })\n                .await\n                .unwrap();\n        }\n        Interaction::ApplicationCommand(command) => {\n            // modify your existing code to create a 5x5 button grid using the create_button_row function\n\n            if command.data.name == \"whac-a-hole\" {\n                command\n                    .create_interaction_response(&ctx.http, |response| {\n                        response\n                            .kind(InteractionResponseType::ChannelMessageWithSource)\n                            .interaction_response_data(|message| {\n                                message.components(|components| {\n                                    generate_map(components);\n                                    components\n                                });\n                                message\n                            })\n                    })\n                    .await?;\n                // let message =\n                //     ApplicationCommandInteraction::get_interaction_response(&command, &ctx.http)\n                //         .await\n                //         .unwrap();\n\n                // let active_map = &mut handler.active_map.read().await;\n\n                // active_map.insert(command.user.id.to_string(), message.id.as_u64());\n            }\n        }\n        _ => {}\n    }\n    Ok(())\n}\n\n#[async_trait]\nimpl EventHandler for Handler {\n    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {\n        let handler = Arc::new(self.clone());\n        let result = interaction_handler(ctx, interaction, handler).await;\n        if let Err(e) = result {\n            println!(\"Error running interaction_handler: {e:?}\");\n        }\n    }\n\n    async fn ready(&self, ctx: Context, ready: Ready) {\n        println!(\"{} is connected!\", ready.user.name);\n\n        if let Err(reason) = Command::create_global_application_command(\n            &ctx.http,\n            |command: &mut CreateApplicationCommand| {\n                command\n                    .name(\"whac-a-hole\")\n                    .description(\"Play Whac-A-Hole in Discord!\")\n            },\n        )\n        .await\n        {\n            println!(\"Caught error: {:?}\", reason)\n        };\n    }\n}\n\n#[tokio::main]\nasync fn main() -> Result<()> {\n    let token = \"\";\n\n    let intents = GatewayIntents::GUILD_MESSAGES\n        | GatewayIntents::DIRECT_MESSAGES\n        | GatewayIntents::MESSAGE_CONTENT;\n\n    let mut client = Client::builder(&token, intents)\n        .event_handler(Handler {\n            game_map: Arc::new(RwLock::new(HashMap::new())),\n            // active_map: Arc::new(RwLock::new(HashMap<std::string::String)),\n        })\n        .await\n        .expect(\"Err creating client\");\n\n    if let Err(why) = client.start().await {\n        println!(\"An error occurred while running the client: {:?}\", why);\n    }\n\n    Ok(())\n}\n"
  }
]