Keep won tricks in front of the player who took them
A trick slid off towards its winner and stopped existing. Where it went was the only record that it had been won at all, and by the third trick of a hand there was nothing on the table to say who was sitting on what — which on a real table is the most-glanced-at thing in the game, and the whole reason cards are gathered rather than binned. Each player now has a face-down pile in front of them: one card back per trick, because a trick is squared into a single bundle and put down, and from above that is what you see. The piles sit beside each player's own card, one place clockwise along their edge, which is both where a player would actually put them and the one spot on each edge nothing else was using. Wizard seats three to six so its piles go half a seat clockwise round the same ring the cards are played on. The count follows the cards, not the rules. A trick is counted the instant its last card is played, which is a beat before the gather and two before the cards arrive; reading the state directly grew the winner's pile while the trick was still lying in the middle of the table, and the cards then slid over and landed on a pile that had already counted them. rememberArrivedTricks holds the count back until the sweep has actually run. The sweep changes to suit its new destination. A gathered card is squared down towards the size of the bundle it is joining, and stays whole until the last stretch instead of fading the whole way over: it now vanishes at the same moment the pile grows by one, and that swap is only invisible if there was something there to swap. Kaiser needed a per-seat trick count, which it had never had — it scores in points, so no rule wanted one. It is defaulted for the wire and carries a test, because a field no rule depends on is a field that can quietly stop being maintained. Hearts already keeps the cards themselves and counts them by the deal. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_0132V9gE6DF9itz2z3Hn7BK9
This commit is contained in:
@@ -250,6 +250,39 @@ class KaiserRulesTest {
|
||||
assertEquals("all four held for display", 4, state.completedTrick.size)
|
||||
}
|
||||
|
||||
/**
|
||||
* Kaiser scores in points, not tricks, so nothing in the rules reads this
|
||||
* count — the table does, to draw the cards each player has taken. That is
|
||||
* exactly why it is worth a test: a field no rule depends on is a field
|
||||
* that can quietly stop being maintained.
|
||||
*/
|
||||
@Test
|
||||
fun `tricks are counted to the seat that took them`() {
|
||||
// Everything is clubs and nothing is trump, so each trick simply goes
|
||||
// to the highest card in it: the ace, then the king.
|
||||
val hands = listOf(
|
||||
listOf(Card(Rank.ACE, Suit.CLUBS), Card(Rank.SEVEN, Suit.CLUBS)),
|
||||
listOf(Card(Rank.EIGHT, Suit.CLUBS), Card(Rank.NINE, Suit.CLUBS)),
|
||||
listOf(Card(Rank.TEN, Suit.CLUBS), Card(Rank.JACK, Suit.CLUBS)),
|
||||
listOf(Card(Rank.QUEEN, Suit.CLUBS), Card(Rank.KING, Suit.CLUBS)),
|
||||
)
|
||||
var state = playingState(hands, trump = Suit.SPADES)
|
||||
assertEquals("nothing taken at the deal", listOf(0, 0, 0, 0), state.tricksWon)
|
||||
|
||||
repeat(4) {
|
||||
val seat = KaiserRules.currentSeat(state)!!
|
||||
state = KaiserRules.applyMove(state, seat, PlayCard(state.hands[seat].first()))
|
||||
}
|
||||
assertEquals("the ace takes the first", listOf(1, 0, 0, 0), state.tricksWon)
|
||||
|
||||
repeat(4) {
|
||||
val seat = KaiserRules.currentSeat(state)!!
|
||||
state = KaiserRules.applyMove(state, seat, PlayCard(state.hands[seat].first()))
|
||||
}
|
||||
assertEquals("the king takes the second", listOf(1, 0, 0, 1), state.tricksWon)
|
||||
assertEquals("and they add up", 2, state.tricksWon.sum())
|
||||
}
|
||||
|
||||
// -- Scoring ------------------------------------------------------------
|
||||
|
||||
@Test
|
||||
|
||||
@@ -209,6 +209,34 @@ class CardPhysicsTest {
|
||||
hand.forEach { assertTrue(sweepProgress(0.95f, it) > 0.5f) }
|
||||
}
|
||||
|
||||
/**
|
||||
* A gathered card has to still be whole when it reaches the winner's pile,
|
||||
* because it vanishes at the same moment the pile grows by one and that
|
||||
* swap is only invisible if there was something there to swap.
|
||||
*/
|
||||
@Test
|
||||
fun `a gathered card is whole most of the way and gone at the end`() {
|
||||
assertEquals(1f, gatherAlpha(0f), 0f)
|
||||
assertEquals(1f, gatherAlpha(0.5f), 0f)
|
||||
assertEquals(0f, gatherAlpha(1f), 0f)
|
||||
var previous = 1f
|
||||
for (step in 0..100) {
|
||||
val now = gatherAlpha(step / 100f)
|
||||
assertTrue("went back up at $step", now <= previous + 0.0001f)
|
||||
assertTrue(now in 0f..1f)
|
||||
previous = now
|
||||
}
|
||||
}
|
||||
|
||||
/** And is squared down into the bundle rather than arriving full size. */
|
||||
@Test
|
||||
fun `a gathered card is squared down on the way`() {
|
||||
assertEquals(1f, gatherScale(0f), 0f)
|
||||
assertTrue(gatherScale(1f) < 1f)
|
||||
assertTrue(gatherScale(1f) > 0.5f)
|
||||
assertTrue(gatherScale(0.5f) < gatherScale(0.25f))
|
||||
}
|
||||
|
||||
/** A trick that never completes must not leave a card part way off the table. */
|
||||
@Test
|
||||
fun `a sweep that never starts moves nothing`() {
|
||||
|
||||
Reference in New Issue
Block a user