Let a scoreboard actually be dismissed
The pause between hands never ended. driveIdleSeats asks awaitsConfirmation about the state, and the state does not change while the players are reading it — so confirmAdvance restarted the loop, the loop stopped on the same scoreboard, and the next hand was never dealt. The screens hid their own dialog on the way out, so the table looked released and then simply sat there. The release is now something spendable rather than a question re-asked about an unchanged state: confirmAdvance grants one, and the loop consumes it to walk past the hold exactly once. This is not new with cribbage — Golf's card, Wizard's bids against tricks and the Crazy 8s round score all stop on the same hold and all end there. Cribbage's show is what made it impossible to miss, because a game of cribbage is nothing but hands one after another. Covered by MatchControllerScoreboardTest, which plays a whole game of one-move hands and insists every one of them stops to be read and then goes on. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01DCuRESmSZeJoJond43itw2
This commit is contained in:
@@ -98,6 +98,18 @@ class MatchController<S : Any, M : Any>(
|
||||
private val lock = Mutex()
|
||||
private var sequence = 0
|
||||
|
||||
/**
|
||||
* A scoreboard a player has read and released, waiting to be let past.
|
||||
*
|
||||
* The hold belongs to a particular scoreboard rather than to the phase, and
|
||||
* releasing it has to be spent: [awaitsConfirmation] is asked about the
|
||||
* state, which has not changed while the players were reading it, so
|
||||
* without something consumable here the same state would stop the table
|
||||
* again the instant [confirmAdvance] restarted the loop — and the next hand
|
||||
* would never be dealt.
|
||||
*/
|
||||
private var released = false
|
||||
|
||||
private val peers = mutableMapOf<String, Connection>()
|
||||
private var hostLink: Connection? = null
|
||||
|
||||
@@ -288,8 +300,15 @@ class MatchController<S : Any, M : Any>(
|
||||
if (role == Role.HOST && awaitsConfirmation(current)) {
|
||||
// Hold here until a player has read the scoreboard. Only the
|
||||
// host waits; clients simply render what they were sent.
|
||||
_awaitingConfirmation.value = true
|
||||
return
|
||||
val letThrough = lock.withLock {
|
||||
val granted = released
|
||||
released = false
|
||||
granted
|
||||
}
|
||||
if (!letThrough) {
|
||||
_awaitingConfirmation.value = true
|
||||
return
|
||||
}
|
||||
}
|
||||
delay(aiThinkingMillis() * 2)
|
||||
val advanced = advanceIdle(current)
|
||||
@@ -398,7 +417,10 @@ class MatchController<S : Any, M : Any>(
|
||||
if (!_awaitingConfirmation.value) return
|
||||
_awaitingConfirmation.value = false
|
||||
if (role != Role.HOST) return
|
||||
scope.launch { driveIdleSeats() }
|
||||
scope.launch {
|
||||
lock.withLock { released = true }
|
||||
driveIdleSeats()
|
||||
}
|
||||
}
|
||||
|
||||
/** Lets a player go on looking at a table they have been told is over. */
|
||||
|
||||
Reference in New Issue
Block a user