pikadick/commands/tic_tac_toe/
play.rs1use crate::{
2 checks::ENABLED_CHECK,
3 database::{
4 model::TicTacToePlayer,
5 TicTacToeCreateGameError,
6 },
7 ClientDataKey,
8};
9use serenity::{
10 builder::{
11 CreateAttachment,
12 CreateMessage,
13 },
14 client::Context,
15 framework::standard::{
16 macros::command,
17 Args,
18 CommandResult,
19 },
20 model::prelude::*,
21};
22use tracing::error;
23
24#[command]
25#[description("Start a game of Tic-Tac-Toe")]
26#[usage("<computer OR @user, X OR O>")]
27#[example("computer X")]
28#[min_args(2)]
29#[max_args(2)]
30#[checks(Enabled)]
31#[bucket("default")]
32pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
33 let data_lock = ctx.data.read().await;
34 let client_data = data_lock
35 .get::<ClientDataKey>()
36 .expect("missing client data");
37 let tic_tac_toe_data = client_data.tic_tac_toe_data.clone();
38 let db = client_data.db.clone();
39 drop(data_lock);
40
41 let opponent: TicTacToePlayer = match args.trimmed().single() {
42 Ok(player) => player,
43 Err(e) => {
44 let response = format!(
45 "Invalid opponent. Choose 'computer' or '@user'. Error: {}",
46 e
47 );
48 msg.channel_id.say(&ctx.http, response).await?;
49 return Ok(());
50 }
51 };
52
53 let author_team: tic_tac_toe::Team = match args.trimmed().single() {
54 Ok(team) => team,
55 Err(e) => {
56 let response = format!("Invalid team. Choose 'X' or 'O'. Error: {}", e);
57 msg.channel_id.say(&ctx.http, response).await?;
58 return Ok(());
59 }
60 };
61
62 let author_id = msg.author.id;
63 let guild_id = msg.guild_id;
64
65 let game = match db
66 .create_tic_tac_toe_game(guild_id.into(), author_id.into(), author_team, opponent)
67 .await
68 {
69 Ok(game) => game,
70 Err(TicTacToeCreateGameError::AuthorInGame) => {
71 let response = "Finish your current game in this server before starting a new one. Use `tic-tac-toe concede` to end your current game.";
72 msg.channel_id.say(&ctx.http, response).await?;
73 return Ok(());
74 }
75 Err(TicTacToeCreateGameError::OpponentInGame) => {
76 let response = "Your opponent is currently in another game in this server. Wait for them to finish.";
77 msg.channel_id.say(&ctx.http, response).await?;
78 return Ok(());
79 }
80 Err(TicTacToeCreateGameError::Database(e)) => {
81 error!("{:?}", e);
82 msg.channel_id.say(&ctx.http, "database error").await?;
83 return Ok(());
84 }
85 };
86
87 let game_board = game.board;
88 let user = if let TicTacToePlayer::User(opponent_id) = opponent {
89 if author_team == tic_tac_toe::Team::X {
91 author_id
92 } else {
93 opponent_id
94 }
95 } else {
96 author_id
100 };
101
102 let file = match tic_tac_toe_data
103 .renderer
104 .render_board_async(game_board)
105 .await
106 {
107 Ok(file) => CreateAttachment::bytes(file, format!("{}.png", game_board.encode_u16())),
108 Err(error) => {
109 error!("Failed to render Tic-Tac-Toe board: {error}");
110 msg.channel_id
111 .say(
112 &ctx.http,
113 format!("Failed to render Tic-Tac-Toe board: {error}"),
114 )
115 .await?;
116 return Ok(());
117 }
118 };
119
120 let message_builder = CreateMessage::new()
121 .content(format!("Game created! Your turn {}", user.mention()))
122 .add_file(file);
123 msg.channel_id
124 .send_message(&ctx.http, message_builder)
125 .await?;
126
127 Ok(())
128}