|
|
|
|
@ -6,6 +6,7 @@ import net.pokeranalytics.android.model.handhistory.* |
|
|
|
|
import net.pokeranalytics.android.model.realm.handhistory.Action |
|
|
|
|
import net.pokeranalytics.android.model.realm.handhistory.Card |
|
|
|
|
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
|
|
|
|
import net.pokeranalytics.android.ui.modules.handhistory.views.CardCentralizer |
|
|
|
|
import net.pokeranalytics.android.ui.modules.handhistory.views.CardsRow |
|
|
|
|
import net.pokeranalytics.android.ui.modules.handhistory.views.PlayerCardsRow |
|
|
|
|
import net.pokeranalytics.android.ui.modules.handhistory.views.StreetCardsRow |
|
|
|
|
@ -21,7 +22,44 @@ enum class HHKeyboard { |
|
|
|
|
CARD; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class HHBuilder { |
|
|
|
|
interface ActionManager { |
|
|
|
|
fun getStreetLastSignificantAction(street: Street, index: Int): ComputedAction? |
|
|
|
|
fun getPreviouslyCommittedAmount(index: Int): Double? |
|
|
|
|
fun selectAction(index: Int, actionType: Action.Type) : List<Int>? |
|
|
|
|
fun getStreetNextCalls(index: Int): List<ComputedAction> |
|
|
|
|
fun getPlayerNextStreetActions(index: Int): List<ComputedAction> |
|
|
|
|
fun dropNextActions(index: Int): Boolean |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class ActionList : ArrayList<ComputedAction>() { |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Keep the first [n] elements |
|
|
|
|
*/ |
|
|
|
|
fun keepFirst(n: Int) { |
|
|
|
|
val cut = this.take(n) |
|
|
|
|
this.clear() |
|
|
|
|
this.addAll(cut) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the first action of the given [street] |
|
|
|
|
*/ |
|
|
|
|
fun firstStreetAction(street: Street): ComputedAction { |
|
|
|
|
return this.first { it.street == street } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the last action index of the street, for the action at [index] |
|
|
|
|
*/ |
|
|
|
|
fun lastIndexOfStreet(index: Int): Int { |
|
|
|
|
val street = this[index].street |
|
|
|
|
return this.last { it.street == street }.action.index |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
class HHBuilder : CardCentralizer, ActionManager { |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* The hand history |
|
|
|
|
@ -36,7 +74,7 @@ class HHBuilder { |
|
|
|
|
/*** |
|
|
|
|
* All actions sorted by index |
|
|
|
|
*/ |
|
|
|
|
private var sortedActions: MutableList<ComputedAction> = mutableListOf() |
|
|
|
|
private var sortedActions: ActionList = ActionList() |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* The board cards sorted by position |
|
|
|
|
@ -93,12 +131,13 @@ class HHBuilder { |
|
|
|
|
var totalPotSize = 0.0 |
|
|
|
|
|
|
|
|
|
// sorted actions |
|
|
|
|
val computedActions = mutableListOf<ComputedAction>() |
|
|
|
|
val computedActions = ActionList() |
|
|
|
|
val sortedActions = this.handHistory.actions.sortedBy { it.index } |
|
|
|
|
sortedActions.forEach { action -> |
|
|
|
|
totalPotSize += action.effectiveAmount |
|
|
|
|
val position = this.positions.elementAt(action.position) |
|
|
|
|
val ca = ComputedAction( |
|
|
|
|
this, |
|
|
|
|
action, |
|
|
|
|
totalPotSize, |
|
|
|
|
action.positionRemainingStack, |
|
|
|
|
@ -121,11 +160,12 @@ class HHBuilder { |
|
|
|
|
val computedAction = this.actionForIndex(index) |
|
|
|
|
val position = computedAction.position |
|
|
|
|
val lastSignificantAction: ComputedAction? = getStreetLastSignificantAction(computedAction.street, index) |
|
|
|
|
val lastUserAction: ComputedAction? = getLastUserAction(index) |
|
|
|
|
|
|
|
|
|
return if (lastSignificantAction != null) { |
|
|
|
|
return if (lastSignificantAction == null) { |
|
|
|
|
setOf(Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
|
|
|
|
} else { |
|
|
|
|
|
|
|
|
|
val remainingStack = lastUserAction?.playerRemainingStack |
|
|
|
|
val remainingStack = getLastPlayerAction(index)?.playerRemainingStack |
|
|
|
|
val actionAmount = lastSignificantAction.action.amount |
|
|
|
|
|
|
|
|
|
when (lastSignificantAction.action.type) { |
|
|
|
|
@ -156,9 +196,6 @@ class HHBuilder { |
|
|
|
|
throw PAIllegalStateException("We should not handle this action: ${lastSignificantAction.action.type}") |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} else { |
|
|
|
|
setOf(Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
@ -173,7 +210,7 @@ class HHBuilder { |
|
|
|
|
* - null when actions have been deleted, requiring a whole table refresh, because streets might me lost |
|
|
|
|
* - the list of modified action indexes that are not the action at [index] |
|
|
|
|
*/ |
|
|
|
|
fun selectAction(index: Int, actionType: Action.Type) : List<Int>? { |
|
|
|
|
override fun selectAction(index: Int, actionType: Action.Type) : List<Int>? { |
|
|
|
|
|
|
|
|
|
var type = actionType |
|
|
|
|
val computedAction = this.actionForIndex(index) |
|
|
|
|
@ -184,10 +221,10 @@ class HHBuilder { |
|
|
|
|
if (significant != null) { |
|
|
|
|
val betAmount = significant.action.amount |
|
|
|
|
val remainingStack = computedAction.playerRemainingStack |
|
|
|
|
if (remainingStack != null && betAmount != null && remainingStack < betAmount) { |
|
|
|
|
type = Action.Type.CALL_ALLIN |
|
|
|
|
type = if (remainingStack != null && betAmount != null && remainingStack < betAmount) { |
|
|
|
|
Action.Type.CALL_ALLIN |
|
|
|
|
} else { |
|
|
|
|
type = Action.Type.RAISE_ALLIN |
|
|
|
|
Action.Type.RAISE_ALLIN |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
type = Action.Type.BET_ALLIN |
|
|
|
|
@ -196,40 +233,7 @@ class HHBuilder { |
|
|
|
|
|
|
|
|
|
Timber.d(">>> Sets $type at index: $index") |
|
|
|
|
|
|
|
|
|
var structureModified = false |
|
|
|
|
val actionPreviouslySet = (computedAction.action.type != null) |
|
|
|
|
|
|
|
|
|
// set type + reset amount if type change |
|
|
|
|
if (computedAction.action.type != type) { |
|
|
|
|
computedAction.action.type = type |
|
|
|
|
computedAction.action.amount = null |
|
|
|
|
|
|
|
|
|
if (actionPreviouslySet) { |
|
|
|
|
structureModified = dropNextActionsIfNecessary(index) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// define action amounts if possible |
|
|
|
|
when (type) { |
|
|
|
|
Action.Type.CALL -> { |
|
|
|
|
val significantAction = getStreetLastSignificantAction(computedAction.street, index - 1) |
|
|
|
|
?: throw PAIllegalStateException("There must be a previously set significant action for a call to be set") |
|
|
|
|
val significantAmount = significantAction.action.amount |
|
|
|
|
?: throw PAIllegalStateException("There must be a set amount on the action for the call to be set") |
|
|
|
|
val committedAmount = getPreviouslyCommittedAmount(index) ?: 0.0 |
|
|
|
|
computedAction.setEffectiveAmount(significantAmount - committedAmount) |
|
|
|
|
} |
|
|
|
|
Action.Type.CALL_ALLIN -> { |
|
|
|
|
computedAction.setEffectiveAmount(computedAction.playerRemainingStack!!) |
|
|
|
|
} |
|
|
|
|
Action.Type.STRADDLE -> { |
|
|
|
|
// TODO |
|
|
|
|
} |
|
|
|
|
Action.Type.BET_ALLIN, Action.Type.RAISE_ALLIN, Action.Type.UNDEFINED_ALLIN -> { |
|
|
|
|
computedAction.action.amount = computedAction.playerRemainingStack |
|
|
|
|
} |
|
|
|
|
else -> {} |
|
|
|
|
} |
|
|
|
|
var structureModified = computedAction.setType(type) // false |
|
|
|
|
|
|
|
|
|
// Automatically sets action for the previous empty actions |
|
|
|
|
val modifiedActions = mutableListOf<ComputedAction>() |
|
|
|
|
@ -249,6 +253,15 @@ class HHBuilder { |
|
|
|
|
return modifiedActions.map { this.rowRepresentables.indexOf(it) } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Sets the amount for the action at the provided [index] |
|
|
|
|
*/ |
|
|
|
|
fun setAmount(index: Int, amount: Double) { |
|
|
|
|
val computedAction = this.actionForIndex(index) |
|
|
|
|
Timber.d(">>> Sets $amount at index: $index, for action ${computedAction.action.type}") |
|
|
|
|
computedAction.setAmount(amount) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Adds, if necessary, new ComputedAction for players that needs to act |
|
|
|
|
* Also adds, if necessary, the Street separators and board selectors |
|
|
|
|
@ -277,7 +290,9 @@ class HHBuilder { |
|
|
|
|
* Recreates the actions after [index] |
|
|
|
|
*/ |
|
|
|
|
private fun removeActionsIfNecessary(index: Int) { |
|
|
|
|
this.sortedActions = this.sortedActions.take(index + 1).toMutableList() |
|
|
|
|
|
|
|
|
|
this.sortedActions.keepFirst(index + 1) |
|
|
|
|
|
|
|
|
|
addsFollowupActionsIfNecessary(index) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@ -291,7 +306,7 @@ class HHBuilder { |
|
|
|
|
val street = this.actionForIndex(index).street |
|
|
|
|
|
|
|
|
|
val refAction = getStreetLastSignificantAction(street, index) |
|
|
|
|
?: this.actionForIndex(this.firstIndexOfStreet(street)) |
|
|
|
|
?: this.sortedActions.firstStreetAction(street) |
|
|
|
|
|
|
|
|
|
val refIndex = refAction.action.index |
|
|
|
|
val refIndexPosition = refAction.position |
|
|
|
|
@ -331,7 +346,7 @@ class HHBuilder { |
|
|
|
|
action.position = position.ordinal |
|
|
|
|
action.street = street |
|
|
|
|
val computedAction = |
|
|
|
|
ComputedAction( |
|
|
|
|
ComputedAction(this, |
|
|
|
|
action, |
|
|
|
|
currentPotSize, |
|
|
|
|
remainingStack, |
|
|
|
|
@ -366,7 +381,7 @@ class HHBuilder { |
|
|
|
|
*/ |
|
|
|
|
private fun createNextStreetIfNecessary(index: Int) : Boolean { |
|
|
|
|
val nextStreet = isStreetActionClosed(index) |
|
|
|
|
if (nextStreet != null) { |
|
|
|
|
if (nextStreet != null && this.sortedActions.firstOrNull { it.street == nextStreet } == null) { |
|
|
|
|
createStreet(nextStreet) |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
@ -415,71 +430,30 @@ class HHBuilder { |
|
|
|
|
* following an action change. |
|
|
|
|
* We want drop all non-auto added rows after the index |
|
|
|
|
*/ |
|
|
|
|
private fun dropNextActionsIfNecessary(index: Int): Boolean { |
|
|
|
|
override fun dropNextActions(index: Int): Boolean { |
|
|
|
|
|
|
|
|
|
val street = this.actionForIndex(index).street |
|
|
|
|
val firstIndexOfStreet = this.firstIndexOfStreet(street) |
|
|
|
|
val activePositions = activePositions(firstIndexOfStreet) |
|
|
|
|
val defaultRowsCount = when (street) { |
|
|
|
|
Street.PREFLOP -> activePositions.size + 2 |
|
|
|
|
else -> activePositions.size |
|
|
|
|
} |
|
|
|
|
val sizeBefore = this.sortedActions.size |
|
|
|
|
// val keptRowsIndex = max(index, firstIndexOfStreet + defaultRowsCount) |
|
|
|
|
this.sortedActions = this.sortedActions.take(index + 1).toMutableList() |
|
|
|
|
|
|
|
|
|
this.sortedActions.keepFirst(index + 1) |
|
|
|
|
|
|
|
|
|
this.updateFollowupActions(index) |
|
|
|
|
this.createRowRepresentation() |
|
|
|
|
val sizeAfter = this.sortedActions.size |
|
|
|
|
|
|
|
|
|
val sizeAfter = this.sortedActions.size |
|
|
|
|
return sizeAfter != sizeBefore |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private fun firstIndexOfStreet(street: Street): Int { |
|
|
|
|
return this.sortedActions.first { it.street == street }.action.index |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Sets the amount for the action at the provided [index] |
|
|
|
|
* In the case of a RAISE_ALLIN, check if it's a CALL_ALLIN |
|
|
|
|
*/ |
|
|
|
|
fun setAmount(index: Int, amount: Double) { |
|
|
|
|
|
|
|
|
|
val computedAction = this.actionForIndex(index) |
|
|
|
|
Timber.d(">>> Sets $amount at index: $index, for action ${computedAction.action.type}") |
|
|
|
|
|
|
|
|
|
val committedAmount = getPreviouslyCommittedAmount(index) ?: 0.0 |
|
|
|
|
computedAction.setAmount(amount, committedAmount) |
|
|
|
|
|
|
|
|
|
when (computedAction.action.type) { |
|
|
|
|
Action.Type.BET, Action.Type.RAISE, Action.Type.BET_ALLIN, Action.Type.RAISE_ALLIN -> { |
|
|
|
|
|
|
|
|
|
getStreetNextCalls(index).forEach { |
|
|
|
|
val playerCommittedAmount = getPreviouslyCommittedAmount(it.action.index) ?: 0.0 |
|
|
|
|
it.setEffectiveAmount(amount) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// verify if the raise is not a call after all |
|
|
|
|
if (computedAction.action.type == Action.Type.RAISE_ALLIN) { |
|
|
|
|
|
|
|
|
|
getStreetLastSignificantAction(computedAction.street, index - 1)?.action?.amount?.let { significantActionAmount -> |
|
|
|
|
val askedAmount = significantActionAmount - committedAmount |
|
|
|
|
|
|
|
|
|
computedAction.playerRemainingStack?.let { remainingStack -> |
|
|
|
|
if (remainingStack < askedAmount) { |
|
|
|
|
selectAction(index, Action.Type.CALL_ALLIN) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else -> {} |
|
|
|
|
override fun getPlayerNextStreetActions(index: Int): List<ComputedAction> { |
|
|
|
|
val computedAction = this.sortedActions[index] |
|
|
|
|
return this.sortedActions.drop(index + 1).filter { |
|
|
|
|
it.street == computedAction.street && it.position == computedAction.position |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the committed amount by the player for the street at the current [index] |
|
|
|
|
*/ |
|
|
|
|
private fun getPreviouslyCommittedAmount(index: Int): Double? { |
|
|
|
|
override fun getPreviouslyCommittedAmount(index: Int): Double? { |
|
|
|
|
val computedAction = this.actionForIndex(index) |
|
|
|
|
val position = computedAction.position |
|
|
|
|
val street = computedAction.action.street |
|
|
|
|
@ -501,45 +475,34 @@ class HHBuilder { |
|
|
|
|
return null |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the last significant player action, if any, for the action at the provided [index] |
|
|
|
|
*/ |
|
|
|
|
override fun getStreetLastSignificantAction(street: Street, index: Int): ComputedAction? { |
|
|
|
|
val previousActions = this.sortedActions.take(index + 1).filter { it.street == street } |
|
|
|
|
return previousActions.lastOrNull { it.action.isActionSignificant } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns all "CALL" ComputedAction between the [index] and the next significant action |
|
|
|
|
*/ |
|
|
|
|
private fun getStreetNextCalls(index: Int): List<ComputedAction> { |
|
|
|
|
val streetNextSignificantIndex = getStreetNextSignificantAction(index)?.action?.index ?: lastIndexOfStreet(index) |
|
|
|
|
override fun getStreetNextCalls(index: Int): List<ComputedAction> { |
|
|
|
|
val streetNextSignificantIndex = getStreetNextSignificantAction(index)?.action?.index ?: this.sortedActions.lastIndexOfStreet(index) |
|
|
|
|
return this.sortedActions.filter { |
|
|
|
|
it.action.index in ((index + 1) until streetNextSignificantIndex) |
|
|
|
|
&& (it.action.type?.isCall ?: false) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the last action index of the street, for the action at [index] |
|
|
|
|
*/ |
|
|
|
|
private fun lastIndexOfStreet(index: Int): Int { |
|
|
|
|
val street = this.actionForIndex(index).street |
|
|
|
|
return this.sortedActions.last { it.street == street }.action.index |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the last user action, if any, for the action at the provided [index] |
|
|
|
|
*/ |
|
|
|
|
private fun getLastUserAction(index: Int): ComputedAction? { |
|
|
|
|
private fun getLastPlayerAction(index: Int): ComputedAction? { |
|
|
|
|
val action = this.actionForIndex(index).action |
|
|
|
|
// Timber.d("**** this.sortedActions.size = ${this.sortedActions.size}") |
|
|
|
|
val previousActions = this.sortedActions.take(index) |
|
|
|
|
// Timber.d("**** this.sortedActions.size = ${this.sortedActions.size}") |
|
|
|
|
return previousActions.lastOrNull { it.action.position == action.position } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the last significant player action, if any, for the action at the provided [index] |
|
|
|
|
*/ |
|
|
|
|
private fun getStreetLastSignificantAction(street: Street, index: Int): ComputedAction? { |
|
|
|
|
// Timber.d("**** this.sortedActions.size = ${this.sortedActions.size}") |
|
|
|
|
val previousActions = this.sortedActions.take(index + 1).filter { it.street == street } |
|
|
|
|
// Timber.d("**** this.sortedActions.size = ${this.sortedActions.size}") |
|
|
|
|
return previousActions.lastOrNull { it.action.isActionSignificant } |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
* Returns the next significant player action in the street, if any, for the action at the provided [index] |
|
|
|
|
@ -565,10 +528,7 @@ class HHBuilder { |
|
|
|
|
*/ |
|
|
|
|
fun setNumberOfPlayers(playerCount: Int) { |
|
|
|
|
this.handHistory.numberOfPlayers = playerCount |
|
|
|
|
this.positions = |
|
|
|
|
Position.positionsPerPlayers( |
|
|
|
|
playerCount |
|
|
|
|
) |
|
|
|
|
this.positions = Position.positionsPerPlayers(playerCount) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
@ -618,6 +578,7 @@ class HHBuilder { |
|
|
|
|
* Adds a card with the selected [value] |
|
|
|
|
*/ |
|
|
|
|
fun cardValueSelected(value: Card.Value, currentSelection: HHSelection) { |
|
|
|
|
this.lastValue = value |
|
|
|
|
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
|
|
|
|
row.valueSelected(value) |
|
|
|
|
} |
|
|
|
|
@ -639,10 +600,9 @@ class HHBuilder { |
|
|
|
|
* Deletes all the card of the selected street |
|
|
|
|
*/ |
|
|
|
|
fun clearCards(currentSelection: HHSelection) { |
|
|
|
|
|
|
|
|
|
this.lastValue = null |
|
|
|
|
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
|
|
|
|
row.clear() |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*** |
|
|
|
|
@ -807,5 +767,25 @@ class HHBuilder { |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Card Centralizer |
|
|
|
|
|
|
|
|
|
private val usedCards: List<Card> |
|
|
|
|
get() { |
|
|
|
|
// TODO is my list always stored in the handHistory, or not? |
|
|
|
|
return listOf() |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private var lastValue: Card.Value? = null |
|
|
|
|
|
|
|
|
|
override fun isValueAvailable(value: Card.Value): Boolean { |
|
|
|
|
val usedValues = this.usedCards.filter { it.value == value.value } |
|
|
|
|
return usedValues.size < 4 |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
override fun isSuitAvailable(suit: Card.Suit, value: Card.Value?): Boolean { |
|
|
|
|
val usedValues = this.usedCards.filter { it.value == value?.value } |
|
|
|
|
return !usedValues.map { it.suit }.contains(suit) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|