import { EmbedFieldData , MessageEmbed , Snowflake , User } from 'discord.js' ; import mongoose , { Document } from 'mongoose' ; import { Maybe } from '../common/types' ; import { C4LeaderboardModel , TTTLeaderboardModel } from '../schemas/games.schema' ; import { GuildService } from './guild.service' ; import { LoggerService } from './logger.service' ; import { StorageService } from './storage.service' ; interface IUserOverallEntry { player : User ; numWins : number ; numLoses : number ; numTies : number ; } export interface IGameLeaderBoardEntry { userId : Snowflake ; guildId : Snowflake ; games : IGame [ ] ; } export type GameLeaderBoardDocument = IGameLeaderBoardEntry & Document ; export interface IGame { opponent : Snowflake ; result : GameResult ; } export type GameDocument = IGame & Document ; export enum GameResult { Won = 1 , Lost , Tie } export enum GameType { TicTacToe = 1 , ConnectFour } type GameCollectionTypes = 'tttLeaderboard' | 'connectFourLeaderboard' ; export class GameLeaderboardService { public gameAliases : Record < GameType , string [ ] > = { [ GameType . TicTacToe ] : [ 'ttt' , 'tictactoe' ] , [ GameType . ConnectFour ] : [ 'c4' , 'connectfour' , 'connect-four' ] , } ; private _gameEnumToString : Record < GameType , string > = { [ GameType . TicTacToe ] : 'Tic-Tac-Toe' , [ GameType . ConnectFour ] : 'Connect 4' , } ; private _gameEnumToCollection : Record < GameType , mongoose . Model < GameLeaderBoardDocument > > = { [ GameType . TicTacToe ] : this . _getCollection ( 'tttLeaderboard' ) , [ GameType . ConnectFour ] : this . _getCollection ( 'connectFourLeaderboard' ) , } ; constructor ( private _storageService , private _guildService , private _loggerService ) { } public async updateLeaderboard ( user , game , gameData ) { const leaderboard = this . _gameEnumToCollection [ game ] ; if ( ! leaderboard ) { this . _loggerService . error ( `Could not get leaderboard for ${ game }` ) ; return ; } let userDoc = await leaderboard . findOne ( { userId : user . id , guildId : this . _guildService . get ( ) . id , } ) ; if ( ! userDoc ) { await leaderboard . create ( { userId : user . id , guildId : this . _guildService . get ( ) . id , games : [ ] , } ) ; userDoc = await leaderboard . findOne ( { userId : user . id , guildId : this . _guildService . get ( ) . id , } ) ; } if ( ! userDoc ) { this . _loggerService . error ( `Failed to make or find entry for user with id ${ user . id } in leaderboard ${ leaderboard } for game ${ game }` ) ; return ; } await leaderboard . updateOne ( { userId : user . id } , { $push : { games : { $each : [ gameData ] , $position : 0 } } } ) ; } private _getCollection ( gameType ) { return gameType === 'tttLeaderboard' ? TTTLeaderboardModel : C4LeaderboardModel ; } public async createOverallLeaderboardEmbed ( user , game ) { const leaderboard = this . _gameEnumToCollection [ game ] ; if ( ! leaderboard ) { this . _loggerService . error ( `Could not get leaderboard for ${ game }` ) ; return 'Unable to get the leaderboards at this time' ; } const collectionData = await this . _parseCollectionData ( leaderboard ) ; const embed = new MessageEmbed ( ) ; embed . setTitle ( `${ this . _gameEnumToString [ game ] } Leaderboard` ) ; const userFieldData = this . _createPlayerFieldData ( user , collectionData ) ; if ( userFieldData ) { const { name , value } = userFieldData ; embed . addField ( name , value , false ) ; } collectionData . slice ( 0 , 24 ) . forEach ( ( e , i ) => embed . addField ( `${ i + 1 }. ${ e . player . username }` , `*Wins:* ${ e . numWins }\n` + `*Loses:* ${ e . numLoses }\n` + `*Ties:* ${ e . numTies }` , true ) ) ; return embed ; } public async createPlayerLeaderboardEmbed ( user , game ) { const leaderboard = this . _gameEnumToCollection [ game ] ; if ( ! leaderboard ) { this . _loggerService . error ( `Could not get leaderboard for ${ game }` ) ; return 'Unable to get the leaderboards at this time' ; } const entries = await this . _parseCollectionData ( leaderboard ) ; const userFieldData = this . _createPlayerFieldData ( user , entries ) ; if ( ! userFieldData ) { return null ; } const { name , value } = userFieldData ; const embed = new MessageEmbed ( ) ; embed . setTitle ( `${ this . _gameEnumToString [ game ] } Leaderboard` ) ; embed . addField ( name , value ) ; return embed ; } private _createPlayerFieldData ( user , entries ) { const [ userEntry ] = entries . filter ( ( e ) => e . player === user ) ; if ( ! userEntry ) { return null ; } const userRank = entries . indexOf ( userEntry ) ; return { name : `${ userRank + 1 }. ${ userEntry . player . username } (You)` , value : `*Wins:* ${ userEntry . numWins }\n` + `*Loses:* ${ userEntry . numLoses }\n` + `*Ties:* ${ userEntry . numTies }` , } ; } private async _parseCollectionData ( leaderboard ) { const res = await leaderboard . find ( { guildId : this . _guildService . get ( ) . id } ) ; return res . reduce ( ( acc , doc ) => { const stats = this . _getOverallStats ( doc ) ; if ( stats ) { acc . push ( stats ) ; } return acc ; } , [ ] ) . sort ( ( a , b ) => b . numWins - a . numWins ) ; } public async createMatchupLeaderboardEmbed ( userOne , userTwo , gameType ) { const leaderboard = this . _gameEnumToCollection [ gameType ] ; if ( ! mongoose . connection . readyState ) { this . _loggerService . error ( `Could not get leaderboard for ${ gameType }` ) ; return 'Unable to get the leaderboards at this time' ; } const entries = await leaderboard . find ( { guildId : this . _guildService . get ( ) . id } ) ; const [ userOneEntry ] = entries . filter ( ( e ) => e . userId === userOne . id ) ; if ( ! userOneEntry ) { return `User \`${ userOne . username }\` not found` ; } const matchupGames = userOneEntry . games . filter ( ( game ) => game . opponent === userTwo . id ) ; let userOneWins = 0 ; let userTwoWins = 0 ; let ties = 0 ; matchupGames . forEach ( ( game ) => { switch ( game . result ) { case GameResult . Won : userOneWins ++ ; break ; case GameResult . Lost : userTwoWins ++ ; break ; case GameResult . Tie : ties ++ ; break ; } } ) ; const embed = new MessageEmbed ( ) ; embed . setTitle ( `${ this . _gameEnumToString [ gameType ] } Matchup` ) ; embed . addField ( `${ userOne . username } vs ${ userTwo . username }` , `__Wins:__ ${ userOneWins } - ${ userTwoWins }\n` + `__Loses:__ ${ userTwoWins } - ${ userOneWins }\n` + `__Ties:__ ${ ties }` ) ; return embed ; } private _getOverallStats ( entry ) { const user = this . _guildService . get ( ) . members . cache . get ( entry . userId ) ?. user ; if ( ! user ) { return null ; } let wins = 0 ; let loses = 0 ; let ties = 0 ; entry . games . forEach ( ( game ) => { if ( game . result === GameResult . Won ) { wins ++ ; } if ( game . result === GameResult . Lost ) { loses ++ ; } if ( game . result === GameResult . Tie ) { ties ++ ; } } ) ; return { player : user , numWins : wins , numLoses : loses , numTies : ties , } ; } }