Merge branch 'master' of gitlab.com:stax-river/poker-analytics

feature/top10
Aurelien Hubert 7 years ago
commit 4b39cc7884
  1. 4
      app/src/androidTest/java/net/pokeranalytics/android/BankrollInstrumentedUnitTest.kt
  2. 74
      app/src/androidTest/java/net/pokeranalytics/android/StatsInstrumentedUnitTest.kt
  3. 18
      app/src/androidTest/java/net/pokeranalytics/android/filter/SessionFilterInstrumentedUnitTest.kt
  4. 64
      app/src/main/java/net/pokeranalytics/android/PokerAnalyticsApplication.kt
  5. 52
      app/src/main/java/net/pokeranalytics/android/calculus/Calculator.kt
  6. 12
      app/src/main/java/net/pokeranalytics/android/calculus/ComputableGroup.kt
  7. 40
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollCalculator.kt
  8. 122
      app/src/main/java/net/pokeranalytics/android/calculus/bankroll/BankrollReport.kt
  9. 17
      app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Computable.kt
  10. 11
      app/src/main/java/net/pokeranalytics/android/calculus/interfaces/Datable.kt
  11. 2
      app/src/main/java/net/pokeranalytics/android/model/filter/Filterable.kt
  12. 92
      app/src/main/java/net/pokeranalytics/android/model/filter/SessionFilterable.kt
  13. 51
      app/src/main/java/net/pokeranalytics/android/model/realm/ComputableResult.kt
  14. 19
      app/src/main/java/net/pokeranalytics/android/model/realm/Currency.kt
  15. 49
      app/src/main/java/net/pokeranalytics/android/model/realm/Result.kt
  16. 42
      app/src/main/java/net/pokeranalytics/android/model/realm/Session.kt
  17. 2
      app/src/main/java/net/pokeranalytics/android/model/realm/SessionSet.kt
  18. 54
      app/src/main/java/net/pokeranalytics/android/ui/fragment/StatsFragment.kt
  19. 6
      app/src/main/java/net/pokeranalytics/android/ui/fragment/components/SessionObserverFragment.kt
  20. 4
      app/src/test/java/net/pokeranalytics/android/ExampleUnitTest.kt

@ -3,7 +3,7 @@ package net.pokeranalytics.android
import androidx.test.ext.junit.runners.AndroidJUnit4
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.SessionGroup
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Currency
@ -56,7 +56,7 @@ class BankrollInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()

