Stage 17 round 6 (#7): reset the nudge cooldown once the player acts
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m4s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 8s
CI / integration (pull_request) Successful in 11s
CI / ui (pull_request) Successful in 30s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m4s
The hourly nudge cooldown now clears as soon as the sender has moved or posted a chat since their last nudge — engagement lifts the 'don't spam' limit. Backend: Nudge checks game.LastMoveAt + the sender's last non-nudge chat against the last nudge time (GameReader gains LastMoveAt). UI: nudgeOnCooldown mirrors it — a chat reset is read from the message list, a move is tracked client-side (lastActedAt on commit/pass/exchange; the backend stays authoritative across a reload). Integration test covers the reset.
This commit is contained in:
@@ -226,6 +226,13 @@ func (svc *Service) RobotSchedule(ctx context.Context, gameID uuid.UUID) (seed i
|
||||
return svc.store.RobotSchedule(ctx, gameID)
|
||||
}
|
||||
|
||||
// LastMoveAt returns the time of an account's most recent move in a game (and whether it
|
||||
// has moved). The social service uses it to reset the nudge cooldown once a player has
|
||||
// taken a turn (Stage 17).
|
||||
func (svc *Service) LastMoveAt(ctx context.Context, gameID, accountID uuid.UUID) (time.Time, bool, error) {
|
||||
return svc.store.LastMoveAt(ctx, gameID, accountID)
|
||||
}
|
||||
|
||||
// transition validates the actor and turn, applies op under the per-game lock and
|
||||
// commits the result.
|
||||
func (svc *Service) transition(ctx context.Context, gameID, accountID uuid.UUID, op engineOp) (MoveResult, error) {
|
||||
|
||||
@@ -651,6 +651,25 @@ func (s *Store) GameSeed(ctx context.Context, id uuid.UUID) (int64, error) {
|
||||
return row.Seed, nil
|
||||
}
|
||||
|
||||
// LastMoveAt returns the time of the account's most recent move in the game and true, or
|
||||
// the zero time and false when it has not moved. The social service uses it to reset the
|
||||
// nudge cooldown once the player has taken a turn (Stage 17).
|
||||
func (s *Store) LastMoveAt(ctx context.Context, gameID, accountID uuid.UUID) (time.Time, bool, error) {
|
||||
var at sql.NullTime
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT MAX(m.created_at) FROM backend.game_moves m
|
||||
JOIN backend.game_players p ON p.game_id = m.game_id AND p.seat = m.seat
|
||||
WHERE m.game_id = $1 AND p.account_id = $2`,
|
||||
gameID, accountID).Scan(&at)
|
||||
if err != nil {
|
||||
return time.Time{}, false, fmt.Errorf("game: last move at %s: %w", gameID, err)
|
||||
}
|
||||
if !at.Valid {
|
||||
return time.Time{}, false, nil
|
||||
}
|
||||
return at.Time, true, nil
|
||||
}
|
||||
|
||||
// RobotSchedule returns a game's bag seed and current turn-start time. The admin console
|
||||
// combines them with the robot strategy to show a robot seat's play-to-win intent and its
|
||||
// next-move ETA. Both are server-only state, never part of the public game view.
|
||||
|
||||
Reference in New Issue
Block a user