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.
278 lines
7.7 KiB
278 lines
7.7 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.model.interfaces.Timed
|
|
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.extensions.formatted
|
|
import net.pokeranalytics.android.util.extensions.formattedHourlyDuration
|
|
import net.pokeranalytics.android.util.extensions.toCurrency
|
|
import java.util.*
|
|
import kotlin.math.exp
|
|
|
|
class StatFormattingException(message: String) : Exception(message) {
|
|
|
|
}
|
|
|
|
class ObjectIdentifier(var id: String, var clazz: Class<out Timed>) {
|
|
|
|
}
|
|
|
|
/**
|
|
* An enum representing all the types of Session statistics
|
|
*/
|
|
enum class Stat : RowRepresentable {
|
|
|
|
NET_RESULT,
|
|
BB_NET_RESULT,
|
|
HOURLY_RATE,
|
|
AVERAGE,
|
|
NUMBER_OF_SETS,
|
|
NUMBER_OF_GAMES,
|
|
HOURLY_DURATION,
|
|
AVERAGE_HOURLY_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,
|
|
LOCATIONS_PLAYED,
|
|
LONGEST_STREAKS,
|
|
MAXIMUM_NETRESULT,
|
|
MINIMUM_NETRESULT,
|
|
MAXIMUM_DURATION,
|
|
DAYS_PLAYED,
|
|
WINNING_SESSION_COUNT,
|
|
BB_SESSION_COUNT,
|
|
TOTAL_BUYIN,
|
|
;
|
|
|
|
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
|
|
}
|
|
|
|
fun riskOfRuin(hourlyRate: Double, hourlyStandardDeviation: Double, bankrollValue: Double) : Double? {
|
|
|
|
if (bankrollValue <= 0.0) {
|
|
return null
|
|
}
|
|
|
|
val numerator = -2 * hourlyRate * bankrollValue
|
|
val denominator = Math.pow(hourlyStandardDeviation, 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
|
|
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
|
|
else -> throw IllegalStateException("Stat ${this.name} name required but undefined")
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Formats the value of the stat to be suitable for display
|
|
*/
|
|
fun format(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 -> {
|
|
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)
|
|
} // white amountsr
|
|
AVERAGE_BUYIN, STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY,
|
|
STANDARD_DEVIATION_BB_PER_100_HANDS -> {
|
|
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")
|
|
}
|
|
}
|
|
|
|
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_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_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
|
|
}
|
|
}
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
val hasEvolutionGraph: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION, AVERAGE_HOURLY_DURATION,
|
|
STANDARD_DEVIATION, STANDARD_DEVIATION_HOURLY, STANDARD_DEVIATION_BB_PER_100_HANDS -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
val significantIndividualValue: Boolean
|
|
get() {
|
|
return when (this) {
|
|
WIN_RATIO, NUMBER_OF_SETS, NUMBER_OF_GAMES, STANDARD_DEVIATION, HOURLY_DURATION -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
val shouldShowNumberOfSessions: Boolean
|
|
get() {
|
|
return when (this) {
|
|
NUMBER_OF_GAMES, NUMBER_OF_SETS -> false
|
|
else -> true
|
|
}
|
|
}
|
|
|
|
val showXAxisZero: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
val showYAxisZero: Boolean
|
|
get() {
|
|
return when (this) {
|
|
HOURLY_DURATION -> true
|
|
else -> false
|
|
}
|
|
}
|
|
|
|
override val viewType: Int = RowViewType.TITLE_VALUE.ordinal
|
|
}
|
|
|
|
/**
|
|
* ComputedStat contains a [stat] and their associated [value]
|
|
*/
|
|
class ComputedStat(var stat: Stat, var value: Double, var secondValue: Double? = null, var currency: Currency? = null) {
|
|
|
|
constructor(stat: Stat, value: Double, previousValue: Double?) : this(stat, value) {
|
|
if (previousValue != null) {
|
|
this.variation = (value - previousValue) / previousValue
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The value used to get evolution dataset
|
|
*/
|
|
var progressValue: Double? = null
|
|
|
|
/**
|
|
* The variation of the stat
|
|
*/
|
|
var variation: Double? = null
|
|
|
|
/**
|
|
* Formats the value of the stat to be suitable for display
|
|
*/
|
|
fun format(): TextFormat {
|
|
return this.stat.format(this.value, this.secondValue, this.currency)
|
|
}
|
|
|
|
}
|
|
|