feat: honest AI opponent in quick game
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 9s
CI / integration (pull_request) Successful in 14s
CI / ui (pull_request) Successful in 49s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m12s
New Game's quick game gains an explicit opponent selector — 🤖 AI (default) or 👤 Random player. AI starts a game seated with a pooled robot that joins and moves at once: no per-move timeout (a 7-day inactivity loss reusing the turn-timeout sweeper), chat/nudge disabled, no statistics, the opponent shown as 🤖 everywhere. The random path (disguised robot) is unchanged. Driven by one game flag (games.vs_ai), set only on AI-started games so the disguised path is never revealed; Matchmaker.StartVsAI seats the robot directly (no open pool); the robot replies event-driven via the game service's after-commit/after-create hook. Wire: vs_ai on EnqueueRequest + GameView.
This commit is contained in:
@@ -63,6 +63,18 @@ func (s *Service) handle(ctx context.Context, rt game.RobotTurn, now time.Time)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Honest-AI game: the robot moves the instant it is its turn — no sleep window and
|
||||
// no proactive nudge (chat and nudge are disabled in these games, and the player
|
||||
// chose an opponent that "moves at once"). It still plays to the same per-game
|
||||
// strength (playToWin) and margin band as the human-mimicry path.
|
||||
if rt.VsAI {
|
||||
if rt.ToMove == rt.RobotSeat {
|
||||
return s.act(ctx, rt, now)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
opp, err := s.accounts.GetByID(ctx, oppID)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -77,6 +89,40 @@ func (s *Service) handle(ctx context.Context, rt game.RobotTurn, now time.Time)
|
||||
return s.maybeNudge(ctx, rt, now)
|
||||
}
|
||||
|
||||
// DriveGame handles a single active game seating a pool robot, immediately. It backs
|
||||
// the honest-AI fast-move trigger fired by the game service after a vs_ai game is
|
||||
// created or advanced; it mirrors Drive's per-game step but for one game (false from
|
||||
// the focused read means the game is gone, finished, or holds no pooled robot).
|
||||
func (s *Service) DriveGame(ctx context.Context, gameID uuid.UUID, now time.Time) error {
|
||||
rt, ok, err := s.games.RobotTurn(ctx, gameID, s.poolIDs())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return s.handle(ctx, rt, now)
|
||||
}
|
||||
|
||||
// driveTimeout bounds a triggered, off-request honest-AI move so a stuck call cannot
|
||||
// leak a goroutine. A robot move is a quick in-process computation, so it is generous.
|
||||
const driveTimeout = 30 * time.Second
|
||||
|
||||
// TriggerMove asynchronously drives the robot's move in an honest-AI game, used by the
|
||||
// game service's after-commit/after-create hook so the robot replies at once instead of
|
||||
// waiting for the next driver scan. It returns immediately and runs on a background
|
||||
// context (the originating request's context may already be cancelled); errors are
|
||||
// logged. The periodic Drive scan remains the fallback if a trigger is missed.
|
||||
func (s *Service) TriggerMove(gameID uuid.UUID) {
|
||||
go func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), driveTimeout)
|
||||
defer cancel()
|
||||
if err := s.DriveGame(ctx, gameID, s.clock()); err != nil {
|
||||
s.log.Warn("robot trigger failed", zap.String("game", gameID.String()), zap.Error(err))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// maybeMove acts when the robot's think time has elapsed. A daytime nudge from
|
||||
// the opponent during the current turn pulls the move in to the short reply
|
||||
// window; otherwise the robot waits out its sampled delay.
|
||||
|
||||
Reference in New Issue
Block a user