You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
9.3 KiB
325 lines
9.3 KiB
package net.pokeranalytics.android.calculus
|
|
|
|
import android.content.Context
|
|
import net.pokeranalytics.android.R
|
|
import net.pokeranalytics.android.exceptions.FormattingException
|
|
import net.pokeranalytics.android.exceptions.PAIllegalStateException
|
|
import net.pokeranalytics.android.ui.view.RowRepresentable
|
|
import net.pokeranalytics.android.ui.view.RowViewType
|
|
import net.pokeranalytics.android.util.NULL_TEXT
|
|
import net.pokeranalytics.android.util.TextFormat
|
|
import net.pokeranalytics.android.util.enumerations.IntIdentifiable
|
|
import net.pokeranalytics.android.util.enumerations.IntSearchable
|
|
import net.pokeranalytics.android.util.extensions.formatted
|
|
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
|
|
import net.pokeranalytics.android.util.extensions.toCurrency
|
|
import java.util.*
|
|
import kotlin.math.exp
|
|
import kotlin.math.pow
|
|
|
|
class StatFormattingException(message: String) : Exception(message)
|
|
|
|
/**
|
|
* An enum representing all the types of Session statistics
|
|
*/
|
|
enum class Stat(override var uniqueIdentifier: Int) : IntIdentifiable, RowRepresentable {
|
|
|
|
NET_RESULT(1),
|
|
BB_NET_RESULT(2),
|
|
HOURLY_RATE(3),
|
|
AVERAGE(4),
|
|
NUMBER_OF_SETS(5),
|
|
NUMBER_OF_GAMES(6),
|
|
HOURLY_DURATION(7),
|
|
AVERAGE_HOURLY_DURATION(8),
|
|
NET_BB_PER_100_HANDS(9),
|
|
HOURLY_RATE_BB(10),
|
|
AVERAGE_NET_BB(11),
|
|
WIN_RATIO(12),
|
|
AVERAGE_BUYIN(13),
|
|
ROI(14),
|
|
STANDARD_DEVIATION(15),
|
|
STANDARD_DEVIATION_HOURLY(16),
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS(17),
|
|
HANDS_PLAYED(18),
|
|
LOCATIONS_PLAYED(19),
|
|
LONGEST_STREAKS(20),
|
|
MAXIMUM_NETRESULT(21),
|
|
MINIMUM_NETRESULT(22),
|
|
MAXIMUM_DURATION(23),
|
|
DAYS_PLAYED(24),
|
|
WINNING_SESSION_COUNT(25),
|
|
BB_SESSION_COUNT(26),
|
|
TOTAL_BUYIN(27),
|
|
RISK_OF_RUIN(28),
|
|
STANDARD_DEVIATION_BB(29),
|
|
;
|
|
|
|
companion object : IntSearchable<Stat> {
|
|
|
|
override fun valuesInternal(): Array<Stat> {
|
|
return values()
|
|
}
|
|
|
|
val userSelectableList: List<Stat>
|
|
get() {
|
|
return values().filter { it.canBeUserSelected }
|
|
}
|
|
|
|
val evolutionValuesList: List<Stat>
|
|
get() {
|
|
return values().filter { it.hasProgressValues }
|
|
}
|
|
|
|
fun returnOnInvestment(netResult: Double, buyin: Double): Double? {
|
|
if (buyin == 0.0) {
|
|
return null
|
|
}
|
|
return netResult / buyin
|
|
}
|
|
|
|
fun netBBPer100Hands(netBB: Double, numberOfHands: Double): Double? {
|
|
if (numberOfHands == 0.0) {
|
|
return null
|
|
}
|
|
return netBB / numberOfHands * 100
|
|
}
|
|
|
|
fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double): Double? {
|
|
|
|
if (bankrollValue <= 0.0) {
|
|
return null
|
|
}
|
|
|
|
val numerator = -2 * hourlyRate * bankrollValue
|
|
val denominator = hourlyStandardDeviation.pow(2.0)
|
|
val ratio = numerator / denominator
|
|
return exp(ratio)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
override val resId: Int?
|
|
get() {
|
|
return when (this) {
|
|
NET_RESULT -> R.string.net_result
|
|
BB_NET_RESULT -> R.string.total_net_result_bb_
|
|
HOURLY_RATE -> R.string.average_hour_rate
|
|
AVERAGE -> R.string.average
|
|
NUMBER_OF_SETS -> R.string.number_of_sessions
|
|
NUMBER_OF_GAMES -> R.string.number_of_records
|
|
HOURLY_DURATION -> R.string.duration
|
|
AVERAGE_HOURLY_DURATION -> R.string.average_hours_played
|
|
NET_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
|
|
HOURLY_RATE_BB -> R.string.average_hour_rate_bb_
|
|
AVERAGE_NET_BB -> R.string.average_net_result_bb_
|
|
WIN_RATIO -> R.string.win_ratio
|
|
AVERAGE_BUYIN -> R.string.average_buyin
|
|
ROI -> R.string.tournament_roi
|
|
STANDARD_DEVIATION -> R.string.standard_deviation
|
|
STANDARD_DEVIATION_HOURLY -> R.string.standard_deviation_per_hour
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.standard_deviation_bb_per_100_hands
|
|
STANDARD_DEVIATION_BB -> R.string.bb_standard_deviation
|
|
HANDS_PLAYED -> R.string.number_of_hands
|
|
LOCATIONS_PLAYED -> R.string.locations_played
|
|
LONGEST_STREAKS -> R.string.longest_streaks
|
|
MAXIMUM_NETRESULT -> R.string.max_net_result
|
|
MINIMUM_NETRESULT -> R.string.min_net_result
|
|
MAXIMUM_DURATION -> R.string.longest_session
|
|
DAYS_PLAYED -> R.string.days_played
|
|
TOTAL_BUYIN -> R.string.total_buyin
|
|
else -> throw PAIllegalStateException("Stat ${this.name} name required but undefined")
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Formats the value of the stat to be suitable for display
|
|
*/
|
|
fun textFormat(value: Double, secondValue: Double? = null, currency: Currency? = null): TextFormat {
|
|
|
|
if (value.isNaN()) {
|
|
return TextFormat(NULL_TEXT, R.color.white)
|
|
}
|
|
|
|
when (this) {
|
|
// Amounts + red/green
|
|
NET_RESULT, HOURLY_RATE, AVERAGE, MAXIMUM_NETRESULT, MINIMUM_NETRESULT -> {
|
|
val color = if (value >= this.threshold) R.color.green else R.color.red
|
|
return TextFormat(value.toCurrency(currency), color)
|
|
}
|
|
// Red/green numericValues
|
|
HOURLY_RATE_BB, AVERAGE_NET_BB, NET_BB_PER_100_HANDS, BB_NET_RESULT -> {
|
|
val color = if (value >= this.threshold) R.color.green else R.color.red
|
|
return TextFormat(value.formatted, color)
|
|
}
|
|
// white integers
|
|
NUMBER_OF_SETS, NUMBER_OF_GAMES, HANDS_PLAYED, LOCATIONS_PLAYED, DAYS_PLAYED -> {
|
|
return TextFormat("${value.toInt()}")
|
|
} // white durations
|
|
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, MAXIMUM_DURATION -> {
|
|
return TextFormat(value.formattedHourlyDuration())
|
|
} // red/green percentages
|
|
WIN_RATIO, ROI -> {
|
|
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red
|
|
return TextFormat("${(value * 100).formatted}%", color)
|
|
}
|
|
RISK_OF_RUIN -> {
|
|
val color = if (value * 100 <= this.threshold) R.color.green else R.color.red
|
|
return TextFormat("${(value * 100).formatted}%", color)
|
|
}
|
|
// white amountsr
|
|
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS, STANDARD_DEVIATION_BB, TOTAL_BUYIN -> {
|
|
return TextFormat(value.toCurrency(currency))
|
|
}
|
|
LONGEST_STREAKS -> {
|
|
return TextFormat("${value.toInt()}W / ${secondValue!!.toInt()}L")
|
|
}
|
|
else -> throw FormattingException("Stat formatting of ${this.name} not handled")
|
|
}
|
|
}
|
|
|
|
private val threshold: Double
|
|
get() {
|
|
return when (this) {
|
|
RISK_OF_RUIN -> 5.0
|
|
WIN_RATIO -> 50.0
|
|
else -> 0.0
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Returns a label used to display the legend right value, typically a total or an average
|
|
*/
|
|
fun cumulativeLabelResId(context: Context): String {
|
|
val resId = when (this) {
|
|
AVERAGE, AVERAGE_HOURLY_DURATION, NET_BB_PER_100_HANDS,
|
|
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, HOURLY_RATE -> R.string.average
|
|
NUMBER_OF_SETS -> R.string.number_of_sessions
|
|
NUMBER_OF_GAMES -> R.string.number_of_records
|
|
NET_RESULT -> R.string.total
|
|
STANDARD_DEVIATION -> R.string.net_result
|
|
STANDARD_DEVIATION_BB -> R.string.average_net_result_bb_
|
|
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands
|
|
WIN_RATIO, HOURLY_DURATION -> return this.localizedTitle(context)
|
|
else -> null
|
|
}
|
|
resId?.let {
|
|
return context.getString(it)
|
|
} ?: run {
|
|
return NULL_TEXT
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the different available aggregation type for each statistic
|
|
*/
|
|
val aggregationTypes: List<AggregationType>
|
|
get() {
|
|
return when (this) {
|
|
NET_RESULT -> listOf(
|
|
AggregationType.SESSION,
|
|
AggregationType.MONTH,
|
|
AggregationType.YEAR,
|
|
AggregationType.DURATION
|
|
)
|
|
NUMBER_OF_GAMES, NUMBER_OF_SETS -> listOf(AggregationType.MONTH, AggregationType.YEAR)
|
|
else -> listOf(AggregationType.SESSION, AggregationType.MONTH, AggregationType.YEAR)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if the stat has an evolution graph
|
|
*/
|
|
val hasProgressGraph: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION, AVERAGE_HOURLY_DURATION, STANDARD_DEVIATION_BB,
|
|
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
val isStandardDeviation: Boolean
|
|
get() {
|
|
return when (this) {
|
|
STANDARD_DEVIATION, STANDARD_DEVIATION_BB,
|
|
STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
val legendHideRightValue: Boolean
|
|
get() {
|
|
return when (this) {
|
|
AVERAGE, NUMBER_OF_SETS, NUMBER_OF_GAMES, WIN_RATIO,
|
|
HOURLY_DURATION, AVERAGE_HOURLY_DURATION -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if the stat has a significant value to display in a progress graph
|
|
*/
|
|
val graphSignificantIndividualValue: Boolean
|
|
get() {
|
|
return when (this) {
|
|
AVERAGE, WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES,
|
|
STANDARD_DEVIATION, HOURLY_DURATION -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns if the stat graph should show the number of sessions
|
|
*/
|
|
val graphShouldShowNumberOfSessions: Boolean
|
|
get() {
|
|
return when (this) {
|
|
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
val graphShowsXAxisZero: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
val graphShowsYAxisZero: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
private val canBeUserSelected: Boolean
|
|
get() {
|
|
return when (this) {
|
|
WINNING_SESSION_COUNT, BB_SESSION_COUNT, RISK_OF_RUIN -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
private val hasProgressValues: Boolean
|
|
get() {
|
|
return when (this) {
|
|
NET_RESULT, NET_BB_PER_100_HANDS, HOURLY_RATE_BB,
|
|
AVERAGE_HOURLY_DURATION, HOURLY_DURATION,
|
|
NUMBER_OF_SETS, ROI, AVERAGE_BUYIN, WIN_RATIO,
|
|
AVERAGE_NET_BB, NUMBER_OF_GAMES, AVERAGE -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
|
|
}
|
|
|