Skip to content

Commit

Permalink
#11 - Selectors reorganization
Browse files Browse the repository at this point in the history
  • Loading branch information
Fezzzi committed Jul 24, 2020
1 parent 53ebd8a commit 386ab96
Show file tree
Hide file tree
Showing 7 changed files with 243 additions and 248 deletions.
2 changes: 1 addition & 1 deletion frontend/src/app/components/ProfileRatingGraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
LineSeries,
} from 'react-vis'

import { getRatingHistoryGraphForPlayer } from '../../modules/matches/matches-selectors'
import { getRatingHistoryGraphForPlayer } from '../../modules/players/players-selectors'
import { Box, TextSpan } from '../../../styles/blocks'
import { plotAxisStyle, plotGridStyle, plotLineStyle, plotMainGridStyle } from '../../../styles/svg'

Expand Down
137 changes: 6 additions & 131 deletions frontend/src/app/modules/matches/matches-computations.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
import { DEFAULT_DAYS_STATISTICS, DEFAULT_PLAYERS_STATISTICS } from '../../const/constants'

const didPlayerWin = (playerId, match) => {
const winningTeam = match.team1Won ? match.team1 : match.team2
return Boolean(winningTeam.find(player => player.id === playerId))
export const didPlayerPlayMatch = (playerId, match) => {
const allPlayers = [...match.team1, ...match.team2]
return !!allPlayers.find(player => player.id === playerId)
}

const getTeamMates = (playerId, match) => {
const playerTeam = match.team1.find(player => player.id === playerId) ? match.team1 : match.team2
return playerTeam.filter(player => player.id !== playerId)
}

const getOpponents = (playerId, match) => {
return match.team1.find(player => player.id === playerId) ? match.team2 : match.team1
export const didPlayerWin = (playerId, { team1Won, team1, team2 }) => {
const winningTeam = team1Won ? team1 : team2
return Boolean(winningTeam.find(player => player.id === playerId))
}

export const generateMatchRatingChanges = (playerId, playerMatches) => playerMatches.map(match => {
Expand Down Expand Up @@ -74,122 +68,3 @@ export const computeLongestWinStreak = (playerId, playerMatches) => {
}
}, initialState).longest
}

export const computeWinRatio = (playerId, playerMatches) => {
const wonMatchesCount = playerMatches.filter(match => didPlayerWin(playerId, match)).length
return (playerMatches.length > 0) ? (wonMatchesCount / playerMatches.length) : 0
}

export const computeWins = (playerId, playerMatches) =>
playerMatches.filter(match => didPlayerWin(playerId, match)).length

const findWithComparator = (comparator, arr, getField, baseScore) =>
arr.reduce((acc, node) =>
comparator(acc.score, getField(node))
? acc
: { value: node.value, score: getField(node) }
, { score: baseScore })

export const findMin = (arr, getField = node => node.score) =>
findWithComparator((e1, e2) => e1 <= e2, arr, getField, Infinity)

export const findMax = (arr, getField = node => node.score) =>
findWithComparator((e1, e2) => e1 >= e2, arr, getField, -Infinity)

export const computeDays = matchChanges => {
const daysMap = new Map()

for (const match of matchChanges) {
const day = match.date.toLocaleDateString()
const score = Number(match.ratingChangeString)
daysMap.set(day, daysMap.has(day)
? {
...daysMap.get(day),
score: daysMap.get(day).score + score,
}
: {
value: day,
score,
})
}
return matchChanges.length
? Array.from(daysMap.values())
: DEFAULT_DAYS_STATISTICS
}

export const computeTeammateStats = (playerId, playerMatches) =>
computePlayerStats(playerId, playerMatches, getTeamMates) || {}

export const computeOpponentsStats = (playerId, playerMatches) =>
computePlayerStats(playerId, playerMatches, getOpponents) || {}

const computePlayerStats = (playerId, playerMatches, playersProvider) => {
const playersMap = new Map()

for (const match of playerMatches) {
const players = playersProvider(playerId, match)
players.forEach(player =>
playersMap.set(player.id, playersMap.has(player.id)
? {
...playersMap.get(player.id),
matches: playersMap.get(player.id)['matches'] + 1,
wins: playersMap.get(player.id)['wins'] += Number(didPlayerWin(playerId, match)),
losses: playersMap.get(player.id)['losses'] += Number(!didPlayerWin(playerId, match)),
}
: {
value: player,
matches: 1,
wins: Number(didPlayerWin(playerId, match)),
losses: Number(!didPlayerWin(playerId, match)),
}))
}
return playerMatches.length
? Array.from(playersMap.values())
: DEFAULT_PLAYERS_STATISTICS
}

export const computeKingStreakDuration = (matchesLast, playersLast) => {
if (playersLast.length < 1) {
return null
}

const playersMap = playersLast.reduce((map, player) => {
map[player.id] = player.rating
return map
}, new Map())

const kingId = playersLast[0].id
let matchFound = false
for (const match of matchesLast) {
const players = [...match.team1, ...match.team2]

// #1 recomptute king's rating before in case he played the match
const kingPlayer = players.find(player => player.id === kingId)
let kingWon = false
if (kingPlayer) {
kingWon = playersMap[kingId] > kingPlayer.matchRating
playersMap[kingId] = kingPlayer.matchRating
}

// #2 check whether none of the other players beat the king
for (const player of players) {
playersMap[player.id] = player.matchRating
matchFound |= (player.id !== kingId && player.matchRating > playersMap[kingId])
}

// #3 check if king was first before winning
if (kingPlayer && kingWon && !matchFound) {
for (const key in playersMap) {
matchFound |= (playersMap[key] > playersMap[kingId])
}
}

if (matchFound) {
return {
id: kingId,
since: match.date,
}
}
}
return matchesLast[matchesLast.length - 1]
}
60 changes: 2 additions & 58 deletions frontend/src/app/modules/matches/matches-selectors.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
import { createSelector } from 'reselect'
import {
computeLongestWinStreak,
computeWinRatio,
computeWins,
generateMatchRatingChanges,
plotRatingHistory,
findMax,
findMin,
computeDays,
computeTeammateStats,
computeOpponentsStats,
computeKingStreakDuration,
} from './matches-computations'
import { didPlayerPlayMatch } from './matches-computations'

const fillPlayers = (team, state) => team.map(emptyPlayer => {
const player = state.players.find(player => player.id === emptyPlayer.id)
Expand All @@ -27,57 +15,13 @@ const getMatches = state => state.matches.map(match => ({
team2: fillPlayers(match.team2, state),
}))

export const didPlayerPlayMatch = (playerId, match) => {
const allPlayers = [...match.team1, ...match.team2]
return !!allPlayers.find(player => player.id === playerId)
}

export const getLastMatches = createSelector(
getMatches,
matches => matches.sort((match1, match2) => match2.date - match1.date),
)

const getLastMatchesForPlayer = createSelector(
export const getLastMatchesForPlayer = createSelector(
getLastMatches,
(state, playerId) => playerId,
(matches, playerId) => matches.filter(match => didPlayerPlayMatch(playerId, match)),
)

const generateStatisticsForPlayer = (playerId, playerMatches) => {
const matchChanges = generateMatchRatingChanges(playerId, playerMatches)
const days = computeDays(matchChanges)
const teammateStats = computeTeammateStats(playerId, playerMatches)
const opponentStats = computeOpponentsStats(playerId, playerMatches)

return {
matchChanges,
longestStreak: computeLongestWinStreak(playerId, playerMatches),
winRatio: computeWinRatio(playerId, playerMatches),
totalMatches: playerMatches.length,
wins: computeWins(playerId, playerMatches),
bestDay: findMax(days),
worstDay: findMin(days),

mostFrequentTeammate: findMax(teammateStats, teammate => teammate.matches),
leastFrequentTeammate: findMin(teammateStats, teammate => teammate.matches),
mostSuccessTeammate: findMax(teammateStats, teammate => teammate.wins),
leastSuccessTeammate: findMax(teammateStats, teammate => teammate.losses),

mostFrequentOpponent: findMax(opponentStats, opponent => opponent.matches),
leastFrequentOpponent: findMin(opponentStats, opponent => opponent.matches),
mostSuccessOpponent: findMax(opponentStats, opponent => opponent.wins),
leastSuccessOpponent: findMax(opponentStats, opponent => opponent.losses),
}
}

export const getStatisticsForPlayer = createSelector(
getLastMatchesForPlayer,
(state, playerId) => playerId,
(playerMatches, playerId) => generateStatisticsForPlayer(playerId, playerMatches),
)

export const getRatingHistoryGraphForPlayer = createSelector(
getLastMatchesForPlayer,
(state, playerId) => state.players.find(player => player.id === playerId),
(playerMatches, player) => plotRatingHistory(playerMatches, player.id, player.initialRating),
)
97 changes: 97 additions & 0 deletions frontend/src/app/modules/players/players-computations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import * as Filters from '../../const/leaderboard-filters'

import { computeWinRatio } from './statistics-computations'
import { didPlayerPlayMatch, computeLongestWinStreak } from '../matches/matches-computations'

const getPlayerCriteriaPoints = (player, matchesLast, criteria) => {
const playerId = Number(player.id)
const playerMatches = matchesLast.filter(match => didPlayerPlayMatch(playerId, match))
const winRatio = computeWinRatio(playerId, playerMatches)
switch (criteria) {
case Filters.criteriaTypes.Wins:
return Math.round(playerMatches.length * winRatio)
case Filters.criteriaTypes.Ratio:
return (winRatio * 100).toFixed(2)
case Filters.criteriaTypes.Streak:
return computeLongestWinStreak(playerId, playerMatches)
case Filters.criteriaTypes.Matches:
return playerMatches.length
default:
return player.rating
}
}

export const sortTopPlayers = (players, matches, filters) => {
const order = filters.order === Filters.orderTypes.ASC ? -1 : 1
const minDate = new Date()
minDate.setDate(minDate.getDate() - filters.timespan)
const matchesLast = matches
.filter(match => filters.timespan === Filters.timespanTypes.AllTime || match.date > minDate)
.sort((match1, match2) => match2.date - match1.date)

const playersWithStatistics = players.map(player => ({
...player,
criteriaPoints: getPlayerCriteriaPoints(player, matchesLast, filters.criteria),
}))

return playersWithStatistics.sort((u1, u2) => (u2.criteriaPoints - u1.criteriaPoints) * order)
}

export const getRatingStatisticsForPlayer = (players, playerId) => {
const index = players.findIndex(player => player.id === playerId)
return {
ranking: index + 1,
scoreToNextRank: index > 0
? 1 + (players[index - 1].rating - players[index].rating)
: 0,
scoreToPrevRank: index < players.length - 1
? 1 + (players[index].rating - players[index + 1].rating)
: 0,
}
}

export const computeKingStreakDuration = (lastMatches, lastPlayers) => {
if (lastPlayers.length < 1) {
return null
}

const playersMap = lastPlayers.reduce((map, player) => {
map[player.id] = player.rating
return map
}, new Map())

const kingId = lastPlayers[0].id
let matchFound = false
for (const match of lastMatches) {
const players = [...match.team1, ...match.team2]

// #1 recomptute king's rating before in case he played the match
const kingPlayer = players.find(player => player.id === kingId)
let kingWon = false
if (kingPlayer) {
kingWon = playersMap[kingId] > kingPlayer.matchRating
playersMap[kingId] = kingPlayer.matchRating
}

// #2 check whether none of the other players beat the king
for (const player of players) {
playersMap[player.id] = player.matchRating
matchFound |= (player.id !== kingId && player.matchRating > playersMap[kingId])
}

// #3 check if king was first before winning
if (kingPlayer && kingWon && !matchFound) {
for (const key in playersMap) {
matchFound |= (playersMap[key] > playersMap[kingId])
}
}

if (matchFound) {
return {
id: kingId,
since: match.date,
}
}
}
return lastMatches[lastMatches.length - 1]
}
Loading

0 comments on commit 386ab96

Please sign in to comment.