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 6b02b9d..24f29b5 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 @@ -5,6 +5,7 @@ import kotlinx.serialization.Serializable import org.prolibertate.games.game.cards.Card import org.prolibertate.games.game.cards.Rank import org.prolibertate.games.game.cards.Suit +import org.prolibertate.games.game.solitaire.buildsDownAlternating const val TABLEAU_PILES: Int = 7 const val FOUNDATIONS: Int = 4 @@ -121,25 +122,13 @@ data class MoveCards(val from: Spot, val to: Spot, val count: Int = 1) : Klondik fun foundationFor(card: Card): Int = card.suit.ordinal /** - * Where a rank sits when the ace is low. + * Down by one and in the other colour. * - * [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. + * Named here because it is what this game builds with, but the rule itself is + * shared: FreeCell builds the same way, Spider builds in suit, and all of them + * need the same ace-low ordering underneath. */ -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.patienceOrder == top.rank.patienceOrder + 1 -} - -/** Down by one, and in the other colour. */ -fun buildsDown(lower: Card, onto: Card): Boolean = - lower.rank.patienceOrder == onto.rank.patienceOrder - 1 && lower.suit.isRed != onto.suit.isRed +fun buildsDown(lower: Card, onto: Card): Boolean = buildsDownAlternating(lower, onto) /** * Whether [cards] is a run that can travel as one: each card a step below the diff --git a/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeRules.kt b/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeRules.kt index ce1c175..5419e0c 100644 --- a/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeRules.kt +++ b/app/src/main/java/org/prolibertate/games/game/klondike/KlondikeRules.kt @@ -8,6 +8,7 @@ import org.prolibertate.games.game.cards.Decks import org.prolibertate.games.game.cards.shuffledWith import org.prolibertate.games.game.engine.GameRules import org.prolibertate.games.game.engine.TableConfig +import org.prolibertate.games.game.solitaire.acceptsOnFoundation import kotlin.random.Random /** diff --git a/app/src/main/java/org/prolibertate/games/game/solitaire/Patience.kt b/app/src/main/java/org/prolibertate/games/game/solitaire/Patience.kt new file mode 100644 index 0000000..653561f --- /dev/null +++ b/app/src/main/java/org/prolibertate/games/game/solitaire/Patience.kt @@ -0,0 +1,41 @@ +package org.prolibertate.games.game.solitaire + +import org.prolibertate.games.game.cards.Card +import org.prolibertate.games.game.cards.Rank + +/** + * Where a rank sits when the ace is low. + * + * [Rank.order] puts the ace at fourteen, which is right for the games this deck + * was built for: in Euchre, Kaiser, Wizard and the rest it is the highest card + * there is. Every patience in this app counts it the other way — foundations run + * ace upwards, columns run down to an ace, and Pyramid needs it to be worth one + * when a pair has to make thirteen. + * + * It lives here rather than on [Rank] because ace high is correct everywhere + * outside these games, and it is shared rather than copied because four + * separate definitions of "the ace is low" would be four chances to disagree. + */ +val Rank.patienceOrder: Int get() = if (this == Rank.ACE) 1 else order + +/** Down by one, and in the other colour: the ordinary tableau build. */ +fun buildsDownAlternating(lower: Card, onto: Card): Boolean = + lower.rank.patienceOrder == onto.rank.patienceOrder - 1 && + lower.suit.isRed != onto.suit.isRed + +/** Down by one in the same suit, which is what Spider pays for. */ +fun buildsDownInSuit(lower: Card, onto: Card): Boolean = + lower.rank.patienceOrder == onto.rank.patienceOrder - 1 && lower.suit == onto.suit + +/** 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.patienceOrder == top.rank.patienceOrder + 1 +} + +/** + * Whether [cards] is a run that travels as one, by whatever rule the game + * builds with. + */ +fun isRun(cards: List, builds: (Card, Card) -> Boolean): Boolean = + cards.isNotEmpty() && cards.zipWithNext().all { (upper, lower) -> builds(lower, upper) } 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 9f62f0e..e86778f 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 @@ -13,6 +13,8 @@ import org.prolibertate.games.game.cards.Suit import org.prolibertate.games.game.engine.PlayerKind import org.prolibertate.games.game.engine.PlayerSlot import org.prolibertate.games.game.engine.TableConfig +import org.prolibertate.games.game.solitaire.acceptsOnFoundation +import org.prolibertate.games.game.solitaire.patienceOrder class KlondikeRulesTest {