|
|
|
|
@ -1,8 +1,10 @@ |
|
|
|
|
package net.pokeranalytics.android.calculus |
|
|
|
|
|
|
|
|
|
import android.content.Context |
|
|
|
|
import io.realm.RealmModel |
|
|
|
|
import net.pokeranalytics.android.R |
|
|
|
|
import net.pokeranalytics.android.exceptions.FormattingException |
|
|
|
|
import net.pokeranalytics.android.model.interfaces.Timed |
|
|
|
|
import net.pokeranalytics.android.ui.view.RowRepresentable |
|
|
|
|
import net.pokeranalytics.android.ui.view.RowViewType |
|
|
|
|
import net.pokeranalytics.android.util.CurrencyUtils |
|
|
|
|
@ -11,90 +13,159 @@ import net.pokeranalytics.android.util.extensions.formatted |
|
|
|
|
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration |
|
|
|
|
import java.util.* |
|
|
|
|
|
|
|
|
|
class StatFormattingException(message: String) : Exception(message) { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface StatBase : RealmModel { |
|
|
|
|
|
|
|
|
|
fun formattedValue(stat: Stat, context: Context): TextFormat |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* An enum representing all the types of Session statistics |
|
|
|
|
*/ |
|
|
|
|
enum class Stat : RowRepresentable { |
|
|
|
|
|
|
|
|
|
NETRESULT, |
|
|
|
|
HOURLY_RATE, |
|
|
|
|
AVERAGE, |
|
|
|
|
NUMBER_OF_SETS, |
|
|
|
|
NUMBER_OF_GAMES, |
|
|
|
|
DURATION, |
|
|
|
|
AVERAGE_DURATION, |
|
|
|
|
NET_BB_PER_100_HANDS, |
|
|
|
|
HOURLY_RATE_BB, |
|
|
|
|
AVERAGE_NET_BB, |
|
|
|
|
WIN_RATIO, |
|
|
|
|
AVERAGE_BUYIN, |
|
|
|
|
ROI, |
|
|
|
|
STANDARD_DEVIATION, |
|
|
|
|
STANDARD_DEVIATION_HOURLY, |
|
|
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS, |
|
|
|
|
HANDS_PLAYED; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns whether the stat evolution numericValues requires a distribution sorting |
|
|
|
|
*/ |
|
|
|
|
fun hasDistributionSorting() : Boolean { |
|
|
|
|
return when (this) { |
|
|
|
|
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true |
|
|
|
|
else -> false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
companion object { |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override val resId: Int? |
|
|
|
|
get() { |
|
|
|
|
return when (this) { |
|
|
|
|
NETRESULT -> R.string.net_result |
|
|
|
|
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 |
|
|
|
|
DURATION -> R.string.duration |
|
|
|
|
AVERAGE_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 |
|
|
|
|
HANDS_PLAYED -> R.string.number_of_hands |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val threshold: Double |
|
|
|
|
get() { |
|
|
|
|
return when (this) { |
|
|
|
|
WIN_RATIO -> 50.0 |
|
|
|
|
else -> 0.0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
|
|
|
|
enum class Stat(var underlyingClass: Class<out Timed>? = null) : RowRepresentable { |
|
|
|
|
|
|
|
|
|
NETRESULT, |
|
|
|
|
HOURLY_RATE, |
|
|
|
|
AVERAGE, |
|
|
|
|
NUMBER_OF_SETS, |
|
|
|
|
NUMBER_OF_GAMES, |
|
|
|
|
DURATION, |
|
|
|
|
AVERAGE_DURATION, |
|
|
|
|
NET_BB_PER_100_HANDS, |
|
|
|
|
HOURLY_RATE_BB, |
|
|
|
|
AVERAGE_NET_BB, |
|
|
|
|
WIN_RATIO, |
|
|
|
|
AVERAGE_BUYIN, |
|
|
|
|
ROI, |
|
|
|
|
STANDARD_DEVIATION, |
|
|
|
|
STANDARD_DEVIATION_HOURLY, |
|
|
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS, |
|
|
|
|
HANDS_PLAYED; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns whether the stat evolution numericValues requires a distribution sorting |
|
|
|
|
*/ |
|
|
|
|
fun hasDistributionSorting(): Boolean { |
|
|
|
|
return when (this) { |
|
|
|
|
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> true |
|
|
|
|
else -> false |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
companion object { |
|
|
|
|
|
|
|
|
|
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 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override val resId: Int? |
|
|
|
|
get() { |
|
|
|
|
return when (this) { |
|
|
|
|
NETRESULT -> R.string.net_result |
|
|
|
|
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 |
|
|
|
|
DURATION -> R.string.duration |
|
|
|
|
AVERAGE_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 |
|
|
|
|
HANDS_PLAYED -> R.string.number_of_hands |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Formats the value of the stat to be suitable for display |
|
|
|
|
*/ |
|
|
|
|
fun format(value: Double, currency: Currency? = null, context: Context): TextFormat { |
|
|
|
|
|
|
|
|
|
if (value.isNaN()) { |
|
|
|
|
return TextFormat(NULL_TEXT, R.color.white) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
when (this) { |
|
|
|
|
// Amounts + red/green |
|
|
|
|
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { |
|
|
|
|
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) |
|
|
|
|
val color = if (value >= this.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat(numberFormat.format(value), color) |
|
|
|
|
} |
|
|
|
|
// Red/green numericValues |
|
|
|
|
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { |
|
|
|
|
val color = if (value >= this.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat(value.formatted(), color) |
|
|
|
|
} |
|
|
|
|
// white integers |
|
|
|
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { |
|
|
|
|
return TextFormat("${value.toInt()}") |
|
|
|
|
} // white durations |
|
|
|
|
Stat.DURATION, Stat.AVERAGE_DURATION -> { |
|
|
|
|
return TextFormat(value.formattedHourlyDuration()) |
|
|
|
|
} // red/green percentages |
|
|
|
|
Stat.WIN_RATIO, Stat.ROI -> { |
|
|
|
|
val color = if (value * 100 >= this.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat("${(value * 100).formatted()}%", color) |
|
|
|
|
} // white amountsr |
|
|
|
|
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, |
|
|
|
|
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { |
|
|
|
|
val numberFormat = CurrencyUtils.getCurrencyFormatter(context, currency) |
|
|
|
|
return TextFormat(numberFormat.format(value)) |
|
|
|
|
} |
|
|
|
|
else -> throw FormattingException("Stat formatting of ${this.name} not handled") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
val threshold: Double |
|
|
|
|
get() { |
|
|
|
|
return when (this) { |
|
|
|
|
WIN_RATIO -> 50.0 |
|
|
|
|
else -> 0.0 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
fun cumulativeLabelResId(context: Context) : String { |
|
|
|
|
val resId = when (this) { |
|
|
|
|
AVERAGE, AVERAGE_DURATION, NETRESULT, NET_BB_PER_100_HANDS, |
|
|
|
|
HOURLY_RATE_BB, AVERAGE_NET_BB, ROI, WIN_RATIO, HOURLY_RATE -> R.string.average |
|
|
|
|
NETRESULT, DURATION -> R.string.total |
|
|
|
|
STANDARD_DEVIATION -> R.string.net_result |
|
|
|
|
STANDARD_DEVIATION_HOURLY -> R.string.hour_rate_without_pauses |
|
|
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS -> R.string.net_result_bb_per_100_hands |
|
|
|
|
else -> null |
|
|
|
|
} |
|
|
|
|
resId?.let { |
|
|
|
|
return context.getString(it) |
|
|
|
|
} ?: run { |
|
|
|
|
return NULL_TEXT |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
@ -102,63 +173,29 @@ enum class Stat : RowRepresentable { |
|
|
|
|
*/ |
|
|
|
|
class ComputedStat(var stat: Stat, var value: Double, var currency: Currency? = null) { |
|
|
|
|
|
|
|
|
|
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { |
|
|
|
|
if (previousValue != null) { |
|
|
|
|
this.variation = (value - previousValue) / previousValue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* The variation of the stat |
|
|
|
|
*/ |
|
|
|
|
var variation: Double? = null |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Formats the value of the stat to be suitable for display |
|
|
|
|
*/ |
|
|
|
|
fun format(context: Context): TextFormat { |
|
|
|
|
|
|
|
|
|
if (this.value.isNaN()) { |
|
|
|
|
return TextFormat(NULL_TEXT, R.color.white) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
when (this.stat) { |
|
|
|
|
// Amounts + red/green |
|
|
|
|
Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE -> { |
|
|
|
|
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency) |
|
|
|
|
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat(numberFormat.format(this.value), color) |
|
|
|
|
} |
|
|
|
|
// Red/green numericValues |
|
|
|
|
Stat.HOURLY_RATE_BB, Stat.AVERAGE_NET_BB, Stat.NET_BB_PER_100_HANDS -> { |
|
|
|
|
val color = if (this.value >= this.stat.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat(this.value.formatted(), color) |
|
|
|
|
} |
|
|
|
|
// white integers |
|
|
|
|
Stat.NUMBER_OF_SETS, Stat.NUMBER_OF_GAMES, Stat.HANDS_PLAYED -> { |
|
|
|
|
return TextFormat("${value.toInt()}") |
|
|
|
|
} // white durations |
|
|
|
|
Stat.DURATION, Stat.AVERAGE_DURATION -> { |
|
|
|
|
return TextFormat(value.formattedHourlyDuration()) |
|
|
|
|
} // red/green percentages |
|
|
|
|
Stat.WIN_RATIO, Stat.ROI -> { |
|
|
|
|
val color = if (value * 100 >= this.stat.threshold) R.color.green else R.color.red |
|
|
|
|
return TextFormat("${(value * 100).formatted()}%", color) |
|
|
|
|
} // white amountsr |
|
|
|
|
Stat.AVERAGE_BUYIN, Stat.STANDARD_DEVIATION, Stat.STANDARD_DEVIATION_HOURLY, |
|
|
|
|
Stat.STANDARD_DEVIATION_BB_PER_100_HANDS -> { |
|
|
|
|
val numberFormat= CurrencyUtils.getCurrencyFormatter(context, currency) |
|
|
|
|
return TextFormat(numberFormat.format(this.value)) |
|
|
|
|
} |
|
|
|
|
else -> throw FormattingException("Stat formatting of ${this.stat.name} not handled") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns a TextFormat instance for an evolution value located at the specified [index] |
|
|
|
|
*/ |
|
|
|
|
fun evolutionValueFormat(index: Int) : TextFormat { |
|
|
|
|
return TextFormat("undef ${index}") |
|
|
|
|
} |
|
|
|
|
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) { |
|
|
|
|
if (previousValue != null) { |
|
|
|
|
this.variation = (value - previousValue) / previousValue |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* The variation of the stat |
|
|
|
|
*/ |
|
|
|
|
var variation: Double? = null |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Formats the value of the stat to be suitable for display |
|
|
|
|
*/ |
|
|
|
|
fun format(context: Context): TextFormat { |
|
|
|
|
return this.stat.format(this.value, this.currency, context) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Returns a TextFormat instance for an evolution value located at the specified [index] |
|
|
|
|
*/ |
|
|
|
|
fun evolutionValueFormat(index: Int): TextFormat { |
|
|
|
|
return TextFormat("undef ${index}") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|