feat: add sounds

This commit is contained in:
2026-06-18 21:57:28 +03:30
parent 519752b478
commit f4d99043c4
18 changed files with 312 additions and 55 deletions
@@ -4,13 +4,14 @@ import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame_audio/flame_audio.dart';
import 'package:flutter/material.dart';
import '../../../../../core/service/app_sounds.dart';
import '../../../domain/entities/game_entities.dart';
import '../../bloc/game_bloc.dart';
import 'card_codes.dart';
import 'card_component.dart';
import 'shuffle_animation.dart';
import 'table_pieces.dart';
/// صحنه‌ی میز حکم با انیمیشن. شما همیشه پایین میز (rel=0) هستید.
@@ -42,6 +43,9 @@ class HokmGame extends FlameGame {
double _t = 0;
List<String> _prevTrick = const [];
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
bool _introActive = true; // تا پایانِ نمایشِ «جستجوی حریف»
bool _pendingShuffle = false; // بُرِ دستِ اول که تا پایانِ اینترو به تعویق افتاده
HokmGame(this.cubit);
@@ -51,14 +55,20 @@ class HokmGame extends FlameGame {
@override
Future<void> onLoad() async {
add(Felt());
// پیش‌بارگذاریِ صدای بُر زدن (در صورت نبودِ فایل، بی‌صدا رد می‌شود).
FlameAudio.audioCache.load('shuffle.mp3').catchError((_) => Uri());
_sub = cubit.stream.listen((ui) {
if (ui.state == null) return;
final s = ui.state!;
_checkCut(s);
// شروعِ دستِ جدید (پخشِ کارت): صدای بُر زدن.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty) _playShuffle();
_checkTrickSounds(s);
// شروعِ دستِ جدید (پخشِ کارت): انیمیشن و صدای بُر زدن.
// دستِ اول تا پایانِ نمایشِ «جستجوی حریف» به تعویق می‌افتد تا انیمیشن
// زیرِ اوورلی پنهان نشود و صدا با تصویر هماهنگ باشد.
if (_prevHandSize == 0 && s.yourHand.isNotEmpty && !_silent) {
if (_introActive) {
_pendingShuffle = true;
} else {
_shuffleNow();
}
}
_prevHandSize = s.yourHand.length;
_s = s;
_relayout();
@@ -71,11 +81,6 @@ class HokmGame extends FlameGame {
}
}
void _playShuffle() {
// اگر فایل صدا نباشد، خطا نادیده گرفته می‌شود (بازی بی‌صدا).
FlameAudio.play('shuffle.mp3', volume: 0.7).then((_) {}, onError: (_) {});
}
@override
void onGameResize(Vector2 size) {
super.onGameResize(size);
@@ -88,24 +93,57 @@ class HokmGame extends FlameGame {
super.onRemove();
}
// ===== افکتِ بریدن با حکم (تکان + رعد) =====
// ===== صداها و افکتِ بریدن با حکم (تکان + رعد) =====
// اگر دقیقاً یک کارتِ تازه به زمین اضافه شده و آن کارت «بُرِش با حکم» باشد
// (خالِ زمینه آتو نیست ولی کارتِ تازه آتوست)، همان لحظه تکان + رعد.
void _checkCut(GameState s) {
// با هر تغییرِ زمین: افزودنِ کارت ⇒ صدای گذاشتن/انداختن (و بریدن با حکم
// شمشیر + تکان + رعد)؛ خالی‌شدنِ زمین ⇒ صدای جمع‌شدنِ دست.
void _checkTrickSounds(GameState s) {
final now = s.trick.map((t) => t.card).toList();
final addedOne = now.length == _prevTrick.length + 1 && _isPrefix(_prevTrick, now);
if (addedOne &&
s.trump != null &&
s.leadSuit != null &&
s.leadSuit != s.trump &&
suitName(now.last) == s.trump) {
_shake = 1.0;
add(Lightning());
if (_silent) {
_prevTrick = now;
return;
}
final addedOne =
now.length == _prevTrick.length + 1 && _isPrefix(_prevTrick, now);
if (addedOne) {
final card = s.trick.last;
final isCut = s.trump != null &&
s.leadSuit != null &&
s.leadSuit != s.trump &&
suitName(card.card) == s.trump;
if (isCut) {
_shake = 1.0;
add(Lightning());
AppSounds.cut(); // بریدن با حکم (شمشیر)
} else {
AppSounds.placeCard(); // کارت روی میز نشست (همه‌ی بازیکنان)
}
} else if (now.isEmpty && _prevTrick.isNotEmpty) {
AppSounds.takeCard(); // دستِ کامل جمع شد
}
_prevTrick = now;
}
/// قطعِ صداهای بازی هنگام خروج از میز (جلوگیری از پخشِ صدای کارت پس از ترک).
void silence() => _silent = true;
/// پایانِ نمایشِ «جستجوی حریف»؛ اگر بُرِ دستِ اول به تعویق افتاده بود، اکنون
/// (که اوورلی کنار رفته) با صدا و تصویرِ هماهنگ اجرا می‌شود.
void endIntro() {
_introActive = false;
if (_pendingShuffle && !_silent) {
_pendingShuffle = false;
_shuffleNow();
}
}
/// اجرای هم‌زمانِ انیمیشن و صدای بُر زدن (صدا با پایانِ انیمیشن قطع می‌شود).
void _shuffleNow() {
if (_silent) return;
AppSounds.shuffle(cutoffMs: ShuffleAnimation.totalMs);
add(ShuffleAnimation(size));
}
static bool _isPrefix(List<String> a, List<String> b) {
for (var i = 0; i < a.length; i++) {
if (a[i] != b[i]) return false;