diff --git a/assets/audio/README.md b/assets/audio/README.md new file mode 100644 index 0000000..27a3a11 --- /dev/null +++ b/assets/audio/README.md @@ -0,0 +1,4 @@ +صداهای بازی را اینجا بگذارید: + shuffle.mp3 — صدای بُر زدن/پخش کارت (هنگام شروع هر دست پخش می‌شود) +اگر این فایل نباشد، بازی بی‌صدا اجرا می‌شود (بدون خطا). +می‌توانید بعداً play.mp3 / win.mp3 و ... هم اضافه کنید. diff --git a/assets/audio/freesound_community-riffle-card-shuffle-104313.mp3 b/assets/audio/freesound_community-riffle-card-shuffle-104313.mp3 new file mode 100644 index 0000000..e9202c0 Binary files /dev/null and b/assets/audio/freesound_community-riffle-card-shuffle-104313.mp3 differ diff --git a/assets/audio/oxidvideos-placing-playing-card-522514.mp3 b/assets/audio/oxidvideos-placing-playing-card-522514.mp3 new file mode 100644 index 0000000..3534b90 Binary files /dev/null and b/assets/audio/oxidvideos-placing-playing-card-522514.mp3 differ diff --git a/assets/audio/oxidvideos-taking-playing-card-522520.mp3 b/assets/audio/oxidvideos-taking-playing-card-522520.mp3 new file mode 100644 index 0000000..d7e7089 Binary files /dev/null and b/assets/audio/oxidvideos-taking-playing-card-522520.mp3 differ diff --git a/assets/audio/u_56l164mftx-carddrop-447013.mp3 b/assets/audio/u_56l164mftx-carddrop-447013.mp3 new file mode 100644 index 0000000..ff65e55 Binary files /dev/null and b/assets/audio/u_56l164mftx-carddrop-447013.mp3 differ diff --git a/lib/features/game/flame/card_component.dart b/lib/features/game/flame/card_component.dart index 258ea96..fa4cc59 100644 --- a/lib/features/game/flame/card_component.dart +++ b/lib/features/game/flame/card_component.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; /// یک کارت روی میز؛ اگر تصویر `assets/images/cards/.png` موجود باشد از آن /// استفاده می‌کند، وگرنه نسخه‌ی برداری می‌کشد. برای پشت کارت `back.jpg`. -/// کارت‌های قابل‌بازی هم با tap و هم با کشیدن (drag) به سمت زمین بازی می‌شوند. +/// کارت‌های قابل‌بازی فقط با **کشیدن (drag)** به سمت زمین بازی می‌شوند (نه tap). class CardComponent extends PositionComponent - with TapCallbacks, DragCallbacks, HasGameReference { + with DragCallbacks, HasGameReference { final String code; // مثل "AS"؛ برای پشت کارت خالی final bool faceUp; VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت @@ -42,11 +42,6 @@ class CardComponent extends PositionComponent } } - @override - void onTapUp(TapUpEvent event) { - if (_playable) onPlay!.call(); - } - @override void onDragStart(DragStartEvent event) { super.onDragStart(event); diff --git a/lib/features/game/flame/hokm_game.dart b/lib/features/game/flame/hokm_game.dart index f3b63ae..a8fa75d 100644 --- a/lib/features/game/flame/hokm_game.dart +++ b/lib/features/game/flame/hokm_game.dart @@ -4,6 +4,7 @@ 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 '../game_cubit.dart'; @@ -40,6 +41,7 @@ class HokmGame extends FlameGame { double _shake = 0; double _t = 0; List _prevTrick = const []; + int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن HokmGame(this.cubit); @@ -49,19 +51,31 @@ class HokmGame extends FlameGame { @override Future onLoad() async { add(Felt()); + // پیش‌بارگذاریِ صدای بُر زدن (در صورت نبودِ فایل، بی‌صدا رد می‌شود). + FlameAudio.audioCache.load('shuffle.mp3').catchError((_) => Uri()); _sub = cubit.stream.listen((ui) { if (ui.state == null) return; - _checkCut(ui.state!); - _s = ui.state; + final s = ui.state!; + _checkCut(s); + // شروعِ دستِ جدید (پخشِ کارت): صدای بُر زدن. + if (_prevHandSize == 0 && s.yourHand.isNotEmpty) _playShuffle(); + _prevHandSize = s.yourHand.length; + _s = s; _relayout(); }); if (cubit.state.state != null) { _prevTrick = cubit.state.state!.trick.map((t) => t.card).toList(); + _prevHandSize = cubit.state.state!.yourHand.length; _s = cubit.state.state; _relayout(); } } + void _playShuffle() { + // اگر فایل صدا نباشد، خطا نادیده گرفته می‌شود (بازی بی‌صدا). + FlameAudio.play('shuffle.mp3', volume: 0.7).then((_) {}, onError: (_) {}); + } + @override void onGameResize(Vector2 size) { super.onGameResize(size); @@ -173,16 +187,21 @@ class HokmGame extends FlameGame { } } - // کارت‌های دستِ خودتان بزرگ‌تر از کارت‌های روی میز. + // چیدمانِ بادبزنیِ منحنی: کارت‌ها چرخش و قوسِ ملایم دارند (مثل دستِ واقعی). final hw = size.x * 0.225, hh = hw * kCardRatio; - final handW = size.x * 0.80; - final step = n > 1 ? (handW - hw) / (n - 1) : 0.0; - final startX = size.x / 2 - (step * (n - 1)) / 2; - final y = size.y * 0.85; + final handW = size.x * 0.86; + final stepX = n > 1 ? ((handW - hw) / (n - 1)).clamp(0.0, hw * 0.62) : 0.0; + final cx = size.x / 2; + final baseY = size.y * 0.86; + final tMax = (n - 1) / 2; + const edgeAngle = 0.34; // چرخشِ کارت‌های کناری (رادیان) + final dip = hh * 0.16; // افتِ عمودیِ کارت‌های کناری برای قوس for (var i = 0; i < n; i++) { final code = codes[i]; - final target = Vector2(startX + i * step, y); + final t = i - tMax; + final norm = tMax > 0 ? t / tMax : 0.0; + final target = Vector2(cx + t * stepX, baseY + norm * norm * dip); final legal = _legal(code); var c = _hand[code]; if (c == null) { @@ -193,6 +212,7 @@ class HokmGame extends FlameGame { } else { c.size = Vector2(hw, hh); } + c.angle = norm * edgeAngle; // چرخشِ بادبزنی c.onPlay = legal ? () => cubit.playCard(code) : null; c.dimmed = s.phase == 'playing' && s.isMyTurn && !legal; c.home = target; @@ -240,6 +260,7 @@ class HokmGame extends FlameGame { _trick[tc.card] = c; } c.size = Vector2(_cardW, _cardH); + c.angle = 0; // کارت‌های روی زمین صاف‌اند (چرخشِ بادبزنیِ دست حذف می‌شود) c.priority = 5; _moveTo(c, slot); } diff --git a/pubspec.lock b/pubspec.lock index 706a9aa..f414e99 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -9,6 +9,62 @@ packages: url: "https://pub.myket.ir" source: hosted version: "2.12.0" + audioplayers: + dependency: transitive + description: + name: audioplayers + sha256: f16640453cc47487b7de72a2b28d37c7df1ac97999849f4a46d92b1d2b0f093d + url: "https://pub.myket.ir" + source: hosted + version: "6.7.1" + audioplayers_android: + dependency: transitive + description: + name: audioplayers_android + sha256: "60a6728277228413a85755bd3ffd6fab98f6555608923813ce383b190a360605" + url: "https://pub.myket.ir" + source: hosted + version: "5.2.1" + audioplayers_darwin: + dependency: transitive + description: + name: audioplayers_darwin + sha256: c994b3bb3a921e4904ac40e013fbc94488e824fd7c1de6326f549943b0b44a91 + url: "https://pub.myket.ir" + source: hosted + version: "6.4.0" + audioplayers_linux: + dependency: transitive + description: + name: audioplayers_linux + sha256: f75bce1ce864170ef5e6a2c6a61cd3339e1a17ce11e99a25bae4474ea491d001 + url: "https://pub.myket.ir" + source: hosted + version: "4.2.1" + audioplayers_platform_interface: + dependency: transitive + description: + name: audioplayers_platform_interface + sha256: "0e2f6a919ab56d0fec272e801abc07b26ae7f31980f912f24af4748763e5a656" + url: "https://pub.myket.ir" + source: hosted + version: "7.1.1" + audioplayers_web: + dependency: transitive + description: + name: audioplayers_web + sha256: "24a6f258062bd7da8cb2157e83fccb9816a08dd306cbaaa24f9813d071470545" + url: "https://pub.myket.ir" + source: hosted + version: "5.2.1" + audioplayers_windows: + dependency: transitive + description: + name: audioplayers_windows + sha256: "95f875a96c88c3dbbcb608d4f8288e300b0113d256a81d0b3197fcc18f0dc91a" + url: "https://pub.myket.ir" + source: hosted + version: "4.3.1" bloc: dependency: transitive description: @@ -105,14 +161,38 @@ packages: url: "https://pub.myket.ir" source: hosted version: "2.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.myket.ir" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.myket.ir" + source: hosted + version: "1.1.1" flame: dependency: "direct main" description: name: flame - sha256: c50e2f39e118f5f6a6f3339ce6825ee803c7e5fada95ec50fb02eb27944e0e76 + sha256: d084557523a45ad446d2480a4fa5a1c75aade5f8eb54251418e0ed4ee73ff3b8 url: "https://pub.myket.ir" source: hosted - version: "1.30.1" + version: "1.35.1" + flame_audio: + dependency: "direct main" + description: + name: flame_audio + sha256: "47d414720769fd0f1e73b1f14f857018fe49509452fe6c412d05357c6285f987" + url: "https://pub.myket.ir" + source: hosted + version: "2.11.14" flutter: dependency: "direct main" description: flutter @@ -200,6 +280,14 @@ packages: url: "https://pub.myket.ir" source: hosted version: "17.0.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.myket.ir" + source: hosted + version: "1.6.0" http_parser: dependency: transitive description: @@ -413,6 +501,14 @@ packages: url: "https://pub.myket.ir" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.myket.ir" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: @@ -437,6 +533,14 @@ packages: url: "https://pub.myket.ir" source: hosted version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.myket.ir" + source: hosted + version: "4.5.3" vector_math: dependency: transitive description: @@ -494,5 +598,5 @@ packages: source: hosted version: "1.1.0" sdks: - dart: ">=3.8.0-0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.8.0 <4.0.0" + flutter: ">=3.35.0" diff --git a/pubspec.yaml b/pubspec.yaml index caf6985..657d38b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: flutter_secure_storage: ^10.3.1 equatable: ^2.0.8 flame: ^1.30.1 + flame_audio: ^2.11.14 # path_provider_android نسخه‌ی 2.3.x به jni (کد native + NDK) وابسته شد و build را # سنگین/کند می‌کند. تا نسخه‌ی پیش‌از‑jni پین می‌کنیم تا NDK لازم نباشد. @@ -69,6 +70,7 @@ flutter: assets: - assets/images/ - assets/images/cards/ + - assets/audio/ fonts: - family: Dana