Caption Sequence jacks as WILD or REMOVE

A jack's power is carried by its artwork on a real deck — drawn in
profile it shows one eye and removes a chip, drawn face-on it shows two
and is wild. These cards render as rank and suit only, so J♦ and J♠ were
indistinguishable in hand despite doing opposite things.

Jacks in hand now carry the word on their face, and the hint under the
hand says what tapping will do rather than the generic "tap a highlighted
square", which was wrong for a one-eyed jack anyway — those target an
opponent's chip, not a highlighted empty square.

The caption is a general PlayingCardView option, so any later game whose
cards carry meaning beyond rank and suit can use it.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015qjfMiPUDshdKEUa6vxqzN
This commit is contained in:
Claude
2026-08-06 02:39:51 +00:00
parent 39a312b157
commit 60ddcdf67f
3 changed files with 51 additions and 4 deletions
@@ -41,6 +41,12 @@ fun PlayingCardView(
width: Dp = 64.dp,
selected: Boolean = false,
enabled: Boolean = true,
/**
* Short note printed on the face, for when the rank and suit alone do not
* say what the card does — a Sequence jack being the obvious case, since
* one-eyed and two-eyed jacks are only distinguishable by their artwork.
*/
caption: String? = null,
onClick: (() -> Unit)? = null,
) {
val ink = if (card.suit.isRed) CardRed else CardBlack
@@ -61,6 +67,8 @@ fun PlayingCardView(
),
contentAlignment = Alignment.Center,
) {
// Shrink the pips slightly when a caption has to share the face.
val pipScale = if (caption == null) 0.34f else 0.28f
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
@@ -69,13 +77,22 @@ fun PlayingCardView(
text = card.rank.short,
color = ink,
fontWeight = FontWeight.Bold,
fontSize = (width.value * 0.34f).sp,
fontSize = (width.value * pipScale).sp,
)
Text(
text = card.suit.symbol,
color = ink,
fontSize = (width.value * 0.34f).sp,
fontSize = (width.value * pipScale).sp,
)
if (caption != null) {
Text(
text = caption,
color = ink,
fontWeight = FontWeight.Bold,
fontSize = (width.value * 0.145f).sp,
maxLines = 1,
)
}
}
}
}
@@ -35,6 +35,8 @@ import androidx.compose.ui.unit.sp
import org.prolibertate.games.game.cards.Card
import org.prolibertate.games.game.sequence.BOARD_SIZE
import org.prolibertate.games.game.sequence.ExchangeDeadCard
import org.prolibertate.games.game.sequence.isOneEyedJack
import org.prolibertate.games.game.sequence.isTwoEyedJack
import org.prolibertate.games.game.sequence.NO_TEAM
import org.prolibertate.games.game.sequence.PlaceChip
import org.prolibertate.games.game.sequence.RemoveChip
@@ -54,6 +56,17 @@ import org.prolibertate.games.ui.theme.TeamColours
*/
private val HighlightAmber = Color(0xFFFFC107)
/**
* What a jack does, printed on its face. On a real deck this is carried by the
* artwork — a jack shown in profile has one eye and removes a chip, one shown
* face-on has two and is wild — which a rank-and-suit card cannot convey.
*/
private fun jackCaption(card: Card): String? = when {
isTwoEyedJack(card) -> "WILD"
isOneEyedJack(card) -> "REMOVE"
else -> null
}
/**
* The Sequence board.
*
@@ -294,6 +307,9 @@ private fun HandStrip(
width = cardWidth,
selected = card == selected,
enabled = card in playable,
// A jack's power comes from its artwork, which these cards
// do not have, so spell it out on the face instead.
caption = jackCaption(card),
onClick = {
val dead = exchanges[card]
if (dead != null) onExchange(dead) else onSelect(card)
@@ -301,9 +317,18 @@ private fun HandStrip(
)
}
}
selected?.let {
selected?.let { card ->
Text(
text = "Tap a highlighted square to play ${it.label}.",
text = when {
isTwoEyedJack(card) ->
"${card.label} is wild — tap any empty square."
isOneEyedJack(card) ->
"${card.label} removes a chip — tap an opponent's, " +
"as long as it isn't part of a finished sequence."
else -> "Tap a highlighted square to play ${card.label}."
},
style = MaterialTheme.typography.bodySmall,
)
}
+5
View File
@@ -53,6 +53,11 @@ Play one card and place a chip on a matching empty square, then draw.
- **Two-eyed jacks** (♦ and ♣) are wild: place a chip on any empty square.
- **One-eyed jacks** (♥ and ♠) remove one opposing chip — but never a chip that
is part of a completed sequence, and never your own team's chip.
On a physical deck the difference is carried by the artwork: a jack drawn in
profile shows one eye, a jack drawn face-on shows two. These cards are drawn as
rank and suit only, so a jack in hand is captioned **WILD** or **REMOVE**
instead.
- **Dead card — *toggle*, on by default.** If both squares printed with your card
are already occupied, you may discard it and draw a replacement. This does not
cost your turn, and you may do it once per turn.