Add two-stage raise confirmation
This commit is contained in:
@@ -154,9 +154,11 @@ export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
||||
straddles the outside rail rather than occupying the community-card band.
|
||||
The terminal modal repeats the board, every public hand turned up (including
|
||||
losing hands), and exact pot awards so dimming the table never hides the
|
||||
explanation. Raise sizing combines coarse slider/presets with one-chip minus
|
||||
and plus controls; visible chip stacks grow with bets and the pot while numeric
|
||||
labels remain authoritative.
|
||||
explanation. The default action bar contains only Fold/Check-or-Call/Raise;
|
||||
Raise opens a confirmation screen with coarse slider/presets, one-chip minus
|
||||
and plus controls, and explicit UI/system Back handling. Merely opening or
|
||||
leaving sizing never submits an action. Visible chip stacks grow with bets and
|
||||
the pot while numeric labels remain authoritative.
|
||||
- A cash-game session begins at an explicit take-a-seat screen. Opponents may
|
||||
auto-reload below the big blind; the human is never silently topped up and must
|
||||
explicitly choose "Reload to N & deal" from the completed-hand screen.
|
||||
|
||||
@@ -68,8 +68,18 @@ fun adjustRaiseAmount(
|
||||
buttons: ActionButtons,
|
||||
): Int = (current + change).coerceIn(buttons.sliderMin, buttons.sliderMax)
|
||||
|
||||
/** The exact amount submitted by the confirmation screen. */
|
||||
fun raiseConfirmationAmount(
|
||||
buttons: ActionButtons,
|
||||
selected: Int,
|
||||
): Int = if (buttons.showSlider) {
|
||||
selected.coerceIn(buttons.sliderMin, buttons.sliderMax)
|
||||
} else {
|
||||
buttons.fixedRaiseTo
|
||||
}
|
||||
|
||||
/**
|
||||
* Honest, legal raise-to shortcuts for the always-visible sizing rail.
|
||||
* Honest, legal raise-to shortcuts for the raise-sizing screen.
|
||||
*
|
||||
* Facing a bet, a fraction describes the extra raise after calling: the pot
|
||||
* after a call is [DecisionOffer.pot] plus the affordable call, and the final
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.jsjdesigns.poker
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
@@ -33,9 +34,9 @@ import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -757,8 +758,12 @@ private fun DecisionControls(
|
||||
onRaise: (Long, Int) -> Unit,
|
||||
) {
|
||||
val buttons = buttonsFor(offer)
|
||||
var raiseTo by remember { mutableIntStateOf(buttons.fixedRaiseTo) }
|
||||
LaunchedEffect(offer.token) { raiseTo = buttons.fixedRaiseTo }
|
||||
var sizingRaise by remember(offer.token) { mutableStateOf(false) }
|
||||
var raiseTo by remember(offer.token) { mutableIntStateOf(buttons.fixedRaiseTo) }
|
||||
|
||||
BackHandler(enabled = sizingRaise) {
|
||||
sizingRaise = false
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -771,26 +776,40 @@ private fun DecisionControls(
|
||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
if (buttons.showRaise && buttons.showSlider) {
|
||||
if (!sizingRaise) {
|
||||
PrimaryDecisionButtons(
|
||||
offer = offer,
|
||||
buttons = buttons,
|
||||
onFold = onFold,
|
||||
onCheckCall = onCheckCall,
|
||||
onOpenRaise = { sizingRaise = true },
|
||||
)
|
||||
} else {
|
||||
val amount = raiseConfirmationAmount(buttons, raiseTo)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
BackToActionsButton { sizingRaise = false }
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
Text(
|
||||
"RAISE TO",
|
||||
color = Muted.copy(alpha = 0.72f),
|
||||
color = Muted.copy(alpha = 0.82f),
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 12.sp,
|
||||
letterSpacing = 1.4.sp,
|
||||
)
|
||||
Text(
|
||||
"$raiseTo",
|
||||
"$amount",
|
||||
color = GoldLight,
|
||||
fontWeight = FontWeight.ExtraBold,
|
||||
fontSize = 22.sp,
|
||||
fontSize = 24.sp,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (buttons.showSlider) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -800,7 +819,7 @@ private fun DecisionControls(
|
||||
raiseTo = adjustRaiseAmount(raiseTo, -1, buttons)
|
||||
}
|
||||
Slider(
|
||||
value = raiseTo.coerceIn(buttons.sliderMin, buttons.sliderMax).toFloat(),
|
||||
value = amount.toFloat(),
|
||||
onValueChange = { raiseTo = it.roundToInt() },
|
||||
valueRange = buttons.sliderMin.toFloat()..buttons.sliderMax.toFloat(),
|
||||
colors = SliderDefaults.colors(
|
||||
@@ -829,12 +848,12 @@ private fun DecisionControls(
|
||||
.height(42.dp),
|
||||
contentPadding = PaddingValues(horizontal = 4.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = if (raiseTo == preset.amount) {
|
||||
containerColor = if (amount == preset.amount) {
|
||||
TableGold.copy(alpha = 0.2f)
|
||||
} else {
|
||||
Color.White.copy(alpha = 0.06f)
|
||||
},
|
||||
contentColor = if (raiseTo == preset.amount) GoldLight else Muted,
|
||||
contentColor = if (amount == preset.amount) GoldLight else Muted,
|
||||
),
|
||||
shape = RoundedCornerShape(9.dp),
|
||||
) {
|
||||
@@ -847,8 +866,42 @@ private fun DecisionControls(
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
if (amount >= buttons.sliderMax) {
|
||||
"Your stack leaves one legal raise: all in for $amount."
|
||||
} else {
|
||||
"There is one legal raise size: $amount."
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp),
|
||||
color = Cream.copy(alpha = 0.82f),
|
||||
fontSize = 13.sp,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
|
||||
ActionButton(
|
||||
title = if (amount >= buttons.sliderMax) "Confirm all in" else "Confirm raise",
|
||||
detail = if (amount >= buttons.sliderMax) "$amount" else "to $amount",
|
||||
container = Brush.verticalGradient(listOf(GoldLight, Color(0xFFC79A38))),
|
||||
content = Color(0xFF22190A),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { onRaise(offer.token, amount) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PrimaryDecisionButtons(
|
||||
offer: DecisionOffer,
|
||||
buttons: ActionButtons,
|
||||
onFold: (Long) -> Unit,
|
||||
onCheckCall: (Long) -> Unit,
|
||||
onOpenRaise: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
@@ -876,21 +929,31 @@ private fun DecisionControls(
|
||||
onClick = { onCheckCall(offer.token) },
|
||||
)
|
||||
if (buttons.showRaise) {
|
||||
val amount = if (buttons.showSlider) {
|
||||
raiseTo.coerceIn(buttons.sliderMin, buttons.sliderMax)
|
||||
} else {
|
||||
buttons.fixedRaiseTo
|
||||
}
|
||||
ActionButton(
|
||||
title = if (amount >= buttons.sliderMax) "All in" else "Raise",
|
||||
detail = if (amount >= buttons.sliderMax) "$amount" else "to $amount",
|
||||
title = "Raise",
|
||||
detail = null,
|
||||
container = Brush.verticalGradient(listOf(GoldLight, Color(0xFFC79A38))),
|
||||
content = Color(0xFF22190A),
|
||||
modifier = Modifier.weight(1.12f),
|
||||
onClick = { onRaise(offer.token, amount) },
|
||||
onClick = onOpenRaise,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackToActionsButton(onClick: () -> Unit) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.height(44.dp),
|
||||
contentPadding = PaddingValues(horizontal = 14.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = Color.White.copy(alpha = 0.09f),
|
||||
contentColor = Cream,
|
||||
),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
) {
|
||||
Text("‹ Back", fontWeight = FontWeight.Bold, fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,17 @@ class ActionButtonsTest {
|
||||
assertEquals(20, adjustRaiseAmount(20, -1, b))
|
||||
assertEquals(500, adjustRaiseAmount(500, 1, b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `raise confirmation clamps a range but ignores selection for an exact raise`() {
|
||||
val range = buttonsFor(offer(minRaiseTo = 20, maxRaiseTo = 500))
|
||||
assertEquals(20, raiseConfirmationAmount(range, -10))
|
||||
assertEquals(60, raiseConfirmationAmount(range, 60))
|
||||
assertEquals(500, raiseConfirmationAmount(range, 900))
|
||||
|
||||
val exact = buttonsFor(offer(minRaiseTo = 100, maxRaiseTo = 100))
|
||||
assertEquals(100, raiseConfirmationAmount(exact, 999))
|
||||
}
|
||||
}
|
||||
|
||||
class CallCostTest {
|
||||
|
||||
Reference in New Issue
Block a user