Count the ace low in Klondike
Rank.order puts the ace at fourteen, which is right for the games this deck was built for -- in Euchre, Kaiser and the rest it is the highest card there is. Patience counts it the other way: foundations run ace upwards and columns run down to an ace. Using the raw order meant a two could never follow an ace home and an ace could never be laid on a two, so no foundation could be built past its first card. Nothing about the deal or the moves would have looked wrong; the game would simply never have gone out. patienceOrder is local to this game rather than a change to Rank, because ace high is correct everywhere else in the app. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Urn6eEBA6XzrnfoibL7faC
This commit is contained in:
@@ -120,16 +120,26 @@ data class MoveCards(val from: Spot, val to: Spot, val count: Int = 1) : Klondik
|
|||||||
/** Foundations are indexed by suit, so a card always knows which one it wants. */
|
/** Foundations are indexed by suit, so a card always knows which one it wants. */
|
||||||
fun foundationFor(card: Card): Int = card.suit.ordinal
|
fun foundationFor(card: Card): Int = card.suit.ordinal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Where a rank sits when the ace is low.
|
||||||
|
*
|
||||||
|
* [Rank.order] puts the ace at fourteen, because the games this deck was built
|
||||||
|
* for are trick-taking ones where it is the highest card there is. Patience
|
||||||
|
* counts it the other way: foundations run ace upwards and columns run down to
|
||||||
|
* an ace, so every comparison here has to use this and not the raw order.
|
||||||
|
*/
|
||||||
|
val Rank.patienceOrder: Int get() = if (this == Rank.ACE) 1 else order
|
||||||
|
|
||||||
/** Ace first, then strictly upwards in the same suit. */
|
/** Ace first, then strictly upwards in the same suit. */
|
||||||
fun acceptsOnFoundation(foundation: List<Card>, card: Card): Boolean {
|
fun acceptsOnFoundation(foundation: List<Card>, card: Card): Boolean {
|
||||||
val top = foundation.lastOrNull()
|
val top = foundation.lastOrNull()
|
||||||
?: return card.rank == Rank.ACE
|
?: return card.rank == Rank.ACE
|
||||||
return card.suit == top.suit && card.rank.order == top.rank.order + 1
|
return card.suit == top.suit && card.rank.patienceOrder == top.rank.patienceOrder + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Down by one, and in the other colour. */
|
/** Down by one, and in the other colour. */
|
||||||
fun buildsDown(lower: Card, onto: Card): Boolean =
|
fun buildsDown(lower: Card, onto: Card): Boolean =
|
||||||
lower.rank.order == onto.rank.order - 1 && lower.suit.isRed != onto.suit.isRed
|
lower.rank.patienceOrder == onto.rank.patienceOrder - 1 && lower.suit.isRed != onto.suit.isRed
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether [cards] is a run that can travel as one: each card a step below the
|
* Whether [cards] is a run that can travel as one: each card a step below the
|
||||||
|
|||||||
@@ -112,6 +112,30 @@ class KlondikeRulesTest {
|
|||||||
|
|
||||||
// ---- building ----------------------------------------------------------
|
// ---- building ----------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `the ace is low here, whatever the rest of the deck thinks`() {
|
||||||
|
// Rank.order puts the ace at fourteen, because the deck was built for
|
||||||
|
// trick-taking games where it is the highest card there is. Patience
|
||||||
|
// counts it as one, and every comparison in this game has to agree —
|
||||||
|
// otherwise a two can never follow an ace home and the game is
|
||||||
|
// unwinnable in a way nothing else would show.
|
||||||
|
assertEquals(14, Rank.ACE.order)
|
||||||
|
assertEquals(1, Rank.ACE.patienceOrder)
|
||||||
|
assertEquals(13, Rank.KING.patienceOrder)
|
||||||
|
|
||||||
|
var foundation = emptyList<Card>()
|
||||||
|
Rank.standard.sortedBy { it.patienceOrder }.forEach { rank ->
|
||||||
|
val card = c(rank, Suit.SPADES)
|
||||||
|
assertTrue("$rank should go home next", acceptsOnFoundation(foundation, card))
|
||||||
|
foundation = foundation + card
|
||||||
|
}
|
||||||
|
assertEquals(13, foundation.size)
|
||||||
|
|
||||||
|
// And a column runs the other way, down to an ace.
|
||||||
|
assertTrue(buildsDown(c(Rank.ACE, Suit.HEARTS), c(Rank.TWO, Suit.SPADES)))
|
||||||
|
assertTrue(buildsDown(c(Rank.QUEEN, Suit.HEARTS), c(Rank.KING, Suit.SPADES)))
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `foundations run ace upwards in one suit`() {
|
fun `foundations run ace upwards in one suit`() {
|
||||||
assertTrue(acceptsOnFoundation(emptyList(), c(Rank.ACE, Suit.SPADES)))
|
assertTrue(acceptsOnFoundation(emptyList(), c(Rank.ACE, Suit.SPADES)))
|
||||||
|
|||||||
Reference in New Issue
Block a user