Improve table legibility and showdown focus
This commit is contained in:
@@ -149,7 +149,10 @@ export JAVA_HOME="/Applications/Android Studio.app/Contents/jbr/Contents/Home"
|
|||||||
completed hand stays on screen until the player explicitly starts the next one.
|
completed hand stays on screen until the player explicitly starts the next one.
|
||||||
The portrait table follows the 1A design direction: oval felt, orbiting seats,
|
The portrait table follows the 1A design direction: oval felt, orbiting seats,
|
||||||
spatial bets, large 2:3 card art, always-visible legal sizing controls, and a
|
spatial bets, large 2:3 card art, always-visible legal sizing controls, and a
|
||||||
winner-focused pot/hand breakdown at completion.
|
winner-focused pot/hand breakdown at completion. Face-up cards have fixed
|
||||||
|
legibility floors rather than shrinking on short displays; the opponent orbit
|
||||||
|
remains above the community-card band, and the terminal modal repeats the
|
||||||
|
board beside exact pot awards so dimming the table never hides the explanation.
|
||||||
- A cash-game session begins at an explicit take-a-seat screen. Opponents may
|
- 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
|
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.
|
explicitly choose "Reload to N & deal" from the completed-hand screen.
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ data class PotAwardSummary(
|
|||||||
*/
|
*/
|
||||||
data class HandSummary(
|
data class HandSummary(
|
||||||
val handNumber: Int,
|
val handNumber: Int,
|
||||||
|
/** Public community cards retained so the modal can explain the winning hand. */
|
||||||
|
val board: List<Int>,
|
||||||
/** Display labels in the engine's winner order; the human seat is always "You". */
|
/** Display labels in the engine's winner order; the human seat is always "You". */
|
||||||
val winnerLabels: List<String>,
|
val winnerLabels: List<String>,
|
||||||
/** Derived from winner seat indices, never from a player-controlled name. */
|
/** Derived from winner seat indices, never from a player-controlled name. */
|
||||||
@@ -101,6 +103,7 @@ fun handSummaryFor(
|
|||||||
|
|
||||||
return HandSummary(
|
return HandSummary(
|
||||||
handNumber = handNumber,
|
handNumber = handNumber,
|
||||||
|
board = result.board.toList(),
|
||||||
winnerLabels = result.winners.map { winner ->
|
winnerLabels = result.winners.map { winner ->
|
||||||
if (winner == heroSeat) "You" else seatNames[winner]
|
if (winner == heroSeat) "You" else seatNames[winner]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ import com.jsjdesigns.poker.core.Card
|
|||||||
import com.jsjdesigns.poker.game.DecisionOffer
|
import com.jsjdesigns.poker.game.DecisionOffer
|
||||||
import com.jsjdesigns.poker.game.SeatSnapshot
|
import com.jsjdesigns.poker.game.SeatSnapshot
|
||||||
import com.jsjdesigns.poker.game.TableSnapshot
|
import com.jsjdesigns.poker.game.TableSnapshot
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
private val Night = Color(0xFF04100C)
|
private val Night = Color(0xFF04100C)
|
||||||
private val TableBackground = Color(0xFF071D16)
|
private val TableBackground = Color(0xFF071D16)
|
||||||
@@ -73,6 +76,26 @@ private val Muted = Color(0xFF9FB0A6)
|
|||||||
private val Danger = Color(0xFF8C242A)
|
private val Danger = Color(0xFF8C242A)
|
||||||
private val Panel = Color(0xE604120D)
|
private val Panel = Color(0xE604120D)
|
||||||
|
|
||||||
|
internal data class NormalizedSeatPosition(
|
||||||
|
val centerX: Float,
|
||||||
|
val topY: Float,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Five positions along the upper half of an ellipse.
|
||||||
|
*
|
||||||
|
* Keeping this geometry separate from Compose makes the table relationship
|
||||||
|
* testable: opponents occupy the upper arc while the board owns the middle band.
|
||||||
|
*/
|
||||||
|
internal fun opponentSeatOrbit(): List<NormalizedSeatPosition> =
|
||||||
|
listOf(145.0, 118.0, 90.0, 62.0, 35.0).map { degrees ->
|
||||||
|
val radians = degrees * PI / 180.0
|
||||||
|
NormalizedSeatPosition(
|
||||||
|
centerX = (0.5 + 0.46 * cos(radians)).toFloat(),
|
||||||
|
topY = (0.35 - 0.27 * sin(radians)).toFloat(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TableScreen(vm: PokerViewModel) {
|
fun TableScreen(vm: PokerViewModel) {
|
||||||
val state by vm.state.collectAsStateWithLifecycle()
|
val state by vm.state.collectAsStateWithLifecycle()
|
||||||
@@ -187,26 +210,21 @@ private fun PokerTable(
|
|||||||
.clip(RoundedCornerShape(26.dp))
|
.clip(RoundedCornerShape(26.dp))
|
||||||
.background(TableBackground),
|
.background(TableBackground),
|
||||||
) {
|
) {
|
||||||
val compact = maxHeight < 520.dp
|
|
||||||
TableFelt(Modifier.fillMaxSize())
|
TableFelt(Modifier.fillMaxSize())
|
||||||
|
|
||||||
val opponentModifiers = listOf(
|
val orbit = opponentSeatOrbit()
|
||||||
Modifier.align(Alignment.CenterStart).offset(x = 7.dp, y = (-26).dp),
|
|
||||||
Modifier.align(Alignment.TopStart).offset(x = 55.dp, y = 18.dp),
|
|
||||||
Modifier.align(Alignment.TopCenter).offset(y = 2.dp),
|
|
||||||
Modifier.align(Alignment.TopEnd).offset(x = (-55).dp, y = 18.dp),
|
|
||||||
Modifier.align(Alignment.CenterEnd).offset(x = (-7).dp, y = (-26).dp),
|
|
||||||
)
|
|
||||||
seats.filter { it.index != HERO_SEAT }
|
seats.filter { it.index != HERO_SEAT }
|
||||||
.forEachIndexed { index, seat ->
|
.forEachIndexed { index, seat ->
|
||||||
|
val position = orbit.getOrElse(index) { orbit.last() }
|
||||||
|
val seatWidth = if (seat.revealed && seat.hole != null) 90.dp else 76.dp
|
||||||
OpponentSeat(
|
OpponentSeat(
|
||||||
seat = seat,
|
seat = seat,
|
||||||
isTurn = snapshot?.toAct == seat.index,
|
isTurn = snapshot?.toAct == seat.index,
|
||||||
isWinner = seat.index in winners,
|
isWinner = seat.index in winners,
|
||||||
compact = compact,
|
modifier = Modifier.offset(
|
||||||
modifier = opponentModifiers.getOrElse(index) {
|
x = maxWidth * position.centerX - seatWidth / 2,
|
||||||
Modifier.align(Alignment.TopCenter)
|
y = maxHeight * position.topY,
|
||||||
},
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,21 +232,31 @@ private fun PokerTable(
|
|||||||
snapshot = snapshot,
|
snapshot = snapshot,
|
||||||
status = status,
|
status = status,
|
||||||
tableTalk = tableTalk,
|
tableTalk = tableTalk,
|
||||||
compact = compact,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.Center)
|
.align(Alignment.Center)
|
||||||
.offset(y = if (compact) 12.dp else 20.dp),
|
.offset(y = 18.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
HeroSeat(
|
HeroSeat(
|
||||||
seat = seats.firstOrNull { it.index == HERO_SEAT },
|
seat = seats.firstOrNull { it.index == HERO_SEAT },
|
||||||
isTurn = snapshot?.toAct == HERO_SEAT,
|
isTurn = snapshot?.toAct == HERO_SEAT,
|
||||||
isWinner = HERO_SEAT in winners,
|
isWinner = HERO_SEAT in winners,
|
||||||
compact = compact,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = 10.dp),
|
.padding(bottom = 10.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
handSummary?.let { summary ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Night.copy(alpha = 0.82f))
|
||||||
|
.padding(18.dp),
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
) {
|
||||||
|
ShowdownModal(summary)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,15 +307,15 @@ private fun OpponentSeat(
|
|||||||
seat: SeatSnapshot,
|
seat: SeatSnapshot,
|
||||||
isTurn: Boolean,
|
isTurn: Boolean,
|
||||||
isWinner: Boolean,
|
isWinner: Boolean,
|
||||||
compact: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val faded = seat.folded && !isWinner
|
val faded = seat.folded && !isWinner
|
||||||
val faceWidth = if (compact) 29.dp else 33.dp
|
val revealed = seat.revealed && seat.hole != null
|
||||||
val faceHeight = faceWidth * 1.5f
|
val faceWidth = 52.dp
|
||||||
|
val faceHeight = 78.dp
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.width(if (compact) 68.dp else 76.dp)
|
.width(if (revealed) 90.dp else 76.dp)
|
||||||
.alpha(if (faded) 0.38f else 1f),
|
.alpha(if (faded) 0.38f else 1f),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(3.dp),
|
verticalArrangement = Arrangement.spacedBy(3.dp),
|
||||||
@@ -309,8 +337,8 @@ private fun OpponentSeat(
|
|||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val hole = seat.hole
|
val hole = seat.hole
|
||||||
if (seat.revealed && hole != null) {
|
if (revealed && hole != null) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy((-18).dp)) {
|
||||||
repeat(2) { index ->
|
repeat(2) { index ->
|
||||||
CardImage(
|
CardImage(
|
||||||
hole.getOrNull(index),
|
hole.getOrNull(index),
|
||||||
@@ -413,12 +441,11 @@ private fun HeroSeat(
|
|||||||
seat: SeatSnapshot?,
|
seat: SeatSnapshot?,
|
||||||
isTurn: Boolean,
|
isTurn: Boolean,
|
||||||
isWinner: Boolean,
|
isWinner: Boolean,
|
||||||
compact: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val folded = seat?.folded == true
|
val folded = seat?.folded == true
|
||||||
val cardWidth = if (compact) 58.dp else 68.dp
|
val cardWidth = 76.dp
|
||||||
val cardHeight = cardWidth * 1.5f
|
val cardHeight = 114.dp
|
||||||
Surface(
|
Surface(
|
||||||
modifier = modifier.alpha(if (folded) 0.42f else 1f),
|
modifier = modifier.alpha(if (folded) 0.42f else 1f),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
@@ -481,19 +508,18 @@ private fun BoardAndStatus(
|
|||||||
snapshot: TableSnapshot?,
|
snapshot: TableSnapshot?,
|
||||||
status: TableStatus?,
|
status: TableStatus?,
|
||||||
tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
|
tableTalk: com.jsjdesigns.poker.bot.TableTalkLine?,
|
||||||
compact: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val board = snapshot?.board.orEmpty()
|
val board = snapshot?.board.orEmpty()
|
||||||
val cardWidth = if (compact) 45.dp else 52.dp
|
val cardWidth = 52.dp
|
||||||
val cardHeight = cardWidth * 1.5f
|
val cardHeight = 78.dp
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(if (compact) 5.dp else 8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
PotPill(snapshot?.pot ?: 0)
|
PotPill(snapshot?.pot ?: 0)
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) {
|
||||||
repeat(5) { index ->
|
repeat(5) { index ->
|
||||||
if (index < board.size) {
|
if (index < board.size) {
|
||||||
CardImage(board[index], Modifier.size(cardWidth, cardHeight))
|
CardImage(board[index], Modifier.size(cardWidth, cardHeight))
|
||||||
@@ -521,8 +547,8 @@ private fun BoardAndStatus(
|
|||||||
tableTalk?.let {
|
tableTalk?.let {
|
||||||
Text(
|
Text(
|
||||||
"${it.speakerName}: “${it.text}”",
|
"${it.speakerName}: “${it.text}”",
|
||||||
color = Cream.copy(alpha = 0.74f),
|
color = Cream.copy(alpha = 0.9f),
|
||||||
fontSize = 10.sp,
|
fontSize = 11.sp,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -667,7 +693,7 @@ private fun TableControls(
|
|||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
offer != null -> DecisionControls(offer, onFold, onCheckCall, onRaise)
|
offer != null -> DecisionControls(offer, onFold, onCheckCall, onRaise)
|
||||||
handSummary != null -> HandResultPanel(
|
handSummary != null -> CompletionControls(
|
||||||
summary = handSummary,
|
summary = handSummary,
|
||||||
nextHandRequested = nextHandRequested,
|
nextHandRequested = nextHandRequested,
|
||||||
rebuyRequired = rebuyRequired,
|
rebuyRequired = rebuyRequired,
|
||||||
@@ -876,7 +902,74 @@ private fun ActionButtonText(title: String, detail: String?, color: Color) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun HandResultPanel(
|
private fun ShowdownModal(summary: HandSummary) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(20.dp),
|
||||||
|
color = Color(0xFA07130F),
|
||||||
|
border = androidx.compose.foundation.BorderStroke(2.dp, TableGold.copy(alpha = 0.72f)),
|
||||||
|
shadowElevation = 14.dp,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 15.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
if (summary.wentToShowdown) "SHOWDOWN" else "HAND COMPLETE",
|
||||||
|
color = TableGold,
|
||||||
|
fontWeight = FontWeight.ExtraBold,
|
||||||
|
fontSize = 10.sp,
|
||||||
|
letterSpacing = 1.8.sp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
summary.title,
|
||||||
|
color = GoldLight,
|
||||||
|
fontWeight = FontWeight.ExtraBold,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
summary.detail,
|
||||||
|
color = Cream.copy(alpha = 0.94f),
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
)
|
||||||
|
if (summary.wentToShowdown && summary.board.isNotEmpty()) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"BOARD",
|
||||||
|
color = Muted.copy(alpha = 0.9f),
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 9.sp,
|
||||||
|
letterSpacing = 1.3.sp,
|
||||||
|
)
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
|
summary.board.forEach { card ->
|
||||||
|
CardImage(card, Modifier.size(46.dp, 69.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.horizontalScroll(rememberScrollState()),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
) {
|
||||||
|
summary.awards.forEach { award ->
|
||||||
|
PotAwardCard(award)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CompletionControls(
|
||||||
summary: HandSummary,
|
summary: HandSummary,
|
||||||
nextHandRequested: Boolean,
|
nextHandRequested: Boolean,
|
||||||
rebuyRequired: Boolean,
|
rebuyRequired: Boolean,
|
||||||
@@ -886,31 +979,10 @@ private fun HandResultPanel(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
.padding(horizontal = 14.dp, vertical = 9.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(6.dp),
|
verticalArrangement = Arrangement.spacedBy(6.dp),
|
||||||
) {
|
) {
|
||||||
Text(
|
|
||||||
summary.title,
|
|
||||||
color = GoldLight,
|
|
||||||
fontWeight = FontWeight.ExtraBold,
|
|
||||||
fontSize = 20.sp,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
summary.detail,
|
|
||||||
color = Cream.copy(alpha = 0.72f),
|
|
||||||
fontSize = 11.sp,
|
|
||||||
)
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.horizontalScroll(rememberScrollState()),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
|
||||||
summary.awards.forEach { award ->
|
|
||||||
PotAwardCard(award)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rebuyRequired) {
|
if (rebuyRequired) {
|
||||||
Text(
|
Text(
|
||||||
"Your stack cannot cover the big blind.",
|
"Your stack cannot cover the big blind.",
|
||||||
@@ -943,36 +1015,40 @@ private fun HandResultPanel(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PotAwardCard(award: PotAwardSummary) {
|
private fun PotAwardCard(award: PotAwardSummary) {
|
||||||
|
// For a split pot, showing only the first winner's cards would imply those
|
||||||
|
// cards alone explain the award. The modal retains the board and names every
|
||||||
|
// tied winner with their exact payout instead.
|
||||||
|
val visibleCards = award.winners
|
||||||
|
.singleOrNull()
|
||||||
|
?.hole
|
||||||
|
.orEmpty()
|
||||||
Card(
|
Card(
|
||||||
|
modifier = Modifier.width(310.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.07f)),
|
colors = CardDefaults.cardColors(containerColor = Color.White.copy(alpha = 0.07f)),
|
||||||
border = androidx.compose.foundation.BorderStroke(1.dp, TableGold.copy(alpha = 0.22f)),
|
border = androidx.compose.foundation.BorderStroke(1.dp, TableGold.copy(alpha = 0.38f)),
|
||||||
shape = RoundedCornerShape(12.dp),
|
shape = RoundedCornerShape(14.dp),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
|
modifier = Modifier.padding(horizontal = 11.dp, vertical = 10.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
// For a split pot, showing only the first winner's cards would imply
|
|
||||||
// those cards alone explain the award. The board remains visible and
|
|
||||||
// every tied winner is named instead.
|
|
||||||
val visibleCards = award.winners
|
|
||||||
.singleOrNull()
|
|
||||||
?.hole
|
|
||||||
.orEmpty()
|
|
||||||
if (visibleCards.isNotEmpty()) {
|
if (visibleCards.isNotEmpty()) {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||||
visibleCards.take(2).forEach { card ->
|
visibleCards.take(2).forEach { card ->
|
||||||
CardImage(card, Modifier.size(34.dp, 51.dp))
|
CardImage(card, Modifier.size(76.dp, 114.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier.width(112.dp)) {
|
Column(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
"${award.label.uppercase()} ${award.amount}",
|
"${award.label.uppercase()} ${award.amount}",
|
||||||
color = TableGold,
|
color = TableGold,
|
||||||
fontWeight = FontWeight.ExtraBold,
|
fontWeight = FontWeight.ExtraBold,
|
||||||
fontSize = 9.sp,
|
fontSize = 11.sp,
|
||||||
letterSpacing = 0.5.sp,
|
letterSpacing = 0.5.sp,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
@@ -982,18 +1058,18 @@ private fun PotAwardCard(award: PotAwardSummary) {
|
|||||||
award.winners.joinToString(" • ") { "${it.label} ${it.amountWon}" }
|
award.winners.joinToString(" • ") { "${it.label} ${it.amountWon}" }
|
||||||
},
|
},
|
||||||
color = Cream,
|
color = Cream,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.ExtraBold,
|
||||||
fontSize = 12.sp,
|
fontSize = 16.sp,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
val descriptions = award.winners.mapNotNull(AwardWinner::handDescription).distinct()
|
val descriptions = award.winners.mapNotNull(AwardWinner::handDescription).distinct()
|
||||||
if (descriptions.isNotEmpty()) {
|
if (descriptions.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
descriptions.joinToString(" / "),
|
descriptions.joinToString(" / "),
|
||||||
color = GoldLight.copy(alpha = 0.82f),
|
color = GoldLight,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 10.sp,
|
fontSize = 14.sp,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class HandSummaryTest {
|
|||||||
assertEquals("Ada wins", summary.title)
|
assertEquals("Ada wins", summary.title)
|
||||||
assertEquals("Showdown • Pot 16 • You lost 8", summary.detail)
|
assertEquals("Showdown • Pot 16 • You lost 8", summary.detail)
|
||||||
assertEquals(7, summary.handNumber)
|
assertEquals(7, summary.handNumber)
|
||||||
|
assertEquals(cardsOf("2c 7d 9s Jc 3h").toList(), summary.board)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -151,6 +152,7 @@ class VisibleHandSummaryTest {
|
|||||||
|
|
||||||
private val summary = HandSummary(
|
private val summary = HandSummary(
|
||||||
handNumber = 4,
|
handNumber = 4,
|
||||||
|
board = emptyList(),
|
||||||
winnerLabels = listOf("Ada"),
|
winnerLabels = listOf("Ada"),
|
||||||
heroWon = false,
|
heroWon = false,
|
||||||
potSize = 20,
|
potSize = 20,
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.jsjdesigns.poker
|
||||||
|
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TableOrbitTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `five opponents form a symmetric upper ellipse above the board band`() {
|
||||||
|
val positions = opponentSeatOrbit()
|
||||||
|
|
||||||
|
assertEquals(5, positions.size)
|
||||||
|
assertTrue(positions.zipWithNext().all { (left, right) -> left.centerX < right.centerX })
|
||||||
|
assertTrue(positions.all { it.centerX in 0f..1f })
|
||||||
|
assertTrue(
|
||||||
|
"opponent tops must stay in the upper fifth, leaving the middle to the board",
|
||||||
|
positions.all { it.topY < 0.21f },
|
||||||
|
)
|
||||||
|
assertEquals(1f, positions.first().centerX + positions.last().centerX, 0.001f)
|
||||||
|
assertEquals(positions.first().topY, positions.last().topY, 0.001f)
|
||||||
|
assertEquals(1f, positions[1].centerX + positions[3].centerX, 0.001f)
|
||||||
|
assertEquals(positions[1].topY, positions[3].topY, 0.001f)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user