diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HHBuilder.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HHBuilder.kt deleted file mode 100644 index 714b9b3b..00000000 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HHBuilder.kt +++ /dev/null @@ -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? - fun getStreetNextCalls(index: Int): List - fun getPlayerNextStreetActions(index: Int): List - fun dropNextActions(index: Int): Boolean -} - -class ActionList : ArrayList() { - - /*** - * 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 = linkedSetOf() - - /*** - * The maximum number of cards in a player's hand - */ - private var playerHandMaxCards: Int? = null - set(value) { - field = value - this.rowRepresentables.filterIsInstance().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 { - - 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? { - - 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() - 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 { - 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 - 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 { - 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 { - 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 { - 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 { - 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) { - this.rowRepresentables.filterIsInstance().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() - private set - - private fun createRowRepresentation() { - val rows: MutableList = 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, 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() - - // create new StreetCardsRow - val boardView = StreetCardsRow(street, cards) - rowRepresentables.add(boardView) - } - - } - - // Card Centralizer - - private val usedCards: List - 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) - } - -} - diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryAdapter.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryAdapter.kt index 1a891360..4098fa54 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryAdapter.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryAdapter.kt @@ -12,7 +12,7 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException -import net.pokeranalytics.android.model.handhistory.ComputedAction +import net.pokeranalytics.android.ui.modules.handhistory.model.ComputedAction import net.pokeranalytics.android.model.handhistory.Street import net.pokeranalytics.android.model.realm.handhistory.formatted import net.pokeranalytics.android.ui.adapter.BindableHolder diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryFragment.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryFragment.kt index 66a3f408..d0364f30 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryFragment.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryFragment.kt @@ -10,7 +10,6 @@ import kotlinx.android.synthetic.main.fragment_hand_history.* import kotlinx.android.synthetic.main.fragment_settings.recyclerView import net.pokeranalytics.android.R import net.pokeranalytics.android.exceptions.PAIllegalStateException -import net.pokeranalytics.android.model.handhistory.ComputedAction import net.pokeranalytics.android.model.handhistory.HandSetup import net.pokeranalytics.android.model.handhistory.Position import net.pokeranalytics.android.model.realm.handhistory.Action @@ -18,6 +17,9 @@ import net.pokeranalytics.android.model.realm.handhistory.Card import net.pokeranalytics.android.model.realm.handhistory.HandHistory import net.pokeranalytics.android.ui.adapter.RowRepresentableDelegate import net.pokeranalytics.android.ui.fragment.components.RealmFragment +import net.pokeranalytics.android.ui.modules.handhistory.model.BuilderListener +import net.pokeranalytics.android.ui.modules.handhistory.model.ComputedAction +import net.pokeranalytics.android.ui.modules.handhistory.model.HHBuilder import net.pokeranalytics.android.ui.modules.handhistory.views.KeyboardListener import net.pokeranalytics.android.ui.modules.handhistory.views.PlayerCardsRow import net.pokeranalytics.android.ui.modules.handhistory.views.StreetCardsRow @@ -27,7 +29,7 @@ import net.pokeranalytics.android.util.extensions.findById import net.pokeranalytics.android.util.extensions.noGroupingFormatted import timber.log.Timber -class HandHistoryFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListener { +class HandHistoryFragment : RealmFragment(), RowRepresentableDelegate, KeyboardListener, BuilderListener { private lateinit var model: HandHistoryViewModel @@ -80,7 +82,9 @@ class HandHistoryFragment : RealmFragment(), RowRepresentableDelegate, KeyboardL handHistory ) } ?: run { - HHBuilder(HandSetup()) + HHBuilder( + HandSetup() + ) } this.model.setBuilder(builder) @@ -238,13 +242,13 @@ class HandHistoryFragment : RealmFragment(), RowRepresentableDelegate, KeyboardL override fun actionSelected(action: Action.Type) { Timber.d(">>> action $action selected") - val indexesToRefresh = this.model.actionSelected(action) + this.model.actionSelected(action) - indexesToRefresh?.let { indexes -> - indexes.forEach { this.handHistoryAdapter.notifyItemChanged(it) } - } ?: run { - this.handHistoryAdapter.notifyDataSetChanged() - } +// indexesToRefresh?.let { indexes -> +// indexes.forEach { this.handHistoryAdapter.notifyItemChanged(it) } +// } ?: run { +// this.handHistoryAdapter.notifyDataSetChanged() +// } this.findNextActionToEdit() } @@ -321,4 +325,8 @@ class HandHistoryFragment : RealmFragment(), RowRepresentableDelegate, KeyboardL } } + override fun rowRepresentablesCountChanged() { + this.handHistoryAdapter.notifyDataSetChanged() + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryViewModel.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryViewModel.kt index be0b24c8..79907c8f 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryViewModel.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/HandHistoryViewModel.kt @@ -7,10 +7,18 @@ import net.pokeranalytics.android.model.handhistory.Position import net.pokeranalytics.android.model.realm.handhistory.Action import net.pokeranalytics.android.model.realm.handhistory.Card import net.pokeranalytics.android.ui.adapter.RowRepresentableDataSource +import net.pokeranalytics.android.ui.modules.handhistory.model.BuilderListener +import net.pokeranalytics.android.ui.modules.handhistory.model.HHBuilder import net.pokeranalytics.android.ui.modules.handhistory.views.CardCentralizer import net.pokeranalytics.android.ui.view.RowRepresentable import timber.log.Timber +enum class HHKeyboard { + ACTION, + AMOUNT, + CARD; +} + class HHSelection(var index: Int, var keyboard: HHKeyboard) class HandHistoryViewModel : ViewModel(), RowRepresentableDataSource { @@ -47,7 +55,7 @@ class HandHistoryViewModel : ViewModel(), RowRepresentableDataSource { // Action - fun actionSelected(action: Action.Type): List? { + fun actionSelected(action: Action.Type) { return this.builder.selectAction(this.actionIndexForSelection, action) } @@ -138,4 +146,8 @@ class HandHistoryViewModel : ViewModel(), RowRepresentableDataSource { this.builder.cardSelectionEnded(this.currentSelection.index) } + fun setBuilderListener(builderListener: BuilderListener) { + this.builder.setBuilderListener(builderListener) + } + } \ No newline at end of file diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ActionList.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ActionList.kt new file mode 100644 index 00000000..e8c78179 --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ActionList.kt @@ -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 + fun getPlayerNextStreetActions(index: Int): List + fun dropNextActions(index: Int) +} + +interface ActionListListener { + fun actionCountChanged() +} + +class ActionList(var listener: ActionListListener) : ArrayList(), 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() + 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 = 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() + 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 { + + 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 { + 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 + 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 { + 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 { + 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 { + 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 { + val currentStreet = this[index].street + val streetActions = this.filter { it.street == currentStreet } + return streetActions.drop(index + 1).map { it.position } + } + +} diff --git a/app/src/main/java/net/pokeranalytics/android/model/handhistory/ComputedAction.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ComputedAction.kt similarity index 95% rename from app/src/main/java/net/pokeranalytics/android/model/handhistory/ComputedAction.kt rename to app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ComputedAction.kt index b2230780..c2d9abb8 100644 --- a/app/src/main/java/net/pokeranalytics/android/model/handhistory/ComputedAction.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/ComputedAction.kt @@ -1,8 +1,9 @@ -package net.pokeranalytics.android.model.handhistory +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.ui.modules.handhistory.ActionManager import net.pokeranalytics.android.ui.modules.handhistory.HHKeyboard import net.pokeranalytics.android.ui.modules.handhistory.HandRowType import net.pokeranalytics.android.ui.view.RowRepresentable @@ -21,7 +22,8 @@ class ComputedAction(var manager: ActionManager, var action: Action, var totalPotSize: Double = 0.0, var playerRemainingStack: Double? = null, - var position: Position) : HandHistoryRow { + var position: Position +) : HandHistoryRow { /*** * Returns whether the action requires the user to enter an amount for the selected action @@ -36,7 +38,7 @@ class ComputedAction(var manager: ActionManager, } } - fun setType(type: Action.Type): Boolean { + fun setType(type: Action.Type) { val typeChange = (this.action.type != null) @@ -63,10 +65,9 @@ class ComputedAction(var manager: ActionManager, } if (typeChange) { - return this.manager.dropNextActions(this.action.index) + this.manager.dropNextActions(this.action.index) } - return false } /*** diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/HHBuilder.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/HHBuilder.kt new file mode 100644 index 00000000..0abfbdff --- /dev/null +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/model/HHBuilder.kt @@ -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() + 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().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 { + 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 { + 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) { + this.rowRepresentables.filterIsInstance().forEach { +// it.cards = cards + } + } + + /*** + * Saves the current hand state in the database + */ + private fun save() { + + } + + private fun createRowRepresentation() { + val rows: MutableList = 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, 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() + + // 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 + 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() + } + +} + diff --git a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/views/CardsRow.kt b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/views/CardsRow.kt index c95a12ed..3bfae1e8 100644 --- a/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/views/CardsRow.kt +++ b/app/src/main/java/net/pokeranalytics/android/ui/modules/handhistory/views/CardsRow.kt @@ -1,14 +1,15 @@ package net.pokeranalytics.android.ui.modules.handhistory.views import net.pokeranalytics.android.exceptions.PAIllegalStateException -import net.pokeranalytics.android.model.handhistory.HandHistoryRow import net.pokeranalytics.android.model.handhistory.Position import net.pokeranalytics.android.model.handhistory.Street import net.pokeranalytics.android.model.realm.handhistory.Card import net.pokeranalytics.android.ui.modules.handhistory.HHKeyboard import net.pokeranalytics.android.ui.modules.handhistory.HandRowType +import net.pokeranalytics.android.ui.modules.handhistory.model.HandHistoryRow -abstract class CardsRow(cards: List) : HandHistoryRow { +abstract class CardsRow(cards: List) : + HandHistoryRow { var cards: MutableList = cards.toMutableList() private set