Files
front-hokm/lib/feature/game/presentation/widgets/flame/table_pieces.dart
T
2026-07-08 00:17:46 +03:30

221 lines
7.4 KiB
Dart

import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../../../../../core/network/card_images.dart';
import '../../../../../core/theme/app_theme.dart';
// رنگ‌های مشترکِ میز (منبعِ واحد در AppColors).
const _gold = AppColors.gold;
const _goldDark = AppColors.goldDark;
/// میز: زمینِ لاجوردیِ نمدی در کلِ صفحه + یک «میزِ» مرکزی با قابِ طلاییِ دولایه.
/// اگر فرشی انتخاب شده باشد، سطحِ میز تصویرِ فرش است (بریده‌شده در شکلِ میز)؛
/// وگرنه سطحِ سرمه‌ایِ ترسیمی. کارت‌ها داخلِ همین ناحیه انداخته می‌شوند.
class Felt extends PositionComponent with HasGameReference {
/// شناسه‌ی فرشِ انتخابی؛ 'classic' یعنی سطحِ ترسیمی (بدون تصویر).
final String carpet;
Felt({this.carpet = 'classic'});
Sprite? _carpetSprite;
bool get _hasCarpet => carpet != 'classic';
@override
Future<void> onLoad() async {
if (_hasCarpet) {
_carpetSprite = await CardImages.carpet(carpet);
}
}
@override
void render(Canvas canvas) {
final w = game.size.x, h = game.size.y;
final center = Offset(w / 2, h / 2);
// ۱) زمینِ لاجوردیِ نمدی (کلِ صفحه) با وینیتِ شعاعی برای عمق. همیشه کشیده
// می‌شود تا زیرِ میز/فرش یکدست باشد و لبه‌های فرش با پس‌زمینه جور شود.
canvas.drawRect(
Rect.fromLTWH(0, 0, w, h),
Paint()
..shader = RadialGradient(
center: Alignment.center,
radius: 1.0,
colors: const [AppColors.lapisHi, AppColors.lapisNight],
stops: const [0.5, 1.0],
).createShader(Rect.fromLTWH(0, 0, w, h)),
);
// ۲) میزِ مرکزی با قابِ طلایی.
final rect = Rect.fromCenter(
center: center, width: w * 0.72, height: h * 0.46);
final radius = Radius.circular(h * 0.05);
final table = RRect.fromRectAndRadius(rect, radius);
// سایه‌ی نرمِ زیرِ میز (یک‌بار، نه هر-فریمی سنگین).
canvas.drawRRect(
table.shift(const Offset(0, 6)),
Paint()
..color = const Color(0x66000000)
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 12),
);
// قابِ طلاییِ بیرونی.
canvas.drawRRect(
RRect.fromRectAndRadius(rect.inflate(w * 0.012), radius),
Paint()
..shader = const LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [_gold, _goldDark],
).createShader(rect),
);
// سطحِ میز: تصویرِ فرش (بریده در شکلِ میز) یا سطحِ سرمه‌ایِ ترسیمی.
final sprite = _carpetSprite;
if (_hasCarpet && sprite != null) {
canvas.save();
canvas.clipRRect(table);
// فرش را به‌صورتِ cover داخلِ مستطیلِ میز می‌کشیم.
sprite.render(
canvas,
position: Vector2(rect.left, rect.top),
size: Vector2(rect.width, rect.height),
);
// تیره‌ترِ ملایم برای خوانایی کارت‌ها روی فرش.
canvas.drawRRect(table, Paint()..color = const Color(0x1A000000));
canvas.restore();
} else {
canvas.drawRRect(
table,
Paint()
..shader = RadialGradient(
center: Alignment.center,
radius: 0.9,
colors: const [AppColors.lapisLo, AppColors.lapisInk],
stops: const [0.4, 1.0],
).createShader(rect),
);
}
// نورِ نرمِ مرکزی (اسپاتلایت) برای برجسته‌شدنِ کارت‌های وسطِ میز.
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)),
Paint()
..style = PaintingStyle.stroke
..strokeWidth = w * 0.004
..color = _gold.withValues(alpha: 0.65),
);
}
}
/// دیسکِ فلزیِ شمارش (امتیاز تیم) با عددِ وسط؛ از ۰ تا ۷.
class ScorePuck extends PositionComponent {
final int count;
ScorePuck(this.count, {required double radius, super.position})
: super(size: Vector2.all(radius * 2), anchor: Anchor.center);
@override
void render(Canvas canvas) {
final r = size.x / 2;
final c = Offset(r, r);
canvas.drawCircle(c, r, Paint()..color = const Color(0xFF14110F));
canvas.drawCircle(
c,
r * 0.92,
Paint()
..style = PaintingStyle.stroke
..strokeWidth = r * 0.22
..color = _goldDark,
);
final tp = TextPainter(
text: TextSpan(
text: '$count',
style: TextStyle(
fontFamily: 'Dana',
color: _gold,
fontSize: r * 1.05,
fontWeight: FontWeight.bold,
),
),
textDirection: TextDirection.ltr,
)..layout();
tp.paint(canvas, c - Offset(tp.width / 2, tp.height / 2));
}
}
/// افکت رعد روی زمین هنگام «بریدن با حکم»؛ پس از مدت کوتاهی خودش حذف می‌شود.
class Lightning extends PositionComponent with HasGameReference {
static const _max = 0.55;
double _life = _max;
final _rng = math.Random();
final List<List<Offset>> _bolts = [];
@override
Future<void> onLoad() async {
size = game.size;
final center = Offset(size.x / 2, size.y / 2);
for (var i = 0; i < 3; i++) {
// هر رعد یک خط شکسته از یک نقطه‌ی تصادفیِ بالا به سمت مرکز.
final start =
Offset(_rng.nextDouble() * size.x, _rng.nextDouble() * size.y * 0.4);
final pts = <Offset>[start];
const segs = 6;
for (var s = 1; s <= segs; s++) {
final t = s / segs;
final base = Offset.lerp(start, center, t)!;
final jitter = (1 - t) * size.x * 0.06;
pts.add(base +
Offset((_rng.nextDouble() - 0.5) * jitter,
(_rng.nextDouble() - 0.5) * jitter));
}
_bolts.add(pts);
}
}
@override
void update(double dt) {
_life -= dt;
if (_life <= 0) removeFromParent();
}
@override
void render(Canvas canvas) {
final op = (_life / _max).clamp(0.0, 1.0);
final glow = Paint()
..color = const Color(0xFFFFE082).withValues(alpha: op * 0.5)
..style = PaintingStyle.stroke
..strokeWidth = size.x * 0.02
..maskFilter = const MaskFilter.blur(BlurStyle.normal, 6);
final core = Paint()
..color = const Color(0xFFFFFDE7).withValues(alpha: op)
..style = PaintingStyle.stroke
..strokeWidth = size.x * 0.006
..strokeCap = StrokeCap.round;
for (final bolt in _bolts) {
final path = Path()..moveTo(bolt.first.dx, bolt.first.dy);
for (final p in bolt.skip(1)) {
path.lineTo(p.dx, p.dy);
}
canvas.drawPath(path, glow);
canvas.drawPath(path, core);
}
}
}