import 'dart:math' as math; import '../../../../../core/service/app_sounds.dart'; import '../../../domain/entities/game_entities.dart'; /// ضربانِ قلبِ نوبتِ کاربر: در ثانیه‌های پایانیِ نوبتِ خودِ کاربر صدای ضربان پخش /// می‌کند و با پایانِ نوبت بلافاصله قطع می‌شود. مستقل از چیدمانِ صحنه است. /// /// زمان‌سنجِ نوبت دقیقاً هم‌زمان با «حلقه‌ی زمانِ نوبت» در HUD شروع می‌شود؛ آن حلقه /// با تغییرِ امضای نوبت (نوبت|تعدادِ کارتِ زمین|فاز) ریست می‌شود و به انیمیشنِ بُر/ /// پخش کاری ندارد. اینجا هم شمارش به همان امضا بسته می‌شود و فقط «پخشِ صدا» در حینِ /// انیمیشن/اینترو خاموش است تا صدا و تصویر یکی شوند. class TurnHeartbeat { double _elapsed = 0; bool _beatPlayed = false; double _accum = 0; bool _wasMyTurn = false; String _sig = ''; /// هر فریم صدا زده می‌شود. [busy] یعنی اینترو/بُر زدن/پخشِ کارت در جریان است /// (شمارش ادامه دارد اما صدایی پخش نمی‌شود). void update( double dt, GameState? s, { required bool silent, required bool busy, }) { final isMyTurn = s != null && ((s.phase == 'playing' && s.turn == s.yourSeat) || (s.phase == 'choose_trump' && s.hakem == s.yourSeat)); if (!isMyTurn || silent) { if (_wasMyTurn) { AppSounds.stopHeartBeat(); // نوبت تمام شد ⇒ توقفِ فوریِ صدا _wasMyTurn = false; } _sig = s == null ? '' : '${s.turn}|${s.trick.length}|${s.phase}'; return; } final sig = '${s.turn}|${s.trick.length}|${s.phase}'; if (!_wasMyTurn || sig != _sig) { // شروعِ نوبتِ من (یا نوبتِ جدید) ⇒ شمارش از صفر، هم‌زمان با حلقه‌ی HUD. _wasMyTurn = true; _sig = sig; _elapsed = 0; _beatPlayed = false; _accum = 0; if (s.phase == 'playing' && !busy) AppSounds.turn(); } _elapsed += dt; if (busy) return; final dur = (s.turnTimeoutMs > 0 ? s.turnTimeoutMs / 1000.0 : 30.0); final remaining = dur - _elapsed; final warn = math.min(7.0, dur * 0.4); // در پنجره‌ی پایانیِ نوبت، ضربان با فاصله‌ی منظم تکرار می‌شود تا تا لحظه‌ی آخر // شنیده شود (نه فقط یک ضربانِ زودهنگام که در سکوتِ پایانی گم می‌شود). if (remaining > 0 && remaining <= warn) { if (!_beatPlayed) { _beatPlayed = true; _accum = 0; AppSounds.heartBeat(); } else { _accum += dt; if (_accum >= 0.95) { _accum = 0; AppSounds.heartBeat(); } } } } void stop() => AppSounds.stopHeartBeat(); }