games: the seat you sit in, and the seat you are left in
Six-handed, the felt printed CO on three seats at once. Position walked the table with nextIn, which steps over folded seats, while the seat count it walked against still included them — so every muck slid the anchors round and the labels landed somewhere new. Folding the small blind relabelled it the cutoff. The two walks are a pair and they are easy to confuse. nextIn asks who is still in the betting; a fold takes you out of it. Position needs the other question — who was dealt in — because where you sit is decided when the button moves and does not change because somebody threw their hand away. So nextDealt, which skips only the seats that are not in the hand at all, and a note at both of them saying which is which. The bots never read this. They use InPosition, which really does want the last seat still live, and which is deliberately not this function. So the policy is untouched and the money never moved — the only thing this ever broke was the badge on the plate, which is precisely why nothing caught it. TestPositionsDoNotMoveWhenSeatsFold deals six-handed, asserts the table prints each of BTN/SB/BB/UTG/MP/CO exactly once, then folds the seats out from under it one at a time and asserts nobody's label moves. Claude-Session: https://claude.ai/code/session_013M5nD7PgUboJXoDcYHzpuJ
This commit is contained in:
@@ -726,3 +726,46 @@ func has(evs []Event, kind string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Where you sit is decided when the button moves, and a fold does not move it.
|
||||
// Position walked the table with nextIn, which steps over folded seats while the
|
||||
// seat count still includes them — so as players mucked, the labels slid round
|
||||
// and a six-handed felt printed CO on three different seats at once. The badge is
|
||||
// the only thing that reads this, which is exactly why nothing caught it.
|
||||
func TestPositionsDoNotMoveWhenSeatsFold(t *testing.T) {
|
||||
s := table(t, Tiers[0], 5, 200) // six-handed
|
||||
s, _, _ = ApplyMove(s, Move{Kind: Deal})
|
||||
|
||||
before := make([]string, len(s.Seats))
|
||||
for i := range s.Seats {
|
||||
before[i] = s.Position(i)
|
||||
}
|
||||
|
||||
// Every seat has its own label, and the ones a six-max table prints are these.
|
||||
seen := map[string]int{}
|
||||
for _, p := range before {
|
||||
seen[p]++
|
||||
}
|
||||
for _, want := range []string{"BTN", "SB", "BB", "UTG", "MP", "CO"} {
|
||||
if seen[want] != 1 {
|
||||
t.Errorf("six-handed: %q appears %d times, want exactly once — got %v",
|
||||
want, seen[want], before)
|
||||
}
|
||||
}
|
||||
|
||||
// Now fold seats out of the hand, one at a time. Nobody's position changes by
|
||||
// mucking — folding is done to the state directly because a fold in the engine
|
||||
// belongs to whoever is to act, and what is under test is the label, not the turn.
|
||||
for i := range s.Seats {
|
||||
if i == You || s.Seats[i].State != Active {
|
||||
continue
|
||||
}
|
||||
s.Seats[i].State = Folded
|
||||
for j := range s.Seats {
|
||||
if got := s.Position(j); got != before[j] {
|
||||
t.Fatalf("seat %d was %q and is now %q after seat %d folded — position is "+
|
||||
"where you sit, not who is left", j, before[j], got, i)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user