parent
f8ab6489c5
commit
b203763882
@ -1,791 +0,0 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory |
||||
|
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
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 |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||
import net.pokeranalytics.android.util.extensions.formatted |
||||
import timber.log.Timber |
||||
import kotlin.math.max |
||||
|
||||
enum class HHKeyboard { |
||||
ACTION, |
||||
AMOUNT, |
||||
CARD; |
||||
} |
||||
|
||||
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 |
||||
*/ |
||||
private var handHistory: HandHistory |
||||
set(value) { |
||||
field = value |
||||
setNumberOfPlayers(value.numberOfPlayers) |
||||
load() |
||||
} |
||||
|
||||
/*** |
||||
* All actions sorted by index |
||||
*/ |
||||
private var sortedActions: ActionList = ActionList() |
||||
|
||||
/*** |
||||
* The board cards sorted by position |
||||
*/ |
||||
// private lateinit var boardManager: BoardManager |
||||
|
||||
/*** |
||||
* A LinkedHashSet containing the sorted positions at the table |
||||
*/ |
||||
var positions: LinkedHashSet<Position> = linkedSetOf() |
||||
|
||||
/*** |
||||
* The maximum number of cards in a player's hand |
||||
*/ |
||||
private var playerHandMaxCards: Int? = null |
||||
set(value) { |
||||
field = value |
||||
this.rowRepresentables.filterIsInstance<PlayerCardsRow>().forEach { |
||||
it.maxCards = value |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Creates a builder using a [handSetup] |
||||
* Also creates a new Hand History and configures it according to the [handSetup] |
||||
*/ |
||||
constructor(handSetup: HandSetup) { |
||||
val handHistory = HandHistory() |
||||
handHistory.configure(handSetup) |
||||
this.playerHandMaxCards = handSetup.game?.playerHandMaxCards |
||||
this.handHistory = handHistory |
||||
} |
||||
|
||||
/*** |
||||
* Creates a builder using the parameter [handHistory] |
||||
*/ |
||||
constructor(handHistory: HandHistory) { |
||||
this.handHistory = handHistory |
||||
} |
||||
|
||||
/*** |
||||
* Returns the action |
||||
*/ |
||||
private fun actionForIndex(index: Int) : ComputedAction { |
||||
return this.sortedActions[index] |
||||
} |
||||
|
||||
/*** |
||||
* Fills the [sortedActions] variable with the proper actions |
||||
* Pre-computes the potsizes for the video export |
||||
*/ |
||||
private fun load() { |
||||
|
||||
var totalPotSize = 0.0 |
||||
|
||||
// sorted actions |
||||
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, |
||||
position |
||||
) |
||||
computedActions.add(ca) |
||||
} |
||||
this.sortedActions = computedActions |
||||
|
||||
// this.boardManager = BoardManager(this.handHistory.board, this) |
||||
|
||||
this.createRowRepresentation() |
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of available user actions at [index] |
||||
*/ |
||||
fun availableActions(index: Int) : Set<Action.Type> { |
||||
|
||||
val computedAction = this.actionForIndex(index) |
||||
val position = computedAction.position |
||||
val lastSignificantAction: ComputedAction? = getStreetLastSignificantAction(computedAction.street, index) |
||||
|
||||
return if (lastSignificantAction == null) { |
||||
setOf(Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} else { |
||||
|
||||
val remainingStack = getLastPlayerAction(index)?.playerRemainingStack |
||||
val actionAmount = lastSignificantAction.action.amount |
||||
|
||||
when (lastSignificantAction.action.type) { |
||||
Action.Type.POST_SB, Action.Type.POST_BB, Action.Type.STRADDLE -> { |
||||
if (position == lastSignificantAction.position) { |
||||
setOf(Action.Type.FOLD, Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} else { |
||||
setOf(Action.Type.STRADDLE, Action.Type.FOLD, Action.Type.CALL, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
Action.Type.BET, Action.Type.RAISE -> { |
||||
if (remainingStack != null && actionAmount != null && remainingStack <= actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL_ALLIN) |
||||
} else { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL, Action.Type.RAISE, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
Action.Type.RAISE_ALLIN, Action.Type.BET_ALLIN -> { |
||||
if (remainingStack != null && actionAmount != null && remainingStack <= actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL_ALLIN) |
||||
} else if (activePositions(index).size == 2 && remainingStack != null && actionAmount != null && remainingStack > actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL) |
||||
} else { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL, Action.Type.RAISE, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
else -> { |
||||
throw PAIllegalStateException("We should not handle this action: ${lastSignificantAction.action.type}") |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* Selects an action type for the action at the provided [index] |
||||
* In case of UNDEFINED_ALLIN, we define which type of allin it is. |
||||
* If the user changes the current action, |
||||
* for convenience we remove all the following actions to avoid managing complex cases |
||||
* Also calculates the player effective amounts in proper cases |
||||
* Returns either: |
||||
* - 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] |
||||
*/ |
||||
override fun selectAction(index: Int, actionType: Action.Type) : List<Int>? { |
||||
|
||||
var type = actionType |
||||
val computedAction = this.actionForIndex(index) |
||||
|
||||
// define allin type |
||||
if (type == Action.Type.UNDEFINED_ALLIN) { |
||||
val significant = getStreetLastSignificantAction(computedAction.street, index - 1) |
||||
if (significant != null) { |
||||
val betAmount = significant.action.amount |
||||
val remainingStack = computedAction.playerRemainingStack |
||||
type = if (remainingStack != null && betAmount != null && remainingStack < betAmount) { |
||||
Action.Type.CALL_ALLIN |
||||
} else { |
||||
Action.Type.RAISE_ALLIN |
||||
} |
||||
} else { |
||||
type = Action.Type.BET_ALLIN |
||||
} |
||||
} |
||||
|
||||
Timber.d(">>> Sets $type at index: $index") |
||||
|
||||
var structureModified = computedAction.setType(type) // false |
||||
|
||||
// Automatically sets action for the previous empty actions |
||||
val modifiedActions = mutableListOf<ComputedAction>() |
||||
getPreviousEmptyActions(index).forEach { |
||||
modifiedActions.add(it) |
||||
val lastSignificant = getStreetLastSignificantAction(computedAction.street, index - 1) |
||||
it.action.type = if (lastSignificant != null) { |
||||
Action.Type.FOLD |
||||
} else { |
||||
Action.Type.CHECK |
||||
} |
||||
} |
||||
|
||||
structureModified = this.updateFollowupActions(index) || structureModified |
||||
|
||||
if (structureModified) return null |
||||
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 |
||||
* Returns true if the action list has been modified |
||||
*/ |
||||
private fun updateFollowupActions(index: Int) : Boolean { |
||||
|
||||
val computedAction = this.actionForIndex(index) |
||||
val type = computedAction.action.type |
||||
|
||||
var actionsChanged = addsFollowupActionsIfNecessary(index) |
||||
|
||||
when (type?.isSignificant) { |
||||
false -> { // closes the action, pass to next street if necessary |
||||
if (type == Action.Type.CALL_ALLIN) { // sometimes we can go from RAISE_ALLIN to CALL_IN, changing the actions required |
||||
removeActionsIfNecessary(index) |
||||
} |
||||
actionsChanged = createNextStreetIfNecessary(index) || actionsChanged |
||||
} |
||||
else -> {} |
||||
} |
||||
return actionsChanged |
||||
} |
||||
|
||||
/*** |
||||
* Recreates the actions after [index] |
||||
*/ |
||||
private fun removeActionsIfNecessary(index: Int) { |
||||
|
||||
this.sortedActions.keepFirst(index + 1) |
||||
|
||||
addsFollowupActionsIfNecessary(index) |
||||
} |
||||
|
||||
/*** |
||||
* Check if some positions are still required to play, |
||||
* and adds new actions if none exists after [index] |
||||
* Returns true if actions have been added |
||||
*/ |
||||
private fun addsFollowupActionsIfNecessary(index: Int) : Boolean { |
||||
|
||||
val street = this.actionForIndex(index).street |
||||
|
||||
val refAction = getStreetLastSignificantAction(street, index) |
||||
?: this.sortedActions.firstStreetAction(street) |
||||
|
||||
val refIndex = refAction.action.index |
||||
val refIndexPosition = refAction.position |
||||
|
||||
val activePositions = activePositions(refIndex) |
||||
activePositions.remove(refIndexPosition) |
||||
|
||||
// We want to remove positions that already have an action after [refIndex] |
||||
for (i in refIndex + 1 until this.sortedActions.size) { |
||||
val ca = this.actionForIndex(i) |
||||
activePositions.remove(ca.position) |
||||
} |
||||
|
||||
// Circularly adds an action for missing positions |
||||
val firstPositionAfterCurrent = max(0, activePositions.indexOfFirst { it.ordinal > refIndexPosition.ordinal }) |
||||
for (i in 0 until activePositions.size) { |
||||
val position = activePositions[(firstPositionAfterCurrent + i) % activePositions.size] |
||||
this.addNewEmptyAction(position, refAction.street, refAction.totalPotSize, lastRemainingStack(position, index)) |
||||
} |
||||
|
||||
return activePositions.isNotEmpty() |
||||
} |
||||
|
||||
/*** |
||||
* Returns the last remaining stack of the player, if available |
||||
*/ |
||||
private fun lastRemainingStack(position: Position, index: Int): Double? { |
||||
return this.sortedActions.take(index).lastOrNull { it.position == position }?.playerRemainingStack |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new action at the end of the action stack, and in the HH representation |
||||
*/ |
||||
private fun addNewEmptyAction(position: Position, street: Street, currentPotSize: Double, remainingStack: Double?) { |
||||
val action = Action() |
||||
action.index = this.sortedActions.size |
||||
action.position = position.ordinal |
||||
action.street = street |
||||
val computedAction = |
||||
ComputedAction(this, |
||||
action, |
||||
currentPotSize, |
||||
remainingStack, |
||||
position |
||||
) |
||||
this.sortedActions.add(computedAction) |
||||
this.rowRepresentables.add(computedAction) |
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of position still in play before the given [index] |
||||
*/ |
||||
private fun activePositions(index: Int, showDown: Boolean = false): MutableList<Position> { |
||||
val oustedPositions = this.sortedActions.take(index + 1) |
||||
.filter { |
||||
if (showDown) { |
||||
it.action.type == Action.Type.FOLD |
||||
} else { |
||||
it.action.type?.isPullOut ?: false |
||||
} |
||||
} |
||||
.map { it.position } |
||||
|
||||
val allPositions = this.positions.clone() as LinkedHashSet<Position> |
||||
allPositions.removeAll(oustedPositions) |
||||
return allPositions.toMutableList() |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new street if no player needs to act |
||||
* Returns true if a street has been created |
||||
*/ |
||||
private fun createNextStreetIfNecessary(index: Int) : Boolean { |
||||
val nextStreet = isStreetActionClosed(index) |
||||
if (nextStreet != null && this.sortedActions.firstOrNull { it.street == nextStreet } == null) { |
||||
createStreet(nextStreet) |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new street: |
||||
* - Adds a Street Header |
||||
* - Adds a Board if necessary |
||||
* - Adds empty actions for the remaining players |
||||
*/ |
||||
private fun createStreet(street: Street) { |
||||
|
||||
val lastComputedAction = this.sortedActions.last() |
||||
val totalPotSize = lastComputedAction.totalPotSize |
||||
|
||||
addStreetHeader(this.rowRepresentables, street) |
||||
|
||||
val lastActionIndex = lastComputedAction.action.index |
||||
val isShowDown = street == Street.SUMMARY |
||||
activePositions(lastActionIndex, isShowDown).sortedBy { it.ordinal }.forEach { |
||||
|
||||
when (street) { |
||||
Street.SUMMARY -> { |
||||
this.rowRepresentables.add(PlayerCardsRow(it)) |
||||
} |
||||
else -> { |
||||
addNewEmptyAction(it, street, totalPotSize, lastRemainingStack(it, lastActionIndex)) |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of empty actions before the action at the given [index] |
||||
*/ |
||||
private fun getPreviousEmptyActions(index: Int) : List<ComputedAction> { |
||||
val street = this.actionForIndex(index).street |
||||
return this.sortedActions.take(index).filter { it.street == street && it.action.type == null } |
||||
} |
||||
|
||||
/*** |
||||
* Removes the actions, not the automatically created ones, |
||||
* following an action change. |
||||
* We want drop all non-auto added rows after the index |
||||
*/ |
||||
override fun dropNextActions(index: Int): Boolean { |
||||
|
||||
val sizeBefore = this.sortedActions.size |
||||
|
||||
this.sortedActions.keepFirst(index + 1) |
||||
|
||||
this.updateFollowupActions(index) |
||||
this.createRowRepresentation() |
||||
|
||||
val sizeAfter = this.sortedActions.size |
||||
return sizeAfter != sizeBefore |
||||
} |
||||
|
||||
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] |
||||
*/ |
||||
override fun getPreviouslyCommittedAmount(index: Int): Double? { |
||||
val computedAction = this.actionForIndex(index) |
||||
val position = computedAction.position |
||||
val street = computedAction.action.street |
||||
|
||||
val previousActions = this.sortedActions.take(index) |
||||
val previousComputedAction = previousActions.lastOrNull { |
||||
it.position == position && it.street == street |
||||
} |
||||
|
||||
previousComputedAction?.action?.let { previousAction -> |
||||
|
||||
return when (previousAction.type) { |
||||
Action.Type.POST_BB, Action.Type.POST_SB, Action.Type.STRADDLE, Action.Type.BET, Action.Type.RAISE -> previousAction.amount |
||||
Action.Type.CALL -> getStreetLastSignificantAction(previousAction.street, previousAction.index)?.action?.amount |
||||
else -> null |
||||
} |
||||
} |
||||
|
||||
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 |
||||
*/ |
||||
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 user action, if any, for the action at the provided [index] |
||||
*/ |
||||
private fun getLastPlayerAction(index: Int): ComputedAction? { |
||||
val action = this.actionForIndex(index).action |
||||
val previousActions = this.sortedActions.take(index) |
||||
return previousActions.lastOrNull { it.action.position == action.position } |
||||
} |
||||
|
||||
|
||||
/*** |
||||
* Returns the next significant player action in the street, if any, for the action at the provided [index] |
||||
*/ |
||||
private fun getStreetNextSignificantAction(index: Int): ComputedAction? { |
||||
val street = this.actionForIndex(index).street |
||||
val nextActions = this.sortedActions.drop(index + 1).filter { it.street == street } |
||||
return nextActions.firstOrNull { it.action.isActionSignificant } |
||||
} |
||||
|
||||
/*** |
||||
* Returns a list of positions at the provided [index] |
||||
*/ |
||||
fun positionsAtIndex(index: Int): List<Position> { |
||||
val currentStreet = this.actionForIndex(index).street |
||||
val streetActions = this.sortedActions.filter { it.street == currentStreet } |
||||
return streetActions.drop(index + 1).map { it.position } |
||||
} |
||||
|
||||
/*** |
||||
* Sets the number of players playing the hand |
||||
* Defines the appropriate positions for this player count |
||||
*/ |
||||
fun setNumberOfPlayers(playerCount: Int) { |
||||
this.handHistory.numberOfPlayers = playerCount |
||||
this.positions = Position.positionsPerPlayers(playerCount) |
||||
} |
||||
|
||||
/*** |
||||
* Returns the index of a ComputedAction given its [rowRepresentableIndex] |
||||
*/ |
||||
fun indexOfComputedAction(rowRepresentableIndex: Int) : Int { |
||||
val computedAction = this.rowRepresentables[rowRepresentableIndex] as ComputedAction |
||||
return computedAction.action.index |
||||
} |
||||
|
||||
/*** |
||||
* Finds the index of the first incomplete action, if existing |
||||
* If the same selection is the same than current, pass to the next row |
||||
*/ |
||||
fun findIndexForEdition(startIndex: Int): HHSelection? { |
||||
|
||||
this.rowRepresentables.forEachIndexed { index, rowRepresentable -> |
||||
|
||||
if (index >= startIndex && rowRepresentable is HandHistoryRow) { |
||||
val foundKeyboard = rowRepresentable.keyboardForCompletion() |
||||
if (foundKeyboard != null) { |
||||
return HHSelection(index, foundKeyboard) |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/*** |
||||
* Returns the index, strictly superior to [actionIndexForSelection], |
||||
* of the next action of the given [position] |
||||
*/ |
||||
fun nextActionIndex(actionIndexForSelection: Int, position: Position): Int { |
||||
|
||||
var i = actionIndexForSelection + 1 |
||||
while (true) { |
||||
val computedAction = this.actionForIndex(i) |
||||
if (computedAction.position == position) { |
||||
return this.rowRepresentables.indexOf(computedAction) |
||||
} |
||||
i++ |
||||
if (i > this.sortedActions.size) throw PAIllegalStateException("algo sucks") |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* 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) |
||||
} |
||||
|
||||
/*** |
||||
* Either affect the [suit] to the current card, |
||||
* or create a Card with an empty value and the [suit] |
||||
*/ |
||||
fun cardSuitSelected(suit: Card.Suit, currentSelection: HHSelection) { |
||||
|
||||
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
||||
row.suitSelected(suit) |
||||
|
||||
// TODO do we want to store the information right now ? |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* 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() |
||||
} |
||||
|
||||
/*** |
||||
* Delete the last property of the last Card of the selected street |
||||
* We don't want empty Card (value + suit), |
||||
* so we delete the whole card if both information are null |
||||
*/ |
||||
fun deleteLastCardProperty(currentSelection: HHSelection) { |
||||
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
||||
row.deleteLastCardProperty() |
||||
} |
||||
|
||||
/*** |
||||
* When a card selection is ended, |
||||
* sets the playerHandMaxCards with the number of cards set to the player |
||||
*/ |
||||
fun cardSelectionEnded(index: Int) { |
||||
val cardsRow = this.rowRepresentables[index] as CardsRow |
||||
if (cardsRow is PlayerCardsRow) { |
||||
this.playerHandMaxCards = cardsRow.cards.size |
||||
} |
||||
} |
||||
|
||||
// BoardChangedListener |
||||
|
||||
/*** |
||||
* Called when the board has changed |
||||
*/ |
||||
fun boardChanged(cards: List<Card>) { |
||||
this.rowRepresentables.filterIsInstance<StreetCardsRow>().forEach { |
||||
// it.cards = cards |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Saves the current hand state in the database |
||||
*/ |
||||
private fun save() { |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* The list of row representables, generated from the hand history actions |
||||
*/ |
||||
var rowRepresentables = mutableListOf<RowRepresentable>() |
||||
private set |
||||
|
||||
private fun createRowRepresentation() { |
||||
val rows: MutableList<RowRepresentable> = mutableListOf() |
||||
|
||||
rows.add(CustomizableRowRepresentable(customViewType = HandRowType.HEADER, resId = R.string.settings, value = "")) |
||||
|
||||
Street.values().forEach { street -> |
||||
|
||||
val actions = this.sortedActions.filter { it.street == street } |
||||
|
||||
when (street) { |
||||
Street.SUMMARY -> { |
||||
val lastActionIndex = this.sortedActions.size - 1 |
||||
|
||||
if (activePositions(lastActionIndex).size < 2 || isStreetActionClosed(lastActionIndex) == Street.SUMMARY) { |
||||
addStreetHeader(rows, street) |
||||
|
||||
activePositions(lastActionIndex).forEach { |
||||
val positionIndex = this.positions.indexOf(it) |
||||
val playerCardsRow = PlayerCardsRow(it, this.handHistory.cardsForPosition(positionIndex), this.playerHandMaxCards) |
||||
this.rowRepresentables.add(playerCardsRow) |
||||
} |
||||
} |
||||
} |
||||
else -> { |
||||
if (actions.isNotEmpty()) { |
||||
addStreetHeader(rows, street) |
||||
rows.addAll(actions) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
this.rowRepresentables = rows |
||||
} |
||||
|
||||
/*** |
||||
* This function evaluates whether the street, identified by an action [index], |
||||
* has its action closed, meaning no more player are required to play. |
||||
* In case of closing, the function returns the next logical Street |
||||
*/ |
||||
private fun isStreetActionClosed(index: Int) : Street? { |
||||
|
||||
val currentStreet = this.actionForIndex(index).street |
||||
|
||||
getStreetLastSignificantAction(currentStreet, index)?.let { significantAction -> |
||||
|
||||
val activePositions = activePositions(index) |
||||
val activePlayerCount = activePositions.size // don't move this line because of removes |
||||
|
||||
// Blinds must act |
||||
if (!significantAction.action.type!!.isBlind) { |
||||
activePositions.remove(significantAction.position) |
||||
} |
||||
|
||||
val significantIndex = significantAction.action.index |
||||
for (i in significantIndex + 1 until this.sortedActions.size) { |
||||
val ca = this.sortedActions[i] |
||||
val type = ca.action.type |
||||
if (type != null && !type.isSignificant) { // Calls and folds |
||||
activePositions.remove(ca.position) |
||||
} |
||||
} |
||||
|
||||
if (activePositions.isEmpty()) { |
||||
|
||||
return if (activePlayerCount >= 2 && currentStreet != Street.RIVER) { |
||||
currentStreet.next |
||||
} else { |
||||
Street.SUMMARY |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
val allPassive = this.sortedActions |
||||
.filter { it.street == currentStreet } |
||||
.all { it.action.type?.isPassive ?: false } |
||||
|
||||
if (allPassive) { |
||||
return if (currentStreet != Street.RIVER) { |
||||
currentStreet.next |
||||
} else { |
||||
Street.SUMMARY |
||||
} |
||||
} |
||||
|
||||
return null |
||||
} |
||||
|
||||
/*** |
||||
* Adds a [street] header to a [rowRepresentables] |
||||
*/ |
||||
private fun addStreetHeader(rowRepresentables: MutableList<RowRepresentable>, street: Street) { |
||||
|
||||
val firstIndexOfStreet = this.sortedActions.firstOrNull { it.street == street }?.action?.index |
||||
?: this.sortedActions.size |
||||
|
||||
val potSize = this.sortedActions.take(firstIndexOfStreet).sumByDouble { it.action.effectiveAmount } |
||||
val potString = if (potSize > 0) potSize.formatted() else "" // "" required otherwise random values come up |
||||
|
||||
val headerView = CustomizableRowRepresentable(customViewType = HandRowType.HEADER, resId = street.resId, value = potString) |
||||
rowRepresentables.add(headerView) |
||||
|
||||
if (street.totalBoardCards > 0) { |
||||
|
||||
// get board from last street |
||||
val lastBoardRow = this.rowRepresentables.lastOrNull { it is StreetCardsRow } as? StreetCardsRow |
||||
val cards = lastBoardRow?.cards ?: listOf<Card>() |
||||
|
||||
// create new StreetCardsRow |
||||
val boardView = StreetCardsRow(street, cards) |
||||
rowRepresentables.add(boardView) |
||||
} |
||||
|
||||
} |
||||
|
||||
// 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) |
||||
} |
||||
|
||||
} |
||||
|
||||
@ -0,0 +1,500 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory.model |
||||
|
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
import net.pokeranalytics.android.model.handhistory.Position |
||||
import net.pokeranalytics.android.model.handhistory.Street |
||||
import net.pokeranalytics.android.model.realm.handhistory.Action |
||||
import net.pokeranalytics.android.model.realm.handhistory.HandHistory |
||||
import timber.log.Timber |
||||
|
||||
interface ActionManager { |
||||
fun selectAction(index: Int, actionType: Action.Type) |
||||
fun getStreetLastSignificantAction(street: Street, index: Int): ComputedAction? |
||||
fun getPreviouslyCommittedAmount(index: Int): Double? |
||||
fun getStreetNextCalls(index: Int): List<ComputedAction> |
||||
fun getPlayerNextStreetActions(index: Int): List<ComputedAction> |
||||
fun dropNextActions(index: Int) |
||||
} |
||||
|
||||
interface ActionListListener { |
||||
fun actionCountChanged() |
||||
} |
||||
|
||||
class ActionList(var listener: ActionListListener) : ArrayList<ComputedAction>(), ActionManager { |
||||
|
||||
private var countChanged: Boolean = false |
||||
|
||||
fun load(handHistory: HandHistory) { |
||||
|
||||
this.positions = Position.positionsPerPlayers(handHistory.numberOfPlayers) |
||||
|
||||
var totalPotSize = 0.0 |
||||
|
||||
// sorted actions |
||||
val computedActions = mutableListOf<ComputedAction>() |
||||
val sortedActions = 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, |
||||
position |
||||
) |
||||
computedActions.add(ca) |
||||
} |
||||
this.addAll(computedActions) |
||||
} |
||||
|
||||
/*** |
||||
* A LinkedHashSet containing the sorted positions at the table |
||||
*/ |
||||
var positions: LinkedHashSet<Position> = linkedSetOf() |
||||
|
||||
/*** |
||||
* Selects an action type for the action at the provided [index] |
||||
* In case of UNDEFINED_ALLIN, we define which type of allin it is. |
||||
* If the user changes the current action, |
||||
* for convenience we remove all the following actions to avoid managing complex cases |
||||
* Also calculates the player effective amounts in proper cases |
||||
*/ |
||||
override fun selectAction(index: Int, actionType: Action.Type) { |
||||
|
||||
var type = actionType |
||||
val computedAction = this[index] |
||||
|
||||
// define allin type |
||||
if (type == Action.Type.UNDEFINED_ALLIN) { |
||||
val significant = getStreetLastSignificantAction(computedAction.street, index - 1) |
||||
type = if (significant != null) { |
||||
val betAmount = significant.action.amount |
||||
val remainingStack = computedAction.playerRemainingStack |
||||
if (remainingStack != null && betAmount != null && remainingStack < betAmount) { |
||||
Action.Type.CALL_ALLIN |
||||
} else { |
||||
Action.Type.RAISE_ALLIN |
||||
} |
||||
} else { |
||||
Action.Type.BET_ALLIN |
||||
} |
||||
} |
||||
|
||||
Timber.d(">>> Sets $type at index: $index") |
||||
|
||||
computedAction.setType(type) // false |
||||
|
||||
// Automatically sets action for the previous empty actions |
||||
val modifiedActions = mutableListOf<ComputedAction>() |
||||
getPreviousEmptyActions(index).forEach { |
||||
modifiedActions.add(it) |
||||
val lastSignificant = getStreetLastSignificantAction(computedAction.street, index - 1) |
||||
it.action.type = if (lastSignificant != null) { |
||||
Action.Type.FOLD |
||||
} else { |
||||
Action.Type.CHECK |
||||
} |
||||
} |
||||
|
||||
this.updateFollowupActions(index) |
||||
|
||||
fireListener() |
||||
} |
||||
|
||||
/*** |
||||
* Fires the listener if the list count has changed |
||||
*/ |
||||
private fun fireListener() { |
||||
if (this.countChanged) { |
||||
this.listener.actionCountChanged() |
||||
this.countChanged = false |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of available user actions at [index] |
||||
*/ |
||||
fun availableActions(index: Int) : Set<Action.Type> { |
||||
|
||||
val computedAction = this[index] |
||||
val position = computedAction.position |
||||
val lastSignificantAction: ComputedAction? = getStreetLastSignificantAction(computedAction.street, index - 1) |
||||
|
||||
return if (lastSignificantAction == null) { |
||||
setOf(Action.Type.FOLD, Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} else { |
||||
val remainingStack = getLastPlayerAction(index)?.playerRemainingStack |
||||
val actionAmount = lastSignificantAction.action.amount |
||||
|
||||
when (lastSignificantAction.action.type) { |
||||
Action.Type.POST_SB, Action.Type.POST_BB, Action.Type.STRADDLE -> { |
||||
if (position == lastSignificantAction.position) { |
||||
setOf(Action.Type.FOLD, Action.Type.CHECK, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} else { |
||||
setOf(Action.Type.STRADDLE, Action.Type.FOLD, Action.Type.CALL, Action.Type.BET, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
Action.Type.BET, Action.Type.RAISE -> { |
||||
if (remainingStack != null && actionAmount != null && remainingStack <= actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL_ALLIN) |
||||
} else { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL, Action.Type.RAISE, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
Action.Type.RAISE_ALLIN, Action.Type.BET_ALLIN -> { |
||||
if (remainingStack != null && actionAmount != null && remainingStack <= actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL_ALLIN) |
||||
} else if (activePositions(index).size == 2 && remainingStack != null && actionAmount != null && remainingStack > actionAmount) { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL) |
||||
} else { |
||||
setOf(Action.Type.FOLD, Action.Type.CALL, Action.Type.RAISE, Action.Type.UNDEFINED_ALLIN) |
||||
} |
||||
} |
||||
else -> { |
||||
throw PAIllegalStateException("We should not handle this action: ${lastSignificantAction.action.type}") |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of position still in play before the given [index] |
||||
*/ |
||||
fun activePositions(index: Int, showDown: Boolean = false): MutableList<Position> { |
||||
val oustedPositions = this.take(index + 1) |
||||
.filter { |
||||
if (showDown) { |
||||
it.action.type == Action.Type.FOLD |
||||
} else { |
||||
it.action.type?.isPullOut ?: false |
||||
} |
||||
} |
||||
.map { it.position } |
||||
|
||||
val allPositions = this.positions.clone() as LinkedHashSet<Position> |
||||
allPositions.removeAll(oustedPositions) |
||||
return allPositions.toMutableList() |
||||
} |
||||
|
||||
/*** |
||||
* Keep the first [n] elements |
||||
*/ |
||||
private fun keepFirst(n: Int) { |
||||
val sizeBefore = this.size |
||||
|
||||
val cut = this.take(n) |
||||
this.clear() |
||||
this.addAll(cut) |
||||
|
||||
val sizeAfter = this.size |
||||
if (sizeBefore != sizeAfter) { |
||||
this.countChanged = true |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Returns the first action of the given [street] |
||||
*/ |
||||
private fun firstStreetAction(street: Street): ComputedAction { |
||||
return this.first { it.street == street } |
||||
} |
||||
|
||||
/*** |
||||
* Returns the last action index of the street, for the action at [index] |
||||
*/ |
||||
private fun lastIndexOfStreet(index: Int): Int { |
||||
val street = this[index].street |
||||
return this.last { it.street == street }.action.index |
||||
} |
||||
|
||||
/*** |
||||
* Returns the list of empty actions before the action at the given [index] |
||||
*/ |
||||
private fun getPreviousEmptyActions(index: Int) : List<ComputedAction> { |
||||
val street = this[index].street |
||||
return this.take(index).filter { it.street == street && it.action.type == null } |
||||
} |
||||
|
||||
/*** |
||||
* Returns the last user action, if any, for the action at the provided [index] |
||||
*/ |
||||
private fun getLastPlayerAction(index: Int): ComputedAction? { |
||||
val action = this[index].action |
||||
val previousActions = this.take(index) |
||||
return previousActions.lastOrNull { it.action.position == action.position } |
||||
} |
||||
|
||||
/*** |
||||
* 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.take(index + 1).filter { it.street == street } |
||||
return previousActions.lastOrNull { it.action.isActionSignificant } |
||||
} |
||||
|
||||
/*** |
||||
* Check if some positions are still required to play, |
||||
* and adds new actions if none exists after [index] |
||||
*/ |
||||
private fun addsFollowupActionsIfNecessary(index: Int) { |
||||
|
||||
val street = this[index].street |
||||
|
||||
val refAction = getStreetLastSignificantAction(street, index) |
||||
?: this.firstStreetAction(street) |
||||
|
||||
val refIndex = refAction.action.index |
||||
val refIndexPosition = refAction.position |
||||
|
||||
val activePositions = activePositions(refIndex) |
||||
activePositions.remove(refIndexPosition) |
||||
|
||||
// We want to remove positions that already have an action after [refIndex] |
||||
for (i in refIndex + 1 until this.size) { |
||||
val ca = this[i] |
||||
activePositions.remove(ca.position) |
||||
} |
||||
|
||||
// Circularly adds an action for missing positions |
||||
val firstPositionAfterCurrent = kotlin.math.max( |
||||
0, |
||||
activePositions.indexOfFirst { it.ordinal > refIndexPosition.ordinal }) |
||||
for (i in 0 until activePositions.size) { |
||||
val position = activePositions[(firstPositionAfterCurrent + i) % activePositions.size] |
||||
this.addNewEmptyAction(position, refAction.street, refAction.totalPotSize, lastRemainingStack(position, index)) |
||||
} |
||||
|
||||
if (activePositions.isNotEmpty()) { |
||||
this.listener.actionCountChanged() |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new action at the end of the action stack, and in the HH representation |
||||
*/ |
||||
private fun addNewEmptyAction(position: Position, street: Street, currentPotSize: Double, remainingStack: Double?) { |
||||
|
||||
if (street == Street.SUMMARY) { |
||||
throw PAIllegalStateException("Summary cannot have player actions") |
||||
} |
||||
|
||||
val action = Action() |
||||
action.index = this.size |
||||
action.position = position.ordinal |
||||
action.street = street |
||||
val computedAction = |
||||
ComputedAction( |
||||
this, |
||||
action, |
||||
currentPotSize, |
||||
remainingStack, |
||||
position |
||||
) |
||||
this.add(computedAction) |
||||
|
||||
this.countChanged = true |
||||
} |
||||
|
||||
/*** |
||||
* Returns the last remaining stack of the player, if available |
||||
*/ |
||||
private fun lastRemainingStack(position: Position, index: Int): Double? { |
||||
return this.take(index).lastOrNull { it.position == position }?.playerRemainingStack |
||||
} |
||||
|
||||
/*** |
||||
* Adds, if necessary, new ComputedAction for players that needs to act |
||||
* Also adds, if necessary, the Street separators and board selectors |
||||
* Returns true if the action list has been modified |
||||
*/ |
||||
private fun updateFollowupActions(index: Int) { |
||||
|
||||
val computedAction = this[index] |
||||
val type = computedAction.action.type |
||||
|
||||
addsFollowupActionsIfNecessary(index) |
||||
|
||||
when (type?.isSignificant) { |
||||
false -> { // closes the action, pass to next street if necessary |
||||
if (type == Action.Type.CALL_ALLIN) { // sometimes we can go from RAISE_ALLIN to CALL_IN, changing the actions required |
||||
removeActionsIfNecessary(index) |
||||
} |
||||
createNextStreetIfNecessary(index) |
||||
} |
||||
else -> {} |
||||
} |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* Recreates the actions after [index] |
||||
*/ |
||||
private fun removeActionsIfNecessary(index: Int) { |
||||
this.keepFirst(index + 1) |
||||
addsFollowupActionsIfNecessary(index) |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new street if no player needs to act |
||||
* Returns true if a street has been created |
||||
*/ |
||||
private fun createNextStreetIfNecessary(index: Int) { |
||||
val nextStreet = isStreetActionClosed(index) |
||||
if (nextStreet != null && this.firstOrNull { it.street == nextStreet } == null) { |
||||
|
||||
if (nextStreet == Street.SUMMARY) { |
||||
this.listener.actionCountChanged() // force recreation of builder rows |
||||
} else { |
||||
createStreet(nextStreet) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* This function evaluates whether the street, identified by an action [index], |
||||
* has its action closed, meaning no more player are required to play. |
||||
* In case of closing, the function returns the next logical Street |
||||
*/ |
||||
fun isStreetActionClosed(index: Int) : Street? { |
||||
|
||||
val currentStreet = this[index].street |
||||
|
||||
getStreetLastSignificantAction(currentStreet, index)?.let { significantAction -> |
||||
|
||||
val activePositions = activePositions(index) |
||||
val activePlayerCount = activePositions.size // don't move this line because of removes |
||||
|
||||
// Blinds must act |
||||
if (!significantAction.action.type!!.isBlind) { |
||||
activePositions.remove(significantAction.position) |
||||
} |
||||
|
||||
val significantIndex = significantAction.action.index |
||||
for (i in significantIndex + 1 until this.size) { |
||||
val ca = this[i] |
||||
val type = ca.action.type |
||||
if (type != null && !type.isSignificant) { // Calls and folds |
||||
activePositions.remove(ca.position) |
||||
} |
||||
} |
||||
|
||||
if (activePositions.isEmpty()) { |
||||
|
||||
return if (activePlayerCount >= 2 && currentStreet != Street.RIVER) { |
||||
currentStreet.next |
||||
} else { |
||||
Street.SUMMARY |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
val allPassive = this.filter { it.street == currentStreet } |
||||
.all { it.action.type?.isPassive ?: false } |
||||
|
||||
if (allPassive) { |
||||
return if (currentStreet != Street.RIVER) { |
||||
currentStreet.next |
||||
} else { |
||||
Street.SUMMARY |
||||
} |
||||
} |
||||
|
||||
return null |
||||
} |
||||
|
||||
/*** |
||||
* Removes the actions, not the automatically created ones, |
||||
* following an action change. |
||||
* We want drop all non-auto added rows after the index |
||||
*/ |
||||
override fun dropNextActions(index: Int) { |
||||
this.keepFirst(index + 1) |
||||
this.updateFollowupActions(index) |
||||
fireListener() |
||||
} |
||||
|
||||
override fun getPlayerNextStreetActions(index: Int): List<ComputedAction> { |
||||
val computedAction = this[index] |
||||
return this.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] |
||||
*/ |
||||
override fun getPreviouslyCommittedAmount(index: Int): Double? { |
||||
val computedAction = this[index] |
||||
val position = computedAction.position |
||||
val street = computedAction.action.street |
||||
|
||||
val previousActions = this.take(index) |
||||
val previousComputedAction = previousActions.lastOrNull { |
||||
it.position == position && it.street == street |
||||
} |
||||
|
||||
previousComputedAction?.action?.let { previousAction -> |
||||
|
||||
return when (previousAction.type) { |
||||
Action.Type.POST_BB, Action.Type.POST_SB, Action.Type.STRADDLE, Action.Type.BET, Action.Type.RAISE -> previousAction.amount |
||||
Action.Type.CALL -> getStreetLastSignificantAction(previousAction.street, previousAction.index)?.action?.amount |
||||
else -> null |
||||
} |
||||
} |
||||
|
||||
return null |
||||
} |
||||
|
||||
/*** |
||||
* Returns all "CALL" ComputedAction between the [index] and the next significant action |
||||
*/ |
||||
override fun getStreetNextCalls(index: Int): List<ComputedAction> { |
||||
val streetNextSignificantIndex = getStreetNextSignificantAction(index)?.action?.index |
||||
?: this.lastIndexOfStreet(index) |
||||
return this.filter { |
||||
it.action.index in ((index + 1) until streetNextSignificantIndex) |
||||
&& (it.action.type?.isCall ?: false) |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Returns the next significant player action in the street, if any, for the action at the provided [index] |
||||
*/ |
||||
private fun getStreetNextSignificantAction(index: Int): ComputedAction? { |
||||
val street = this[index].street |
||||
val nextActions = this.drop(index + 1).filter { it.street == street } |
||||
return nextActions.firstOrNull { it.action.isActionSignificant } |
||||
} |
||||
|
||||
/*** |
||||
* Creates a new street: |
||||
* - Adds a Street Header |
||||
* - Adds a Board if necessary |
||||
* - Adds empty actions for the remaining players |
||||
*/ |
||||
private fun createStreet(street: Street) { |
||||
|
||||
val lastComputedAction = this.last() |
||||
val totalPotSize = lastComputedAction.totalPotSize |
||||
|
||||
val lastActionIndex = lastComputedAction.action.index |
||||
val isShowDown = street == Street.SUMMARY |
||||
activePositions(lastActionIndex, isShowDown).sortedBy { it.ordinal }.forEach { |
||||
addNewEmptyAction(it, street, totalPotSize, lastRemainingStack(it, lastActionIndex)) |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Returns a list of positions at the provided [index] |
||||
*/ |
||||
fun positionsAtIndex(index: Int): List<Position> { |
||||
val currentStreet = this[index].street |
||||
val streetActions = this.filter { it.street == currentStreet } |
||||
return streetActions.drop(index + 1).map { it.position } |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,390 @@ |
||||
package net.pokeranalytics.android.ui.modules.handhistory.model |
||||
|
||||
import net.pokeranalytics.android.R |
||||
import net.pokeranalytics.android.exceptions.PAIllegalStateException |
||||
import net.pokeranalytics.android.model.handhistory.HandSetup |
||||
import net.pokeranalytics.android.model.handhistory.Position |
||||
import net.pokeranalytics.android.model.handhistory.Street |
||||
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.HHSelection |
||||
import net.pokeranalytics.android.ui.modules.handhistory.HandRowType |
||||
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 |
||||
import net.pokeranalytics.android.ui.view.RowRepresentable |
||||
import net.pokeranalytics.android.ui.view.rowrepresentable.CustomizableRowRepresentable |
||||
import net.pokeranalytics.android.util.extensions.formatted |
||||
import timber.log.Timber |
||||
|
||||
interface BuilderListener { |
||||
fun rowRepresentablesCountChanged() |
||||
} |
||||
|
||||
class HHBuilder : CardCentralizer, ActionListListener { |
||||
|
||||
/*** |
||||
* The list of row representables, generated from the hand history actions |
||||
*/ |
||||
var rowRepresentables = mutableListOf<RowRepresentable>() |
||||
private set |
||||
|
||||
/*** |
||||
* The hand history |
||||
*/ |
||||
private var handHistory: HandHistory |
||||
set(value) { |
||||
field = value |
||||
setNumberOfPlayers(value.numberOfPlayers) |
||||
load() |
||||
} |
||||
|
||||
/*** |
||||
* All actions sorted by index |
||||
*/ |
||||
private var sortedActions: ActionList = ActionList(this) |
||||
|
||||
/*** |
||||
* The board cards sorted by position |
||||
*/ |
||||
// private lateinit var boardManager: BoardManager |
||||
|
||||
/*** |
||||
* The maximum number of cards in a player's hand |
||||
*/ |
||||
private var playerHandMaxCards: Int? = null |
||||
set(value) { |
||||
field = value |
||||
this.rowRepresentables.filterIsInstance<PlayerCardsRow>().forEach { |
||||
it.maxCards = value |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Creates a builder using a [handSetup] |
||||
* Also creates a new Hand History and configures it according to the [handSetup] |
||||
*/ |
||||
constructor(handSetup: HandSetup) { |
||||
val handHistory = HandHistory() |
||||
handHistory.configure(handSetup) |
||||
this.playerHandMaxCards = handSetup.game?.playerHandMaxCards |
||||
this.handHistory = handHistory |
||||
} |
||||
|
||||
/*** |
||||
* Creates a builder using the parameter [handHistory] |
||||
*/ |
||||
constructor(handHistory: HandHistory) { |
||||
this.handHistory = handHistory |
||||
} |
||||
|
||||
private var listener: BuilderListener? = null |
||||
|
||||
fun setBuilderListener(builderListener: BuilderListener) { |
||||
this.listener = builderListener |
||||
} |
||||
|
||||
/*** |
||||
* Returns the action |
||||
*/ |
||||
private fun actionForIndex(index: Int) : ComputedAction { |
||||
return this.sortedActions[index] |
||||
} |
||||
|
||||
/*** |
||||
* Fills the [sortedActions] variable with the proper actions |
||||
* Pre-computes the potsizes for the video export |
||||
*/ |
||||
private fun load() { |
||||
|
||||
// this.boardManager = BoardManager(this.handHistory.board, this) |
||||
this.sortedActions.load(this.handHistory) |
||||
|
||||
this.createRowRepresentation() |
||||
} |
||||
|
||||
/*** |
||||
* Sets the number of players playing the hand |
||||
* Defines the appropriate positions for this player count |
||||
*/ |
||||
fun setNumberOfPlayers(playerCount: Int) { |
||||
this.handHistory.numberOfPlayers = playerCount |
||||
this.sortedActions.positions = Position.positionsPerPlayers(playerCount) |
||||
} |
||||
|
||||
fun availableActions(actionIndexForSelection: Int): Set<Action.Type> { |
||||
return this.sortedActions.availableActions(actionIndexForSelection) |
||||
} |
||||
|
||||
fun selectAction(index: Int, actionType: Action.Type) { |
||||
this.sortedActions.selectAction(index, actionType) |
||||
} |
||||
|
||||
/*** |
||||
* 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) |
||||
} |
||||
|
||||
// /*** |
||||
// * Creates a new street: |
||||
// * - Adds a Street Header |
||||
// * - Adds a Board if necessary |
||||
// * - Adds empty actions for the remaining players |
||||
// */ |
||||
// private fun createStreet(street: Street) { |
||||
// |
||||
// val lastComputedAction = this.sortedActions.last() |
||||
// val totalPotSize = lastComputedAction.totalPotSize |
||||
// |
||||
// addStreetHeader(this.rowRepresentables, street) |
||||
// |
||||
// val lastActionIndex = lastComputedAction.action.index |
||||
// val isShowDown = street == Street.SUMMARY |
||||
// activePositions(lastActionIndex, isShowDown).sortedBy { it.ordinal }.forEach { |
||||
// |
||||
// when (street) { |
||||
// Street.SUMMARY -> { |
||||
// this.rowRepresentables.add(PlayerCardsRow(it)) |
||||
// } |
||||
// else -> { |
||||
// addNewEmptyAction(it, street, totalPotSize, lastRemainingStack(it, lastActionIndex)) |
||||
// } |
||||
// } |
||||
// |
||||
// } |
||||
// } |
||||
|
||||
/*** |
||||
* Returns a list of positions at the provided [index] |
||||
*/ |
||||
fun positionsAtIndex(index: Int): List<Position> { |
||||
return this.sortedActions.positionsAtIndex(index) |
||||
} |
||||
|
||||
/*** |
||||
* Returns the index of a ComputedAction given its [rowRepresentableIndex] |
||||
*/ |
||||
fun indexOfComputedAction(rowRepresentableIndex: Int) : Int { |
||||
val computedAction = this.rowRepresentables[rowRepresentableIndex] as ComputedAction |
||||
return computedAction.action.index |
||||
} |
||||
|
||||
/*** |
||||
* Finds the index of the first incomplete action, if existing |
||||
* If the same selection is the same than current, pass to the next row |
||||
*/ |
||||
fun findIndexForEdition(startIndex: Int): HHSelection? { |
||||
|
||||
this.rowRepresentables.forEachIndexed { index, rowRepresentable -> |
||||
|
||||
if (index >= startIndex && rowRepresentable is HandHistoryRow) { |
||||
val foundKeyboard = rowRepresentable.keyboardForCompletion() |
||||
if (foundKeyboard != null) { |
||||
return HHSelection( |
||||
index, |
||||
foundKeyboard |
||||
) |
||||
} |
||||
} |
||||
} |
||||
return null |
||||
} |
||||
|
||||
/*** |
||||
* Returns the index, strictly superior to [actionIndexForSelection], |
||||
* of the next action of the given [position] |
||||
*/ |
||||
fun nextActionIndex(actionIndexForSelection: Int, position: Position): Int { |
||||
|
||||
var i = actionIndexForSelection + 1 |
||||
while (true) { |
||||
val computedAction = this.actionForIndex(i) |
||||
if (computedAction.position == position) { |
||||
return this.rowRepresentables.indexOf(computedAction) |
||||
} |
||||
i++ |
||||
if (i > this.sortedActions.size) throw PAIllegalStateException("algo sucks") |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* 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) |
||||
} |
||||
|
||||
/*** |
||||
* Either affect the [suit] to the current card, |
||||
* or create a Card with an empty value and the [suit] |
||||
*/ |
||||
fun cardSuitSelected(suit: Card.Suit, currentSelection: HHSelection) { |
||||
|
||||
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
||||
row.suitSelected(suit) |
||||
|
||||
// TODO do we want to store the information right now ? |
||||
|
||||
} |
||||
|
||||
/*** |
||||
* 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() |
||||
} |
||||
|
||||
/*** |
||||
* Delete the last property of the last Card of the selected street |
||||
* We don't want empty Card (value + suit), |
||||
* so we delete the whole card if both information are null |
||||
*/ |
||||
fun deleteLastCardProperty(currentSelection: HHSelection) { |
||||
val row = this.rowRepresentables[currentSelection.index] as CardsRow |
||||
row.deleteLastCardProperty() |
||||
} |
||||
|
||||
/*** |
||||
* When a card selection is ended, |
||||
* sets the playerHandMaxCards with the number of cards set to the player |
||||
*/ |
||||
fun cardSelectionEnded(index: Int) { |
||||
val cardsRow = this.rowRepresentables[index] as CardsRow |
||||
if (cardsRow is PlayerCardsRow) { |
||||
this.playerHandMaxCards = cardsRow.cards.size |
||||
} |
||||
} |
||||
|
||||
// BoardChangedListener |
||||
|
||||
/*** |
||||
* Called when the board has changed |
||||
*/ |
||||
fun boardChanged(cards: List<Card>) { |
||||
this.rowRepresentables.filterIsInstance<StreetCardsRow>().forEach { |
||||
// it.cards = cards |
||||
} |
||||
} |
||||
|
||||
/*** |
||||
* Saves the current hand state in the database |
||||
*/ |
||||
private fun save() { |
||||
|
||||
} |
||||
|
||||
private fun createRowRepresentation() { |
||||
val rows: MutableList<RowRepresentable> = mutableListOf() |
||||
|
||||
rows.add(CustomizableRowRepresentable(customViewType = HandRowType.HEADER, resId = R.string.settings, value = "")) |
||||
|
||||
Street.values().forEach { street -> |
||||
|
||||
val actions = this.sortedActions.filter { it.street == street } |
||||
|
||||
when (street) { |
||||
Street.SUMMARY -> { |
||||
val lastActionIndex = this.sortedActions.size - 1 |
||||
|
||||
if (this.sortedActions.activePositions(lastActionIndex).size < 2 || this.sortedActions.isStreetActionClosed(lastActionIndex) == Street.SUMMARY) { |
||||
addStreetHeader(rows, street) |
||||
|
||||
val positions = this.sortedActions.activePositions(lastActionIndex, true) |
||||
positions.forEach { |
||||
val positionIndex = this.sortedActions.positions.indexOf(it) |
||||
val playerCardsRow = PlayerCardsRow(it, this.handHistory.cardsForPosition(positionIndex), this.playerHandMaxCards) |
||||
rows.add(playerCardsRow) |
||||
} |
||||
} |
||||
} |
||||
else -> { |
||||
if (actions.isNotEmpty()) { |
||||
addStreetHeader(rows, street) |
||||
rows.addAll(actions) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
this.rowRepresentables = rows |
||||
this.listener?.rowRepresentablesCountChanged() |
||||
} |
||||
|
||||
/*** |
||||
* Adds a [street] header to a [rowRepresentables] |
||||
*/ |
||||
private fun addStreetHeader(rowRepresentables: MutableList<RowRepresentable>, street: Street) { |
||||
|
||||
val firstIndexOfStreet = this.sortedActions.firstOrNull { it.street == street }?.action?.index |
||||
?: this.sortedActions.size |
||||
|
||||
val potSize = this.sortedActions.take(firstIndexOfStreet).sumByDouble { it.action.effectiveAmount } |
||||
val potString = if (potSize > 0) potSize.formatted() else "" // "" required otherwise random values come up |
||||
|
||||
val headerView = CustomizableRowRepresentable(customViewType = HandRowType.HEADER, resId = street.resId, value = potString) |
||||
rowRepresentables.add(headerView) |
||||
|
||||
if (street.totalBoardCards > 0) { |
||||
|
||||
// get board from last street |
||||
val lastBoardRow = this.rowRepresentables.lastOrNull { it is StreetCardsRow } as? StreetCardsRow |
||||
val cards = lastBoardRow?.cards ?: listOf<Card>() |
||||
|
||||
// create new StreetCardsRow |
||||
val boardView = StreetCardsRow(street, cards) |
||||
rowRepresentables.add(boardView) |
||||
} |
||||
|
||||
} |
||||
|
||||
// Card Centralizer |
||||
|
||||
/*** |
||||
* The list of card already used, for the board or players |
||||
*/ |
||||
private val usedCards: List<Card> |
||||
get() { |
||||
// TODO is my list always stored in the handHistory, or not? |
||||
return listOf() |
||||
} |
||||
|
||||
/*** |
||||
* The value of the last chosen card |
||||
*/ |
||||
private var lastValue: Card.Value? = null |
||||
|
||||
/*** |
||||
* Returns true if the card [value] is available |
||||
*/ |
||||
override fun isValueAvailable(value: Card.Value): Boolean { |
||||
val usedValues = this.usedCards.filter { it.value == value.value } |
||||
return usedValues.size < 4 |
||||
} |
||||
|
||||
/*** |
||||
* Returns true if the [suit] is available for the given [value] |
||||
*/ |
||||
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) |
||||
} |
||||
|
||||
// Action List Listener |
||||
|
||||
override fun actionCountChanged() { |
||||
this.createRowRepresentation() |
||||
} |
||||
|
||||
} |
||||
|
||||
Loading…
Reference in new issue