@ -1,18 +1,24 @@
package net.pokeranalytics.android
import androidx.test.ext.junit.runners.AndroidJUnit4
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmResults
import io.realm.kotlin.where
import net.pokeranalytics.android.calculus.Calculator
import net.pokeranalytics.android.calculus.ComputedResults
import net.pokeranalytics.android.calculus.SessionGroup
import net.pokeranalytics.android.calculus.ComputableGroup
import net.pokeranalytics.android.calculus.Stat
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.SessionSet
import net.pokeranalytics.android.model.Limit
import net.pokeranalytics.android.model.realm.*
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import timber.log.Timber
import java.text.SimpleDateFormat
import java.time.LocalDate
import java.time.Period
import java.util.*
/**
@ -24,14 +30,58 @@ import java.util.*
class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
// convenience extension
fun Session.Companion.testInstance(netResult: Double, startDate: Date, endDate: Date?): Session {
val session: Session = Session.newInstance(super.mockRealm, false)
fun Session.Companion.testInstance(
netResult: Double = 0.0,
isTournament: Boolean = false,
startDate: Date = Date(),
endDate: Int = 1,
bankroll: Bankroll? = null,
game: Game? = null,
location : Location? = null,
tournamentName: TournamentName? = null,
tournamentFeatures: RealmList<TournamentFeature> = RealmList(),
numberOfTable: Int = 1,
limit: Int? = null,
tableSize: Int? = null
): Session {
val session: Session = Session.newInstance(super.mockRealm, isTournament, bankroll)
session.game = game
session.location = location
session.tournamentFeatures = tournamentFeatures
session.tournamentName = tournamentName
session.limit = limit
session.numberOfTables = numberOfTable
session.tableSize = tableSize
session.result?.netResult = netResult
session.startDate = startDate
session.endDate = endDate
val cal = Calendar.getInstance() // creates calendar
cal.time = startDate // sets calendar time/date
cal.add(Calendar.HOUR_OF_DAY, endDate) // adds one hour
session.endDate = cal.time // returns new date object, one hour in the future
return session
}
@Test
fun testSessionNetResultOnLoad() {
val realm = mockRealm
realm.beginTransaction()
for (index in 0..100) {
Session.testInstance((-2000..2000).random().toDouble())
println("*** creating ${index}")
}
realm.commitTransaction()
var d1 = Date()
var result = realm.where(Result::class.java).sum("netResult")
var d2 = Date()
val duration = (d2.time - d1.time)
println("*** ended in ${duration} milliseconds")
}
@Test
fun testSessionStats() {
@ -77,7 +127,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
@ -227,7 +277,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
@ -294,7 +344,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
@ -377,7 +427,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
realm.commitTransaction()
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()
options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)
@ -396,7 +446,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
s1.deleteFromRealm()
}
val group2 = SessionGroup(name = "test", sessions = sessions)
val group2 = ComputableGroup(name = "test", computables = sessions)
val results2: ComputedResults = Calculator.compute(group2, options)
val duration2 = results2.computedStat(Stat.DURATION)
@ -548,7 +598,7 @@ class StatsInstrumentedUnitTest : RealmInstrumentedUnitTest() {
}
val sessions = realm.where(Session::class.java).findAll()
val group = SessionGroup(name = "test", sessions = sessions)
val group = ComputableGroup(name = "test", computables = sessions)
val options = Calculator.Options()
// options.displayedStats = listOf(Stat.STANDARD_DEVIATION_BB_PER_100_HANDS, Stat.STANDARD_DEVIATION)

@ -288,7 +288,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
Assert.assertEquals(6, sessions.size)
val result = arrayListOf(l2.id, l3.id)
val result = arrayListOf(l1.id, l3.id)
sessions.forEach {
Assert.assertTrue(result.contains((it as Session).location?.id))
@ -384,7 +384,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
)
Assert.assertEquals(1, sessions.size)
(sessions[0] as Session)?.run {
(sessions[0] as Session).run {
Assert.assertEquals(s.id, this.id)
}
}
@ -492,7 +492,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filter = SessionFilterable.MORE_THAN_NET_RESULT
filter.valueMap = mapOf("net" to 204.0)
filter.valueMap = mapOf("value" to 204.0)
val sessions = FilterManager().filter(
realm,
@ -519,7 +519,7 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
realm.commitTransaction()
val filter = SessionFilterable.LESS_THAN_NET_RESULT
filter.valueMap = mapOf("net" to 540.0)
filter.valueMap = mapOf("value" to 540.0)
val sessions = FilterManager().filter(
realm,
@ -540,21 +540,21 @@ class SessionFilterInstrumentedUnitTest : BaseFilterInstrumentedUnitTest() {
val realm = this.mockRealm
realm.beginTransaction()
val s1 = Session.testInstance(netResult = 200.0)
val s2 = Session.testInstance(netResult = 500.0)
val s3 = Session.testInstance(netResult = 50.0)
Session.testInstance(netResult = 500.0)
val s2 = Session.testInstance(netResult = 50.0)
Session.testInstance(netResult = 570.0)
realm.commitTransaction()
val filterMore = SessionFilterable.MORE_THAN_NET_RESULT
filterMore.valueMap = mapOf("net" to 200.0)
filterMore.valueMap = mapOf("value" to 200.0)
val filterLess = SessionFilterable.LESS_THAN_NET_RESULT
filterLess.valueMap = mapOf("net" to 400.0)
filterLess.valueMap = mapOf("value" to 400.0)
val sessions = FilterManager().filter(
realm,
Session::class.java,
arrayListOf(filterLess, filterMore)
arrayListOf(filterMore, filterLess)
)
Assert.assertEquals(1, sessions.size)

@ -70,6 +70,64 @@ class PokerAnalyticsApplication : Application() {
}
}
//
// private fun createFakeStats() {
//
// val buyinList = arrayListOf(100.0, 200.0, 300.0, 500.0, 1000.0, 2000.0)
// val resultsList = arrayListOf(
// -2500.0, -2000.0, -1500.0, -1000.0, -500.0, 200.0, 1000.0, 1500.0, 2500.0
// )
//
// val commitFrequency = 100
//
// Thread() {
//
// try {
//
// val realm = Realm.getDefaultInstance()
//
// // Test endedSessions
// val pstats = realm.where<ComputableResult>().findAll()
// if (pstats.size < 10) {
//
// val numberOfSessions = 2000
// Timber.d("*** Start creating ${numberOfSessions} fake computables...")
//
// val s = Date()
//
// realm.beginTransaction()
//
// for (index in 0..numberOfSessions) {
//
// if (index % commitFrequency == 0) {
// Timber.d("****** committing at ${index} computables...")
// realm.commitTransaction()
// realm.beginTransaction()
// }
//
// val ps = realm.createObject(ComputableResult::class.java)
// ps.ratedBuyin = buyinList.random()
// ps.ratedNet = resultsList.random()
//
// }
//
// realm.commitTransaction()
//
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d("*** ended in ${duration} seconds")
//
// }
//
// realm.close()
//
// } catch (e: Exception) {
// Timber.e(e)
// }
//
// }.start()
//
// }
private fun createFakeSessions() {
@ -92,8 +150,8 @@ class PokerAnalyticsApplication : Application() {
val sessions = realm.where<Session>().findAll()
if (sessions.size < 10) {
val numberOfSessions = 200
Timber.d("*** Start creating ${numberOfSessions} fake sessions...")
val numberOfSessions = 2000
Timber.d("*** Start creating ${numberOfSessions} fake computables...")
val s = Date()
@ -102,7 +160,7 @@ class PokerAnalyticsApplication : Application() {
for (index in 0..numberOfSessions) {
if (index % commitFrequency == 0) {
Timber.d("****** committing at ${index} sessions...")
Timber.d("****** committing at ${index} computables...")
realm.commitTransaction()
realm.beginTransaction()
}

@ -1,7 +1,7 @@
package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.calculus.Stat.*
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.calculus.interfaces.Computable
import net.pokeranalytics.android.model.realm.SessionSet
import timber.log.Timber
import java.util.*
@ -66,7 +66,7 @@ class Calculator {
/**
* Computes all stats for list of Session sessionGroup
*/
fun computeGroups(groups: List<SessionGroup>, options: Options): List<ComputedResults> {
fun computeGroups(groups: List<ComputableGroup>, options: Options): List<ComputedResults> {
val computedResults = mutableListOf<ComputedResults>()
groups.forEach { group ->
@ -75,7 +75,7 @@ class Calculator {
val results: ComputedResults = Calculator.compute(group, options = options)
// Computes the compared sessionGroup if existing
val comparedGroup = group.comparedSessions
val comparedGroup = group.comparedComputables
if (comparedGroup != null) {
val comparedResults = Calculator.compute(comparedGroup, options = options)
group.comparedComputedResults = comparedResults
@ -94,13 +94,13 @@ class Calculator {
return computedResults
}
// fun compute(sessionGroup: SessionGroup, options: Options): ComputedResults {
// fun compute(sessionGroup: ComputableGroup, options: Options): ComputedResults {
//
// var sum: Double = 0.0
// val sessions: List<SessionInterface> = sessionGroup.sessions
// val computables: List<SessionInterface> = sessionGroup.computables
// val results: ComputedResults = ComputedResults(sessionGroup)
//
// sessions.forEach { s ->
// computables.forEach { s ->
// sum += s.ratedNet
// }
//
@ -117,34 +117,34 @@ class Calculator {
/**
* Computes stats for a SessionSet
*/
fun compute(sessionGroup: SessionGroup, options: Options): ComputedResults {
fun compute(computableGroup: ComputableGroup, options: Options): ComputedResults {
Timber.d(">>>> Start computing group ${sessionGroup.name}, ${sessionGroup.sessions.size} sessions")
Timber.d(">>>> Start computing group ${computableGroup.name}, ${computableGroup.computables.size} computables")
val sessions: List<Session> = sessionGroup.sessions
val sessionSets = sessionGroup.sessions.mapNotNull { it.sessionSet }.toHashSet()
val computables: List<Computable> = computableGroup.computables
val sessionSets = computableGroup.computables.mapNotNull { it.sessionSet }.toHashSet()
val results: ComputedResults = ComputedResults(sessionGroup)
val results: ComputedResults = ComputedResults(computableGroup)
var sum: Double = 0.0
var totalHands: Double = 0.0
var bbSum: Double = 0.0
var bbSessionCount: Int = 0
var winningSessionCount: Int = 0 // sessions.filter { it.value >= 0.0 }.size
var winningSessionCount: Int = 0 // computables.filter { it.value >= 0.0 }.size
var totalBuyin = 0.0
// Compute for each session
var index: Int = 0
sessions.forEach { s ->
computables.forEach { s ->
index++;
val result = s.result!! // ok to crash to see problems
// val result = s.result!! // ok to crash to see problems
sum += result.ratedNet
sum += s.ratedNet
bbSum += s.bbNetResult
bbSessionCount += s.hasBigBlind
winningSessionCount = result.isPositive
totalBuyin += s.buyin
winningSessionCount += s.isPositive
totalBuyin += s.ratedBuyin
totalHands += s.estimatedHands
when (options.evolutionValues) {
@ -212,10 +212,10 @@ class Calculator {
}
var average = 0.0
if (sessions.size > 0) {
average = sum / sessions.size.toDouble()
val winRatio = winningSessionCount.toDouble() / sessions.size.toDouble()
val avgBuyin = totalBuyin / sessions.size
if (computables.size > 0) {
average = sum / computables.size.toDouble()
val winRatio = winningSessionCount.toDouble() / computables.size.toDouble()
val avgBuyin = totalBuyin / computables.size
results.addStats(
setOf(
@ -242,7 +242,7 @@ class Calculator {
ComputedStat(NETRESULT, sum),
ComputedStat(DURATION, duration),
ComputedStat(NUMBER_OF_SETS, sessionSets.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, sessions.size.toDouble()),
ComputedStat(NUMBER_OF_GAMES, computables.size.toDouble()),
ComputedStat(HOURLY_RATE_BB, bbSum / duration),
ComputedStat(AVERAGE_NET_BB, bbSum / bbSessionCount),
ComputedStat(HANDS_PLAYED, totalHands)
@ -268,12 +268,12 @@ class Calculator {
// Session
var stdSum: Double = 0.0
var stdBBper100HandsSum: Double = 0.0
sessions.forEach { session ->
stdSum += Math.pow(session.result!!.ratedNet - average, 2.0)
computables.forEach { session ->
stdSum += Math.pow(session.ratedNet - average, 2.0)
stdBBper100HandsSum += Math.pow(session.bbPer100Hands - bbPer100Hands, 2.0)
}
val standardDeviation: Double = Math.sqrt(stdSum / sessions.size)
val standardDeviationBBper100Hands: Double = Math.sqrt(stdBBper100HandsSum / sessions.size)
val standardDeviation: Double = Math.sqrt(stdSum / computables.size)
val standardDeviationBBper100Hands: Double = Math.sqrt(stdBBper100HandsSum / computables.size)
// Session Set
var hourlyStdSum: Double = 0.0

@ -1,11 +1,11 @@
package net.pokeranalytics.android.calculus
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.calculus.interfaces.Computable
/**
* A sessionGroup of computable items identified by a name
*/
class SessionGroup(name: String, sessions: List<Session>, stats: List<Stat>? = null) {
class ComputableGroup(name: String, computables: List<Computable>, stats: List<Stat>? = null) {
/**
* The display name of the group
@ -15,7 +15,7 @@ class SessionGroup(name: String, sessions: List<Session>, stats: List<Stat>? = n
/**
* The list of endedSessions to compute
*/
var sessions: List<Session> = sessions
var computables: List<Computable> = computables
/**
* The list of stats to display
@ -25,7 +25,7 @@ class SessionGroup(name: String, sessions: List<Session>, stats: List<Stat>? = n
/**
* A subgroup used to compute stat variation
*/
var comparedSessions: SessionGroup? = null
var comparedComputables: ComputableGroup? = null
/**
* The computed stats of the comparable sessionGroup
@ -34,12 +34,12 @@ class SessionGroup(name: String, sessions: List<Session>, stats: List<Stat>? = n
}
class ComputedResults(group: SessionGroup) {
class ComputedResults(group: ComputableGroup) {
/**
* The session group used to computed the stats
*/
var group: SessionGroup = group
var group: ComputableGroup = group
// The computed stats of the sessionGroup
private var _computedStats: MutableMap<Stat, ComputedStat> = mutableMapOf()

@ -0,0 +1,40 @@
package net.pokeranalytics.android.calculus.bankroll
import io.realm.Realm
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.Transaction
class BankrollCalculator {
companion object {
fun computeReport(setup: BankrollReportSetup) : BankrollReport {
val realm = Realm.getDefaultInstance()
val report = BankrollReport(setup)
val sessionQuery = realm.where(Session::class.java)
if (setup.bankroll != null) {
sessionQuery.equalTo("bankroll.id", setup.bankroll.id)
}
val sessions = sessionQuery.findAll()
val transactionQuery = realm.where(Transaction::class.java)
if (setup.bankroll != null) {
transactionQuery.equalTo("bankroll.id", setup.bankroll.id).findAll()
}
val transactions = transactionQuery.findAll()
val sessionsNet = sessions.sum("result.net")
val transactionsNet = transactions.sum("value")
transactions.forEach {
report.addTransaction(it)
}
return report
}
}
}

@ -0,0 +1,122 @@
package net.pokeranalytics.android.calculus.bankroll
import net.pokeranalytics.android.calculus.interfaces.DatableValue
import net.pokeranalytics.android.model.realm.Bankroll
import net.pokeranalytics.android.model.realm.Transaction
import java.util.*
import kotlin.collections.HashMap
/**
* A class describing the parameters required to launch a bankroll report
*
*/
class BankrollReportSetup(bankroll: Bankroll?, from: Date? = null, to: Date? = null) {
/**
* The bankroll to compute. If null, the virtual global bankroll
*/
val bankroll = bankroll
/**
* The start of the report
*/
val from = from
/**
* The end of the report
*/
val to = to
}
class TransactionBucket(useRate: Boolean = false) {
var transactions: MutableList<Transaction> = mutableListOf()
private set
var total: Double = 0.0
private set
var useRate: Boolean = useRate
private set
fun addTransaction(transaction: Transaction) {
this.transactions.add(transaction)
var rate = 1.0
if (this.useRate) {
rate = transaction.bankroll?.currency?.rate ?: 1.0
}
val ratedAmount = rate * transaction.amount
this.total += ratedAmount
}
}
class BRGraphPoint {
var value: Double = 0.0
var variation: Double = 0.0
var date: Date? = null
}
class BankrollReport(setup: BankrollReportSetup) {
/**
* The setup used to compute the report
*/
var setup: BankrollReportSetup = setup
/**
* The value of the bankroll
*/
var total: Double = 0.0
private set
/**
* The net result from poker computables
*/
var netResult: Double = 0.0
private set
/**
* The difference between withdrawals and deposits
*/
var netBanked: Double = 0.0
private set
/**
* The risk of ruin
*/
var riskOfRuin: Double = 0.0
private set
var transactions: List<Transaction> = mutableListOf()
private set
var transactionBuckets: HashMap<String, TransactionBucket> = HashMap()
var evolutionPoints: Array<BRGraphPoint> = arrayOf()
var evolutionItems: Array<DatableValue> = arrayOf()
fun addTransaction(transaction: Transaction) {
transaction.type?.let { type ->
var bucket = this.transactionBuckets[type.id]
if (bucket == null) {
val b = TransactionBucket(this.setup.bankroll == null)
this.transactionBuckets[type.id] = b
bucket = b
}
bucket.addTransaction(transaction)
} ?: run {
throw Exception("Transaction has no type")
}
}
}

@ -0,0 +1,17 @@
package net.pokeranalytics.android.calculus.interfaces
import net.pokeranalytics.android.model.realm.SessionSet
interface Computable {
var ratedNet: Double
var bbNetResult: Double
var hasBigBlind: Int
var isPositive: Int
var ratedBuyin: Double
var estimatedHands: Double
var bbPer100Hands: Double
var sessionSet: SessionSet?
}

@ -0,0 +1,11 @@
package net.pokeranalytics.android.calculus.interfaces
import java.util.*
interface Datable {
var date: Date
}
interface DatableValue : Datable {
var value: Double
}

@ -36,7 +36,7 @@ class FilterManager {
fun filter(realm:Realm, relatedEntity: Class<out RealmObject>, queries:List<Filterable>): RealmResults<*> {
var realmQuery : RealmQuery<out RealmObject> = realm.where(relatedEntity)
queries.forEach {
realmQuery = (it.filter(realmQuery)).and()
realmQuery = (it.filter(realmQuery))
}
return realmQuery.findAll()
}

@ -6,7 +6,7 @@ import net.pokeranalytics.android.exceptions.FilterValueMapException
import net.pokeranalytics.android.model.filter.interfaces.Filterable
import net.pokeranalytics.android.model.realm.Session
enum class SessionFilterable(private var fieldName:String? = null): Filterable {
enum class SessionFilterable(private var fieldName:String? = null, private var subType:SubType? = null): Filterable {
LIVE,
CASH,
ONLINE,
@ -19,13 +19,33 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
LOCATION("location.id"),
LIMIT("limit"),
TABLE_SIZE("tableSize"),
NUMBER_OF_TABLE("numberOfTable"),
TOURNAMENT_TYPE("tournamentType"),
BLINDS,
LESS_THAN_NET_RESULT,
MORE_THAN_NET_RESULT,
MORE_NUMBER_OF_TABLE(Field.NUMBER_OF_TABLE.fieldName, SubType.MORE),
LESS_NUMBER_OF_TABLE(Field.NUMBER_OF_TABLE.fieldName, SubType.LESS),
BETWEEN_NUMBER_OF_TABLE(Field.NUMBER_OF_TABLE.fieldName, SubType.BETWEEN),
MORE_THAN_NET_RESULT(Field.NET_RESULT.fieldName, SubType.MORE),
LESS_THAN_NET_RESULT(Field.NET_RESULT.fieldName, SubType.LESS),
MORE_THAN_BUY_IN(Field.BUY_IN.fieldName, SubType.MORE),
LESS_THAN_BUY_IN(Field.BUY_IN.fieldName, SubType.LESS),
MORE_THAN_CASH_OUT(Field.CASH_OUT.fieldName, SubType.MORE),
LESS_THAN_CASH_OUT(Field.CASH_OUT.fieldName, SubType.LESS),
MORE_THAN_TIPS(Field.TIPS.fieldName, SubType.MORE),
LESS_THAN_TIPS(Field.TIPS.fieldName, SubType.LESS),
MORE_THAN_NUMBER_OF_PLAYER(Field.NUMBER_OF_PLAYER.fieldName, SubType.MORE),
LESS_THAN_NUMBER_OF_PLAYER(Field.NUMBER_OF_PLAYER.fieldName, SubType.LESS),
BETWEEN_NUMBER_OF_PLAYER(Field.NUMBER_OF_PLAYER.fieldName, SubType.BETWEEN),
MORE_THAN_TOURNAMENT_FEE(Field.NET_RESULT.fieldName, SubType.MORE),
LESS_THAN_TOURNAMENT_FEE(Field.NET_RESULT.fieldName, SubType.LESS),
BETWEEN_TOURNAMENT_FEE(Field.TOURNAMENT_FEE.fieldName, SubType.BETWEEN),
;
enum class SubType {
BETWEEN,
MORE,
LESS;
}
private enum class Field(var fieldName:String) {
LIVE("bankroll.live"),
CASH("type"),
@ -35,12 +55,13 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
SMALL_BLIND("cgSmallBlind"),
COMMENT("comment"),
TOURNAMENT_FEATURES("tournamentFeatures.id"),
TOURNAMENT_NUMBER_OF_PLAYER("tournamentNumberOfPlayers"),
TOURNAMENT_ENTRY_FEE("tournamentEntryFee"),
RESULT_BUY_IN("result.buyin"),
RESULT_CASHED_OUT("result.cashout"),
RESULT_NET("result.ratedNet"),
RESULT_TIPS("result.tips"),
NET_RESULT("result.ratedNet"),
BUY_IN("result.buyin"),
CASH_OUT("result.cashout"),
TIPS("result.tips"),
NUMBER_OF_TABLE("numberOfTable"),
NUMBER_OF_PLAYER("tournamentNumberOfPlayers"),
TOURNAMENT_FEE("tournamentEntryFee"),
;
}
@ -48,21 +69,46 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
override val filterValuesExceptedKeys : Array<String>?
get() {
this.subType?.let {
return when (it) {
SubType.BETWEEN -> arrayOf("leftValue", "rightValue")
else -> arrayOf("value")
}
}
return when (this) {
BANKROLL, GAME, LOCATION, ANY_TOURNAMENT_FEATURES, ALL_TOURNAMENT_FEATURES -> arrayOf("ids")
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE, NUMBER_OF_TABLE -> arrayOf("values")
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> arrayOf("values")
BLINDS -> arrayOf("map")
MORE_THAN_NET_RESULT, LESS_THAN_NET_RESULT -> arrayOf("net")
else -> null
}
}
var between: Boolean = false
var moreThan: Boolean = false
var lessThan: Boolean = false
var strict: Boolean = false
override fun filter(realmQuery: RealmQuery<out RealmObject>): RealmQuery<out RealmObject> {
this.subType?.let {subType ->
this.fieldName?.let {
return when (subType) {
SubType.LESS -> {
val value: Double by filterValues
println("filter test less")
realmQuery.lessThanOrEqualTo(it, value)
}
SubType.MORE -> {
println("filter test more")
val value: Double by filterValues
realmQuery.greaterThanOrEqualTo(it, value)
}
SubType.BETWEEN -> {
val leftValue: Double by filterValues
val rightValue: Double by filterValues
realmQuery.between(it, leftValue, rightValue)
}
}
} ?: run {
throw FilterValueMapException("fieldName is missing")
}
}
return when (this) {
LIVE -> realmQuery.equalTo(Field.LIVE.fieldName, true)
CASH -> realmQuery.equalTo(Field.CASH.fieldName, Session.Type.CASH_GAME.ordinal)
@ -87,7 +133,7 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
throw FilterValueMapException("fieldName is missing")
}
}
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE, NUMBER_OF_TABLE -> {
LIMIT, TOURNAMENT_TYPE, TABLE_SIZE -> {
val values : Array<Int?>? by filterValues
this.fieldName?.let {
realmQuery.`in`(it, values)
@ -95,13 +141,6 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
throw FilterValueMapException("fieldName is missing")
}
}
MORE_THAN_NET_RESULT, LESS_THAN_NET_RESULT -> {
val net : Double by filterValues
if (this == LESS_THAN_NET_RESULT) {
realmQuery.not()
}
realmQuery.greaterThanOrEqualTo(Field.RESULT_NET.fieldName, net)
}
BLINDS -> {
val map : Array<Map<String,Any?>> by filterValues
val expectedSubKeys = arrayOf("sb", "bb", "code")
@ -142,6 +181,9 @@ enum class SessionFilterable(private var fieldName:String? = null): Filterable {
}
realmQuery
}
else -> {
realmQuery
}
}
}
}

@ -0,0 +1,51 @@
package net.pokeranalytics.android.model.realm
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import net.pokeranalytics.android.calculus.interfaces.Computable
open class ComputableResult : RealmObject(), Computable {
override var ratedNet: Double = 0.0
override var bbNetResult: Double = 0.0
override var hasBigBlind: Int = 0
override var isPositive: Int = 0
override var ratedBuyin: Double = 0.0
override var estimatedHands: Double = 0.0
override var bbPer100Hands: Double = 0.0
override var sessionSet: SessionSet? = null
@LinkingObjects("computableResult")
private val sessions: RealmResults<Session>? = null
@Ignore
val session: Session? = this.sessions?.firstOrNull()
fun updateWith(session: Session) {
val rate = session.bankroll?.currency?.rate ?: 1.0
session.result?.let { result ->
this.ratedNet = result.net * rate
this.isPositive = result.isPositive
this.ratedBuyin = (result.buyin ?: 0.0) * rate
}
this.bbNetResult = session.bbNetResult
this.hasBigBlind = session.hasBigBlind
this.estimatedHands = session.estimatedHands
this.bbPer100Hands = session.bbPer100Hands
this.sessionSet = session.sessionSet
}
}

@ -15,5 +15,24 @@ open class Currency : RealmObject() {
// The rate of the currency with the main currency
var rate: Double? = null
set(value) {
field = value
val rate = value ?: 1.0
// could be async
val cResults = this.realm.where(ComputableResult::class.java).equalTo("session.bankroll.currency", this.id).findAll()
cResults.forEach { computable ->
computable.session?.result?.net?.let {
computable.ratedNet = it * rate
}
computable.session?.result?.buyin?.let {
computable.ratedBuyin = it * rate
}
}
}
}

@ -2,29 +2,35 @@ package net.pokeranalytics.android.model.realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.Ignore
import io.realm.annotations.LinkingObjects
import io.realm.annotations.RealmClass
@RealmClass
open class Result : RealmObject() {
// The buyin amount
/**
* The buyin amount
*/
var buyin: Double? = null
set(value) {
field = value
this.computeNet()
}
// the cashed out amount
/**
* The cashed out amount
*/
var cashout: Double? = null
set(value) {
field = value
this.computeNet()
}
// Tips
var tips: Double? = null
// The net result
/**
* The net result
*/
var netResult: Double? = null
set(value) {
field = value
@ -38,10 +44,9 @@ open class Result : RealmObject() {
private set
/**
* The pre-computed rated net (readonly)
* Tips
*/
var ratedNet: Double = 0.0
private set
var tips: Double? = null
// The transactions associated with the Result, impacting the result
var transactions: RealmList<Transaction> = RealmList()
@ -53,23 +58,17 @@ open class Result : RealmObject() {
// The tournament final position, if applicable
var tournamentFinalPosition: Int? = null
/**
* The automatically Realm-updated sessions list
* Should contain only one element
*/
// @LinkingObjects("result")
// private val sessions: RealmResults<Session>? = null
@LinkingObjects("result")
private val sessions: RealmResults<Session>? = null
/**
* The associated Session
*/
// @Ignore
// val session: Session? = this.sessions?.firstOrNull()
@Ignore
val session: Session? = this.sessions?.firstOrNull()
/**
* Returns 1 if the session is positive
*/
var isPositive: Int = 0
@Ignore
val isPositive: Int = if (this.net >= 0.0) 1 else 0
// Computes the Net
private fun computeNet() {
@ -81,12 +80,12 @@ open class Result : RealmObject() {
val buyin = this.buyin ?: 0.0
val cashOut = this.cashout ?: 0.0
this.net = cashOut - buyin + transactionsSum
}
this.ratedNet = this.net * 1.0
this.isPositive = if (this.ratedNet >= 0.0) 1 else 0
}
// private var rate: Double = (this.session?.bankroll?.currency?.rate ?: 1.0)
// Update ComputableResult
this.session?.updateComputableResult()
}
// @todo tips?
}

@ -5,6 +5,7 @@ import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.Index
import io.realm.annotations.PrimaryKey
import io.realm.kotlin.where
import net.pokeranalytics.android.R
@ -39,6 +40,18 @@ import kotlin.collections.ArrayList
open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource, RowRepresentable, Timed,
TimeFilterable {
// @Ignore
// var rate: Double = 0.0
// get() = this.bankroll?.currency?.rate ?: 0.0
//
// @Ignore
// override var ratedNet: Double = 0.0
// get() = this.result!!.net * this.rate
//
// @Ignore
// override var isPositive: Int = 0
// get() = this.result!!.isPositive
enum class Type {
CASH_GAME,
TOURNAMENT
@ -48,6 +61,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
fun newInstance(realm: Realm, isTournament: Boolean, bankroll: Bankroll? = null): Session {
val session = Session()
session.result = Result()
session.computableResult = ComputableResult()
if (bankroll != null) {
session.bankroll = bankroll
} else {
@ -69,6 +83,11 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
// The result of the main user
var result: Result? = null
/**
* Optimized result for faster stats
*/
var computableResult: ComputableResult? = null
// Timed interface
override var dayOfWeek : Int? = null
@ -84,23 +103,24 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
this.updateTimeParameter(field)
this.computeNetDuration()
this.computeStats()
// nullifies endate when setting the start date after the end date
if (value != null && this.endDate != null && value.after(this.endDate)) {
this.endDate = null
}
this.dateChanged()
this.computeStats()
}
/**
* the end date of the session
*/
@Index
var endDate: Date? = null
set(value) {
field = value
this.computeNetDuration()
this.computeStats()
this.dateChanged()
this.computeStats()
}
/**
@ -175,6 +195,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
set(value) {
this.hasBigBlind = if (value != null) 1 else 0
field = value
this.updateComputableResult()
}
// Tournament
@ -204,6 +225,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
} else if (this.sessionSet != null) {
SessionSetManager.removeFromTimeline(this)
}
this.updateComputableResult()
this.updateRowRepresentation()
}
@ -267,6 +289,15 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
this.computeEstimatedHands()
this.computeBBNetResult()
this.computeBBPer100Hands()
this.updateComputableResult()
}
fun updateComputableResult() {
this.computableResult?.let {
it.updateWith(this)
} ?: run {
throw IllegalStateException("Session should always have a Light Result")
}
}
/**
@ -304,7 +335,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
private set
@Ignore
var buyin: Double = 0.0
var ratedBuyin: Double = 0.0
get() {
this.result?.let { result ->
result.buyin?.let {
@ -481,7 +512,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
}
@Ignore
private var rowRepresentationForCurrentState : List<RowRepresentable> = this.updatedRowRepresentationForCurrentState()
private var rowRepresentationForCurrentState : List<RowRepresentable> = mutableListOf()
private fun updatedRowRepresentationForCurrentState(): List<RowRepresentable> {
val rows = ArrayList<RowRepresentable>()
@ -550,7 +581,6 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
override fun adapterRows(): List<RowRepresentable>? {
return this.rowRepresentationForCurrentState
}
override fun boolForRow(row: RowRepresentable): Boolean {
@ -635,7 +665,7 @@ open class Session : RealmObject(), Manageable, StaticRowRepresentableDataSource
SessionRow.BUY_IN -> row.editingDescriptors(mapOf(
"bb" to cgBigBlind,
"fee" to this.tournamentEntryFee,
"buyin" to buyin
"ratedBuyin" to ratedBuyin
))
SessionRow.BREAK_TIME -> row.editingDescriptors(mapOf())
SessionRow.CASHED_OUT, SessionRow.PRIZE, SessionRow.NET_RESULT -> row.editingDescriptors(mapOf(

@ -68,7 +68,7 @@ open class SessionSet : RealmObject(), Timed {
val sessions: RealmResults<Session>? = null
@Ignore
val ratedNet: Double = this.sessions?.sumByDouble { it.result!!.ratedNet } ?: 0.0
val ratedNet: Double = this.sessions?.sumByDouble { it.computableResult!!.ratedNet } ?: 0.0
@Ignore
val hourlyRate: Double = this.ratedNet / this.hourlyDuration

@ -10,8 +10,9 @@ import kotlinx.android.synthetic.main.fragment_stats.*
import kotlinx.coroutines.*
import net.pokeranalytics.android.R
import net.pokeranalytics.android.calculus.*
import net.pokeranalytics.android.calculus.interfaces.Computable
import net.pokeranalytics.android.model.StatRepresentable
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.ComputableResult
import net.pokeranalytics.android.ui.adapter.DisplayDescriptor
import net.pokeranalytics.android.ui.adapter.RowRepresentableAdapter
import net.pokeranalytics.android.ui.adapter.StaticRowRepresentableDataSource
@ -97,7 +98,7 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
// Override
override fun sessionsChanged() {
// this.launchStatComputation()
this.launchStatComputation()
this.statsAdapter.notifyDataSetChanged()
}
@ -132,26 +133,6 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
private fun launchStatComputation() {
// Thread() {
//// var results = listOf<ComputedResults>()
// val s = Date()
// Timber.d(">>> start...")
//
// val results = createSessionGroupsAndStartCompute()
//
// val e = Date()
// val duration = (e.time - s.time) / 1000.0
// Timber.d(">>> ended in ${duration} seconds")
//
//
// val mainHandler = Handler(Looper.getMainLooper())
// val runnable = Runnable {
// showResults(results)
// }
// mainHandler.post(runnable)
//
// }.start()
GlobalScope.launch(coroutineContext) {
var results = listOf<ComputedResults>()
@ -177,35 +158,36 @@ class StatsFragment : SessionObserverFragment(), StaticRowRepresentableDataSourc
private fun createSessionGroupsAndStartCompute() : List<ComputedResults> {
val cgSessions = mutableListOf<Session>()
val tSessions = mutableListOf<Session>()
val cgSessions = mutableListOf<Computable>()
val tSessions = mutableListOf<Computable>()
val s = Date()
val realm = Realm.getDefaultInstance()
val allSessions = realm.where(Session::class.java).isNotNull("endDate").findAll()
Timber.d(">>>>> number of sessions to compute = ${allSessions.size}")
val sessionsList = allSessions.toList()
// val allSessions = realm.where(ComputableResult::class.java).findAll()
val allSessions = realm.where(ComputableResult::class.java).isNotNull("sessions.endDate").findAll()
Timber.d(">>>>> number of computables to compute = ${allSessions.size}")
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> filtering in ${duration} seconds")
val computableList = realm.copyFromRealm(allSessions)
realm.close()
sessionsList.forEach { session ->
if (session.isCashGame()) {
computableList.forEach { session ->
if (true) { // @todo
cgSessions.add(session)
} else {
tSessions.add(session)
}
}
val e = Date()
val duration = (e.time - s.time) / 1000.0
Timber.d(">>> filtering in ${duration} seconds")
val allStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.AVERAGE, Stat.NUMBER_OF_SETS, Stat.AVERAGE_DURATION, Stat.DURATION)
val allSessionGroup = SessionGroup(stringAll, sessionsList, allStats)
val allSessionGroup = ComputableGroup(getString(R.string.all), computableList, allStats)
val cgStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.NET_BB_PER_100_HANDS, Stat.HOURLY_RATE_BB, Stat.AVERAGE, Stat.STANDARD_DEVIATION_HOURLY, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val cgSessionGroup = SessionGroup(stringCashGame, cgSessions, cgStats)
val cgSessionGroup = ComputableGroup(getString(R.string.cash_game), cgSessions, cgStats)
val tStats: List<Stat> = listOf(Stat.NETRESULT, Stat.HOURLY_RATE, Stat.ROI, Stat.WIN_RATIO, Stat.NUMBER_OF_GAMES, Stat.AVERAGE_BUYIN)
val tSessionGroup = SessionGroup(stringTournament, tSessions, tStats)
val tSessionGroup = ComputableGroup(getString(R.string.tournament), tSessions, tStats)
Timber.d(">>>>> Start computations...")

@ -2,15 +2,15 @@ package net.pokeranalytics.android.ui.fragment.components
import io.realm.Realm
import io.realm.RealmResults
import net.pokeranalytics.android.model.realm.Session
import net.pokeranalytics.android.model.realm.ComputableResult
open class SessionObserverFragment : PokerAnalyticsFragment() {
val endedSessions: RealmResults<Session>
val endedSessions: RealmResults<ComputableResult>
init {
val realm = Realm.getDefaultInstance()
this.endedSessions = realm.where(Session::class.java).isNotNull("endDate").findAll()
this.endedSessions = realm.where(ComputableResult::class.java).isNotNull("sessions.endDate").findAll()
this.endedSessions.addChangeListener { _, _ ->
this.sessionsChanged()
}

@ -27,7 +27,7 @@ class ExampleUnitTest : RealmUnitTest() {
// override var estimatedHands: Double = 0.0
// override var bbNetResult: Double = 0.0
// override var bigBlindSessionCount: Int = 0 // 0 or 1
// override var buyin: Double = 0.0
// override var ratedBuyin: Double = 0.0
//
// }
//
@ -35,7 +35,7 @@ class ExampleUnitTest : RealmUnitTest() {
// fun testStats() {
//
// val grades: List<Grade> = listOf(Grade(10.0), Grade(20.0))
// val group = SessionGroup(name = "test", sessions = grades)
// val group = ComputableGroup(name = "test", computables = grades)
//
// val results: ComputedResults = Calculator.compute(group, Calculator.Options())
//

Loading…
Cancel
Save