use bitflags::bitflags;
use rusqlite::{
types::{
FromSql,
FromSqlError,
FromSqlResult,
ToSqlOutput,
ValueRef,
},
ToSql,
};
use serenity::{
model::prelude::*,
utils::parse_user_mention,
};
use std::{
borrow::Cow,
num::NonZeroU64,
str::FromStr,
};
struct DatabaseUserId(UserId);
impl FromSql for DatabaseUserId {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
#[allow(clippy::or_fun_call)]
let value = value
.as_i64()
.map(i64::to_ne_bytes)
.map(u64::from_ne_bytes)
.map(NonZeroU64::new)?
.ok_or(FromSqlError::OutOfRange(0))?;
Ok(Self(UserId::from(value)))
}
}
#[derive(Debug, Copy, Clone)]
pub struct TicTacToeGame {
pub board: tic_tac_toe::Board,
pub x_player: TicTacToePlayer,
pub o_player: TicTacToePlayer,
}
impl TicTacToeGame {
pub(super) fn new(x_player: TicTacToePlayer, o_player: TicTacToePlayer) -> Self {
Self {
board: Default::default(),
x_player,
o_player,
}
}
pub fn get_team_turn(&self) -> tic_tac_toe::Team {
self.board.get_turn()
}
pub fn get_player(&self, team: tic_tac_toe::Team) -> TicTacToePlayer {
match team {
tic_tac_toe::Team::X => self.x_player,
tic_tac_toe::Team::O => self.o_player,
}
}
pub fn get_player_turn(&self) -> TicTacToePlayer {
self.get_player(self.get_team_turn())
}
pub fn try_move(&mut self, index: u8, team: tic_tac_toe::Team) -> bool {
if index >= tic_tac_toe::NUM_TILES || self.board.get(index).is_some() {
false
} else {
self.board = self.board.set(index, Some(team));
true
}
}
pub fn get_opponent(&self, player: TicTacToePlayer) -> Option<TicTacToePlayer> {
match (player == self.x_player, player == self.o_player) {
(false, false) => None,
(false, true) => Some(self.x_player),
(true, false) => Some(self.o_player),
(true, true) => Some(player), }
}
pub fn iter_players(&self) -> impl Iterator<Item = TicTacToePlayer> + '_ {
let mut count = 0;
std::iter::from_fn(move || {
let ret = match count {
0 => self.x_player,
1 => self.o_player,
_c => return None,
};
count += 1;
Some(ret)
})
}
}
#[derive(Debug, Clone)]
pub struct TicTacToePlayerParseError(std::num::ParseIntError);
impl std::fmt::Display for TicTacToePlayerParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
"invalid player".fmt(f)
}
}
impl std::error::Error for TicTacToePlayerParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum TicTacToePlayer {
Computer,
User(UserId),
}
impl TicTacToePlayer {
pub fn is_computer(self) -> bool {
matches!(self, Self::Computer)
}
pub fn is_user(self) -> bool {
matches!(self, Self::User(_))
}
pub fn get_user(self) -> Option<UserId> {
match self {
Self::Computer => None,
Self::User(user_id) => Some(user_id),
}
}
}
impl From<TicTacToePlayer> for Cow<'static, str> {
fn from(player: TicTacToePlayer) -> Self {
match player {
TicTacToePlayer::Computer => "computer".into(),
TicTacToePlayer::User(id) => id.to_string().into(),
}
}
}
impl FromStr for TicTacToePlayer {
type Err = TicTacToePlayerParseError;
fn from_str(input: &str) -> Result<Self, Self::Err> {
if input.eq_ignore_ascii_case("computer") {
Ok(Self::Computer)
} else if let Some(user_id) = parse_user_mention(input) {
Ok(Self::User(user_id))
} else {
let user_id: NonZeroU64 = input.parse().map_err(TicTacToePlayerParseError)?;
Ok(Self::User(UserId::from(user_id)))
}
}
}
impl From<UserId> for TicTacToePlayer {
fn from(user_id: UserId) -> Self {
Self::User(user_id)
}
}
impl ToSql for TicTacToePlayer {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
match self {
Self::Computer => Ok(ToSqlOutput::Borrowed(ValueRef::Null)),
Self::User(id) => Ok(ToSqlOutput::Borrowed(ValueRef::Integer(i64::from(*id)))),
}
}
}
impl FromSql for TicTacToePlayer {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
match value {
ValueRef::Integer(int) => {
let int_u64 = u64::from_ne_bytes(int.to_ne_bytes());
#[allow(clippy::or_fun_call)]
let user_id =
UserId::from(NonZeroU64::new(int_u64).ok_or(FromSqlError::OutOfRange(0))?);
Ok(Self::User(user_id))
}
ValueRef::Null => Ok(Self::Computer),
_ => Err(FromSqlError::InvalidType),
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct MaybeGuildString {
pub guild_id: Option<GuildId>,
}
impl From<Option<GuildId>> for MaybeGuildString {
fn from(guild_id: Option<GuildId>) -> Self {
Self { guild_id }
}
}
impl ToSql for MaybeGuildString {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
match self.guild_id {
Some(guild_id) => Ok(ToSqlOutput::from(guild_id.to_string())),
None => Ok(ToSqlOutput::Borrowed(ValueRef::Text(b"empty"))),
}
}
}
impl FromSql for MaybeGuildString {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let text = value.as_str()?;
match text.parse::<NonZeroU64>() {
Ok(guild_id) => Ok(MaybeGuildString {
guild_id: Some(GuildId::from(guild_id)),
}),
Err(e) => {
if text == "empty" {
Ok(MaybeGuildString { guild_id: None })
} else {
Err(FromSqlError::Other(Box::new(e)))
}
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TicTacToeScore {
pub wins: u64,
pub losses: u64,
pub ties: u64,
pub concedes: u64,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TicTacToeTopPlayerScore {
pub score: i64,
pub player: UserId,
pub wins: u64,
pub losses: u64,
pub ties: u64,
pub concedes: u64,
}
impl TicTacToeTopPlayerScore {
pub(crate) fn from_row(row: &rusqlite::Row<'_>) -> rusqlite::Result<Self> {
let score = row.get(0)?;
let player = row.get::<_, DatabaseUserId>(1)?.0;
let wins = row.get(2)?;
let losses = row.get(3)?;
let ties = row.get(4)?;
let concedes = row.get(5)?;
Ok(TicTacToeTopPlayerScore {
score,
player,
wins,
losses,
ties,
concedes,
})
}
}
bitflags! {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct TikTokEmbedFlags: u32 {
const ENABLED = 1 << 0;
const DELETE_LINK = 1 << 1;
}
}
impl ToSql for TikTokEmbedFlags {
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
Ok(self.bits().into())
}
}
impl FromSql for TikTokEmbedFlags {
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
let value = value.as_i64()?;
let value = u32::try_from(value).map_err(|_e| FromSqlError::OutOfRange(value))?;
Self::from_bits(value).ok_or_else(|| FromSqlError::OutOfRange(value.into()))
}
}
impl Default for TikTokEmbedFlags {
fn default() -> Self {
Self::empty()
}
}