import 'package:flame/components.dart'; import 'package:flame/effects.dart'; import 'package:flame/events.dart'; import 'package:flutter/material.dart'; /// نسبت ابعاد کارت (۵۰۰×۷۲۶ منبع). const double kCardRatio = 726 / 500; /// یک کارت روی میز؛ اگر تصویر `assets/images/cards/.png` موجود باشد از آن /// استفاده می‌کند، وگرنه نسخه‌ی برداری می‌کشد. برای پشت کارت `back.jpg`. /// کارت‌های قابل‌بازی هم با tap و هم با کشیدن (drag) به سمت زمین بازی می‌شوند. class CardComponent extends PositionComponent with TapCallbacks, DragCallbacks, HasGameReference { final String code; // مثل "AS"؛ برای پشت کارت خالی final bool faceUp; VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت bool dimmed; // کارت غیرمجاز/غیرفعال Vector2? home; // موقعیت اصلی در دست (برای برگشت پس از کشیدنِ ناقص) int restPriority = 0; // ترتیب لایه‌ی اصلی در دست (برای بازگردانی پس از کشیدن) Rect? dropZone; // ناحیه‌ی وسط میز؛ رهاکردن کارت در آن یعنی بازی Sprite? _sprite; bool _dragging = false; bool _overZone = false; // کارت روی ناحیه‌ی انداختن است (برای هایلایت) CardComponent({ required this.code, required this.faceUp, this.onPlay, this.dimmed = false, super.position, super.size, super.angle, super.priority, super.anchor = Anchor.center, }); bool get _playable => onPlay != null && !dimmed; @override Future onLoad() async { try { _sprite = await game.loadSprite(faceUp ? 'cards/$code.png' : 'cards/back.jpg'); } catch (_) { _sprite = null; // fallback برداری } } @override void onTapUp(TapUpEvent event) { if (_playable) onPlay!.call(); } @override void onDragStart(DragStartEvent event) { super.onDragStart(event); if (!_playable) return; _dragging = true; priority = 1000; // روی همه‌ی کارت‌ها scale = Vector2.all(1.12); // بزرگ‌نمایی هنگام کشیدن (فیدبک) } @override void onDragUpdate(DragUpdateEvent event) { if (!_dragging) return; position += event.localDelta; _overZone = _inDropZone(); } @override void onDragEnd(DragEndEvent event) { super.onDragEnd(event); if (!_dragging) return; _dragging = false; scale = Vector2.all(1); // فقط اگر داخل ناحیه‌ی وسط میز رها شد ⇒ بازی؛ وگرنه برگشت به جای مرتبِ خود. if (_inDropZone()) { onPlay?.call(); } else { _returnHome(); } _overZone = false; } @override void onDragCancel(DragCancelEvent event) { super.onDragCancel(event); if (!_dragging) return; _dragging = false; _overZone = false; scale = Vector2.all(1); _returnHome(); } bool _inDropZone() { final z = dropZone; if (z != null) return z.contains(position.toOffset()); return position.y < game.size.y * 0.72; // fallback } void _returnHome() { priority = restPriority; // بازگردانی ترتیب لایه تا روی کارت‌های دیگر نیفتد if (home == null) return; add(MoveToEffect(home!, EffectController(duration: 0.2, curve: Curves.easeOut))); } @override void render(Canvas canvas) { final rect = size.toRect(); final radius = Radius.circular(size.x * 0.09); final rrect = RRect.fromRectAndRadius(rect, radius); if (_sprite != null) { canvas.save(); canvas.clipRRect(rrect); _sprite!.render(canvas, size: size); canvas.restore(); } else if (faceUp) { _drawVectorFace(canvas, rrect); } else { _drawVectorBack(canvas, rrect); } if (dimmed) { canvas.drawRRect(rrect, Paint()..color = const Color(0x73000000)); } canvas.drawRRect( rrect, Paint() ..style = PaintingStyle.stroke ..strokeWidth = 1.5 ..color = const Color(0xFF1A1A1A), ); // هایلایت طلایی وقتی کارت روی ناحیه‌ی انداختن است (رهاکنی، بازی می‌شود). if (_dragging && _overZone) { canvas.drawRRect( rrect, Paint() ..style = PaintingStyle.stroke ..strokeWidth = size.x * 0.06 ..color = const Color(0xFFE9B949) ..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6), ); } } void _drawVectorBack(Canvas canvas, RRect rrect) { canvas.drawRRect(rrect, Paint()..color = const Color(0xFF1C3A66)); final inner = rrect.deflate(size.x * 0.08); canvas.drawRRect( inner, Paint() ..style = PaintingStyle.stroke ..strokeWidth = 2 ..color = const Color(0xFFE9B949), ); } void _drawVectorFace(Canvas canvas, RRect rrect) { canvas.drawRRect(rrect, Paint()..color = Colors.white); final rank = code.substring(0, code.length - 1); final suit = code.substring(code.length - 1); final (symbol, color) = _suit(suit); _text(canvas, '$rank$symbol', size.x * 0.26, color, Offset(size.x * 0.08, size.y * 0.05)); // نماد بزرگ وسط _text(canvas, symbol, size.x * 0.5, color, Offset(size.x * 0.5, size.y * 0.5), center: true); } (String, Color) _suit(String s) { switch (s) { case 'H': return ('♥', const Color(0xFFD32F2F)); case 'D': return ('♦', const Color(0xFFD32F2F)); case 'C': return ('♣', const Color(0xFF1A1A1A)); default: return ('♠', const Color(0xFF1A1A1A)); } } void _text(Canvas canvas, String s, double fontSize, Color color, Offset at, {bool center = false}) { final tp = TextPainter( text: TextSpan( text: s, style: TextStyle(color: color, fontSize: fontSize)), textDirection: TextDirection.ltr, )..layout(); final offset = center ? at - Offset(tp.width / 2, tp.height / 2) : at; tp.paint(canvas, offset); } }