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:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user