diff --git a/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeModel.kt b/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeModel.kt index cf8ed09..6b02b9d 100644 --- a/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeModel.kt +++ b/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeModel.kt @@ -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. */ 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. */ fun acceptsOnFoundation(foundation: List, card: Card): Boolean { val top = foundation.lastOrNull() ?: 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. */ 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 diff --git a/app/src/test/java/org/prolibertate/games/game/klondike/KlondikeRulesTest.kt b/app/src/test/java/org/prolibertate/games/game/klondike/KlondikeRulesTest.kt index e9ada33..9f62f0e 100644 --- a/app/src/test/java/org/prolibertate/games/game/klondike/KlondikeRulesTest.kt +++ b/app/src/test/java/org/prolibertate/games/game/klondike/KlondikeRulesTest.kt @@ -112,6 +112,30 @@ class KlondikeRulesTest { // ---- 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() + 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 fun `foundations run ace upwards in one suit`() { assertTrue(acceptsOnFoundation(emptyList(), c(Rank.ACE, Suit.SPADES)))