feat: style

This commit is contained in:
2026-06-17 08:04:10 +03:30
parent 32c0e543d9
commit 943ba13872
9 changed files with 145 additions and 19 deletions
+2 -7
View File
@@ -5,9 +5,9 @@ import 'package:flutter/material.dart';
/// یک کارت روی میز؛ اگر تصویر `assets/images/cards/<code>.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);
+29 -8
View File
@@ -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<String> _prevTrick = const [];
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
HokmGame(this.cubit);
@@ -49,19 +51,31 @@ 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;
_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);
}