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