feat: phase 1 of improve
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// بارشِ کاغذرنگی (کانفتی) سبک و بدونِ وابستگی — برای صفحهی بردِ بازی.
|
||||
class Confetti extends StatefulWidget {
|
||||
final int count;
|
||||
const Confetti({super.key, this.count = 60});
|
||||
|
||||
@override
|
||||
State<Confetti> createState() => _ConfettiState();
|
||||
}
|
||||
|
||||
class _ConfettiState extends State<Confetti> with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _c;
|
||||
late final List<_Piece> _pieces;
|
||||
|
||||
static const _colors = [
|
||||
Color(0xFFE9B949), // طلایی
|
||||
Color(0xFF1E5FA8), // لاجوردی
|
||||
Color(0xFFB81D2A), // شنگرف
|
||||
Color(0xFF3FA34D), // سبز
|
||||
Color(0xFFFFFFFF),
|
||||
];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final r = math.Random();
|
||||
_pieces = List.generate(widget.count, (_) {
|
||||
return _Piece(
|
||||
x: r.nextDouble(),
|
||||
delay: r.nextDouble() * 0.5,
|
||||
speed: 0.7 + r.nextDouble() * 0.6,
|
||||
drift: (r.nextDouble() - 0.5) * 0.4,
|
||||
size: 6 + r.nextDouble() * 7,
|
||||
color: _colors[r.nextInt(_colors.length)],
|
||||
rot: r.nextDouble() * math.pi,
|
||||
rotSpeed: (r.nextDouble() - 0.5) * 12,
|
||||
round: r.nextBool(),
|
||||
);
|
||||
});
|
||||
_c = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 2600),
|
||||
)..forward();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_c.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IgnorePointer(
|
||||
child: AnimatedBuilder(
|
||||
animation: _c,
|
||||
builder: (_, __) => CustomPaint(
|
||||
size: Size.infinite,
|
||||
painter: _ConfettiPainter(_pieces, _c.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Piece {
|
||||
final double x, delay, speed, drift, size, rot, rotSpeed;
|
||||
final Color color;
|
||||
final bool round;
|
||||
_Piece({
|
||||
required this.x,
|
||||
required this.delay,
|
||||
required this.speed,
|
||||
required this.drift,
|
||||
required this.size,
|
||||
required this.color,
|
||||
required this.rot,
|
||||
required this.rotSpeed,
|
||||
required this.round,
|
||||
});
|
||||
}
|
||||
|
||||
class _ConfettiPainter extends CustomPainter {
|
||||
final List<_Piece> pieces;
|
||||
final double t; // ۰..۱
|
||||
_ConfettiPainter(this.pieces, this.t);
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
for (final p in pieces) {
|
||||
final local = ((t - p.delay) * p.speed).clamp(0.0, 1.0);
|
||||
if (local <= 0) continue;
|
||||
final y = -0.1 + local * 1.25;
|
||||
final x = (p.x + p.drift * local) * size.width;
|
||||
final py = y * size.height;
|
||||
final opacity = (1.0 - local).clamp(0.0, 1.0);
|
||||
final paint = Paint()..color = p.color.withValues(alpha: opacity);
|
||||
|
||||
canvas.save();
|
||||
canvas.translate(x, py);
|
||||
canvas.rotate(p.rot + p.rotSpeed * local);
|
||||
if (p.round) {
|
||||
canvas.drawCircle(Offset.zero, p.size / 2, paint);
|
||||
} else {
|
||||
canvas.drawRect(
|
||||
Rect.fromCenter(center: Offset.zero, width: p.size, height: p.size * 0.6),
|
||||
paint,
|
||||
);
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _ConfettiPainter old) => old.t != t;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show HapticFeedback;
|
||||
|
||||
import '../../../../../core/network/card_images.dart';
|
||||
import '../../../../../core/theme/app_theme.dart';
|
||||
@@ -17,6 +18,7 @@ class CardComponent extends PositionComponent
|
||||
VoidCallback? onPlay; // در صورت مجاز بودن، بازیِ این کارت
|
||||
bool dimmed; // کارت غیرمجاز/غیرفعال
|
||||
Vector2? home; // موقعیت اصلی در دست (برای برگشت پس از کشیدنِ ناقص)
|
||||
bool highlight = false; // کارتِ برندهی دست ⇒ درخششِ طلایی پیش از جمعشدن
|
||||
int restPriority = 0; // ترتیب لایهی اصلی در دست (برای بازگردانی پس از کشیدن)
|
||||
Rect? dropZone; // ناحیهی وسط میز؛ رهاکردن کارت در آن یعنی بازی
|
||||
Sprite? _front;
|
||||
@@ -76,6 +78,15 @@ class CardComponent extends PositionComponent
|
||||
scale = Vector2.all(1);
|
||||
}
|
||||
|
||||
/// یک «پاپِ» کوتاه (بزرگنماییِ رفتوبرگشتی) برای کارتِ برندهی دست.
|
||||
void pulse() {
|
||||
children.whereType<ScaleEffect>().toList().forEach((e) => e.removeFromParent());
|
||||
add(ScaleEffect.to(
|
||||
Vector2.all(1.16),
|
||||
EffectController(duration: 0.18, alternate: true, curve: Curves.easeOut),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void onDragStart(DragStartEvent event) {
|
||||
super.onDragStart(event);
|
||||
@@ -101,6 +112,7 @@ class CardComponent extends PositionComponent
|
||||
// وگرنه برگشت به جای مرتبِ خود.
|
||||
if (_inDropZone()) {
|
||||
resetScale();
|
||||
HapticFeedback.selectionClick(); // بازخوردِ لمسیِ بازیِ کارت
|
||||
onPlay?.call();
|
||||
} else {
|
||||
_scaleTo(1);
|
||||
@@ -184,6 +196,18 @@ class CardComponent extends PositionComponent
|
||||
..color = AppColors.suitDark,
|
||||
);
|
||||
|
||||
// درخششِ طلاییِ کارتِ برندهی دست (فقط یک کارت همزمان ⇒ blur ارزان).
|
||||
if (highlight) {
|
||||
canvas.drawRRect(
|
||||
rrect,
|
||||
Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = size.x * 0.07
|
||||
..color = AppColors.gold
|
||||
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6),
|
||||
);
|
||||
}
|
||||
|
||||
// هایلایت طلایی: هنگام کشیدن همیشه (انتخاب)، و پررنگتر روی ناحیهی انداختن.
|
||||
if (_dragging) {
|
||||
canvas.drawRRect(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show HapticFeedback;
|
||||
|
||||
import '../../../../../core/service/app_sounds.dart';
|
||||
import '../../../../../core/theme/app_theme.dart';
|
||||
@@ -46,6 +47,7 @@ class HokmGame extends FlameGame {
|
||||
double _shake = 0;
|
||||
double _t = 0;
|
||||
List<String> _prevTrick = const [];
|
||||
bool _highlightedTrick = false; // آیا کارتِ برندهی دستِ کامل هایلایت شده (یکبار)
|
||||
int _bestTrumpRank = 0; // بالاترین رتبهی حکمِ بازیشده در دستِ جاری (برای افکتِ بریدن)
|
||||
int _prevHandSize = 0; // برای تشخیصِ پخشِ کارت (دستِ جدید) جهت صدای بُر زدن
|
||||
String _prevHandSig = ''; // امضای دست؛ برای تشخیصِ بُرِ مجدد (تغییرِ ۵ کارت در فازِ حکم)
|
||||
@@ -171,6 +173,7 @@ class HokmGame extends FlameGame {
|
||||
_shake = 1.0;
|
||||
add(Lightning());
|
||||
AppSounds.cut(); // بریدن با حکم (شمشیر)
|
||||
HapticFeedback.heavyImpact(); // ضربهی لمسیِ بُرِش با حکم
|
||||
} else {
|
||||
AppSounds.placeCard(); // کارت روی میز نشست
|
||||
}
|
||||
@@ -475,22 +478,30 @@ class HokmGame extends FlameGame {
|
||||
}
|
||||
final present = s.trick.map((t) => t.card).toSet();
|
||||
|
||||
// کارتهای trick که دیگر نیستند (دست جمع شد) ⇒ به سمت برنده برو و حذف شو.
|
||||
// کارتهای trick که دیگر نیستند (دست جمع شد) ⇒ برنده کارتها را «جمع میکند»:
|
||||
// به سمتِ جایگاهِ برنده (s.turn) پرواز میکنند، کوچک میشوند و محو (scoop).
|
||||
final winnerOrigin = _seatOrigin(_rel(s.turn));
|
||||
var i = 0;
|
||||
for (final code in _trick.keys.toList()) {
|
||||
if (present.contains(code)) continue;
|
||||
final c = _trick.remove(code)!;
|
||||
c.children.whereType<MoveToEffect>().toList().forEach(
|
||||
(e) => e.removeFromParent(),
|
||||
);
|
||||
c.add(
|
||||
SequenceEffect([
|
||||
MoveToEffect(
|
||||
_seatOrigin(_rel(s.turn)),
|
||||
EffectController(duration: 0.25, curve: Curves.easeIn),
|
||||
),
|
||||
RemoveEffect(),
|
||||
]),
|
||||
);
|
||||
c.highlight = false;
|
||||
c.priority = 30; // رویِ همه هنگام جمعشدن
|
||||
c.children.whereType<Effect>().toList().forEach((e) => e.removeFromParent());
|
||||
final delay = i * 0.05; // ترتیبِ کوتاه برای حسِ «جارو کردن»
|
||||
c.add(ScaleEffect.to(
|
||||
Vector2.all(0.5),
|
||||
EffectController(duration: 0.34, startDelay: delay, curve: Curves.easeIn),
|
||||
));
|
||||
c.add(SequenceEffect([
|
||||
MoveToEffect(
|
||||
winnerOrigin,
|
||||
EffectController(
|
||||
duration: 0.36, startDelay: delay, curve: Curves.easeInBack),
|
||||
),
|
||||
RemoveEffect(),
|
||||
]));
|
||||
i++;
|
||||
}
|
||||
|
||||
for (final tc in s.trick) {
|
||||
@@ -521,6 +532,45 @@ class HokmGame extends FlameGame {
|
||||
c.priority = 5;
|
||||
_moveTo(c, slot);
|
||||
}
|
||||
|
||||
// دستِ کامل (۴ کارت) ⇒ کارتِ برنده را طلایی هایلایت کن و یکبار «پاپ» بزن،
|
||||
// تا کاربر پیش از جمعشدنِ کارتها بفهمد چه کسی برد.
|
||||
if (s.trickDone && s.trick.length == 4) {
|
||||
final ws = _winnerSeat(s.trick, s.trump, s.leadSuit);
|
||||
for (final tc in s.trick) {
|
||||
final comp = _trick[tc.card];
|
||||
if (comp == null) continue;
|
||||
final win = tc.seat == ws;
|
||||
comp.highlight = win;
|
||||
if (win && !_highlightedTrick) comp.pulse();
|
||||
}
|
||||
_highlightedTrick = true;
|
||||
} else if (!s.trickDone) {
|
||||
_highlightedTrick = false;
|
||||
for (final c in _trick.values) {
|
||||
c.highlight = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// برندهی دستِ جاری را از روی کارتها + حکم + خالِ زمینه حساب میکند (سمتِ کلاینت).
|
||||
int _winnerSeat(List<TrickCard> trick, String? trump, String? lead) {
|
||||
var best = trick.first;
|
||||
for (final tc in trick.skip(1)) {
|
||||
if (_beats(tc.card, best.card, trump, lead)) best = tc;
|
||||
}
|
||||
return best.seat;
|
||||
}
|
||||
|
||||
bool _beats(String a, String b, String? trump, String? lead) {
|
||||
final aS = suitName(a), bS = suitName(b);
|
||||
final aT = aS == trump, bT = bS == trump;
|
||||
if (aT && !bT) return true;
|
||||
if (!aT && bT) return false;
|
||||
if (aT && bT) return rankValue(a) > rankValue(b);
|
||||
if (aS != lead) return false; // فقط کارتِ همخالِ زمینه میتواند ببرد
|
||||
if (bS != lead) return true;
|
||||
return rankValue(a) > rankValue(b);
|
||||
}
|
||||
|
||||
// مبدأِ نشستنِ هر جایگاه (برای پرتاب/جمعِ کارتها).
|
||||
|
||||
@@ -99,6 +99,21 @@ class Felt extends PositionComponent with HasGameReference {
|
||||
);
|
||||
}
|
||||
|
||||
// نورِ نرمِ مرکزی (اسپاتلایت) برای برجستهشدنِ کارتهای وسطِ میز.
|
||||
canvas.save();
|
||||
canvas.clipRRect(table);
|
||||
canvas.drawRect(
|
||||
rect,
|
||||
Paint()
|
||||
..shader = RadialGradient(
|
||||
center: Alignment.center,
|
||||
radius: 0.62,
|
||||
colors: const [Color(0x1FFFFFFF), Color(0x00FFFFFF)],
|
||||
stops: const [0.0, 1.0],
|
||||
).createShader(rect),
|
||||
);
|
||||
canvas.restore();
|
||||
|
||||
// خطِ طلاییِ نازکِ داخلی (قابِ دولایه).
|
||||
canvas.drawRRect(
|
||||
RRect.fromRectAndRadius(rect.deflate(w * 0.018), Radius.circular(h * 0.04)),
|
||||
|
||||
Reference in New Issue
Block a user