fix: some buggs

This commit is contained in:
2026-06-19 00:45:30 +03:30
parent f4d99043c4
commit a4cf2b5f39
7 changed files with 266 additions and 10 deletions
@@ -0,0 +1,91 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
const _gold = Color(0xFFE9B949);
const _goldDark = Color(0xFF8A5A00);
/// ستاره‌ی طلایی روی بازیکنِ نوبت‌دار (مطابقِ تصویرِ مرجع).
class StarBadge extends PositionComponent {
final double r;
StarBadge(this.r, Vector2 pos)
: super(position: pos, size: Vector2.all(r * 2), anchor: Anchor.center);
static final _fill = Paint()..color = _gold;
static final _stroke = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.5
..color = _goldDark;
@override
void render(Canvas canvas) {
final path = _starPath(Offset(r, r), r, r * 0.45, 5);
canvas.drawPath(path, _fill);
canvas.drawPath(path, _stroke);
}
}
/// تاجِ طلایی روی حاکم.
class CrownBadge extends PositionComponent {
final double w;
CrownBadge(this.w, Vector2 pos)
: super(
position: pos,
size: Vector2(w, w * 0.8),
anchor: Anchor.center,
);
static final _fill = Paint()..color = _gold;
static final _stroke = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 1.5
..color = _goldDark;
@override
void render(Canvas canvas) {
final h = size.y;
final p = Path()
..moveTo(0, h) // پایین‌چپ
..lineTo(0, h * 0.35) // نوکِ چپ
..lineTo(w * 0.25, h * 0.62) // فرورفتگی
..lineTo(w * 0.5, h * 0.18) // نوکِ میانی (بلندترین)
..lineTo(w * 0.75, h * 0.62)
..lineTo(w, h * 0.35) // نوکِ راست
..lineTo(w, h) // پایین‌راست
..close();
canvas.drawPath(p, _fill);
canvas.drawPath(p, _stroke);
// نگین‌های نوکِ تاج
final gem = Paint()..color = const Color(0xFFB71C1C);
canvas.drawCircle(Offset(w * 0.5, h * 0.14), w * 0.05, gem);
}
}
Path _starPath(Offset c, double outer, double inner, int points) {
final path = Path();
for (var i = 0; i < points * 2; i++) {
final rad = i.isEven ? outer : inner;
final ang = -math.pi / 2 + i * math.pi / points;
final pt = Offset(c.dx + rad * math.cos(ang), c.dy + rad * math.sin(ang));
i == 0 ? path.moveTo(pt.dx, pt.dy) : path.lineTo(pt.dx, pt.dy);
}
return path..close();
}
/// افکتِ ضربان (برای جلبِ توجه؛ مثلاً ستاره‌ی نوبت).
PulseEffect pulse() => PulseEffect();
class PulseEffect extends ScaleEffect {
PulseEffect()
: super.by(
Vector2.all(0.25),
EffectController(
duration: 0.5,
reverseDuration: 0.5,
infinite: true,
curve: Curves.easeInOut,
),
);
}
@@ -9,10 +9,12 @@ import 'package:flutter/material.dart';
import '../../../../../core/service/app_sounds.dart';
import '../../../domain/entities/game_entities.dart';
import '../../bloc/game_bloc.dart';
import 'badges.dart';
import 'card_codes.dart';
import 'card_component.dart';
import 'shuffle_animation.dart';
import 'table_pieces.dart';
import 'turn_timer.dart';
/// صحنه‌ی میز حکم با انیمیشن. شما همیشه پایین میز (rel=0) هستید.
/// کامپوننت‌های دست و trick ماندگارند و با افکت حرکت جابه‌جا می‌شوند.
@@ -46,6 +48,8 @@ class HokmGame extends FlameGame {
bool _silent = false; // هنگام خروج از میز، دیگر صدایی پخش نشود
bool _introActive = true; // تا پایانِ نمایشِ «جستجوی حریف»
bool _pendingShuffle = false; // بُرِ دستِ اول که تا پایانِ اینترو به تعویق افتاده
bool _shuffling = false; // در حالِ پخشِ انیمیشنِ بُر زدن (بازیِ کارت ممنوع)
String? _prevTrump; // برای تشخیصِ لحظه‌ی انتخابِ حکم
HokmGame(this.cubit);
@@ -69,6 +73,12 @@ class HokmGame extends FlameGame {
_shuffleNow();
}
}
// لحظه‌ی انتخابِ حکم: صدا + نمایشِ بزرگِ حکم در مرکز.
if (_prevTrump == null && s.trump != null && !_silent) {
AppSounds.selectHokm();
_spawnTrumpReveal(s.trump!);
}
_prevTrump = s.trump;
_prevHandSize = s.yourHand.length;
_s = s;
_relayout();
@@ -76,6 +86,7 @@ class HokmGame extends FlameGame {
if (cubit.state.state != null) {
_prevTrick = cubit.state.state!.trick.map((t) => t.card).toList();
_prevHandSize = cubit.state.state!.yourHand.length;
_prevTrump = cubit.state.state!.trump;
_s = cubit.state.state;
_relayout();
}
@@ -138,10 +149,41 @@ class HokmGame extends FlameGame {
}
/// اجرای هم‌زمانِ انیمیشن و صدای بُر زدن (صدا با پایانِ انیمیشن قطع می‌شود).
/// در طولِ انیمیشن، بازیِ کارت قفل است تا کارتی وسطِ بُر زدن انداخته نشود.
void _shuffleNow() {
if (_silent) return;
AppSounds.shuffle(cutoffMs: ShuffleAnimation.totalMs);
add(ShuffleAnimation(size));
_shuffling = true;
Future.delayed(const Duration(milliseconds: ShuffleAnimation.totalMs), () {
_shuffling = false;
if (_s != null && !_silent) _relayout(); // فعال‌سازیِ دوباره‌ی کارت‌ها
});
}
/// نمایشِ بزرگ و گذرای حکمِ انتخاب‌شده در مرکزِ میز (با پاپ).
void _spawnTrumpReveal(String trump) {
final (sym, col) = _trumpGlyph(trump);
final reveal = TextComponent(
text: 'حکم $sym',
anchor: Anchor.center,
position: size / 2 - Vector2(0, size.y * 0.10),
priority: 7000,
scale: Vector2.all(0.4),
textRenderer: TextPaint(
style: TextStyle(
fontFamily: 'Dana',
color: col,
fontSize: size.x * 0.11,
fontWeight: FontWeight.bold,
shadows: const [Shadow(color: Colors.black, blurRadius: 6)],
),
),
);
reveal.add(ScaleEffect.to(
Vector2.all(1.0), EffectController(duration: 0.35, curve: Curves.easeOutBack)));
reveal.add(RemoveEffect(delay: 1.3));
add(reveal);
}
static bool _isPrefix(List<String> a, List<String> b) {
@@ -191,6 +233,8 @@ class HokmGame extends FlameGame {
// آیا این کارت در نوبتِ فعلی قابل بازی است (با رعایت follow-suit)؟
bool _legal(String code) {
final s = _s!;
// هنگامِ نمایشِ اینترو یا انیمیشنِ بُر زدن، بازیِ کارت مجاز نیست.
if (_introActive || _shuffling) return false;
if (s.phase != 'playing' || s.trickDone || !s.isMyTurn) return false;
final lead = s.leadSuit;
if (lead == null || lead.isEmpty) return true;
@@ -441,15 +485,33 @@ class HokmGame extends FlameGame {
color: isTurn ? const Color(0xFFE9B949) : Colors.white70,
bold: isTurn,
);
// تاجِ حاکم (روی همه‌ی فازها نشان داده می‌شود تا حاکم مشخص باشد).
if (s.hakem == p.seat) {
final crown = CrownBadge(size.x * 0.06, pos - Vector2(0, size.y * 0.066))
..priority = 23;
_info.add(crown);
add(crown);
}
if (isTurn) {
final dot = CircleComponent(
radius: size.x * 0.012,
anchor: Anchor.center,
position: pos - Vector2(0, size.y * 0.03),
paint: Paint()..color = const Color(0xFFE9B949),
)..priority = 20;
_info.add(dot);
add(dot);
final tpos = pos - Vector2(0, size.y * 0.03);
final human = !p.bot && p.connected;
// حلقه‌ی شمارشِ نوبت برای بازیکنِ انسان.
if (human &&
s.turnTimeoutMs > 0 &&
(s.phase == 'playing' || s.phase == 'choose_trump')) {
final timer = TurnTimer(
radius: size.x * 0.03,
durationSeconds: s.turnTimeoutMs / 1000.0,
position: tpos,
)..priority = 20;
_info.add(timer);
add(timer);
}
// ستاره‌ی نوبت (مطابقِ تصویرِ مرجع) با ضربان.
final star = StarBadge(size.x * 0.015, tpos)..priority = 24;
star.add(pulse());
_info.add(star);
add(star);
}
}
}
@@ -0,0 +1,56 @@
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
/// حلقه‌ی شمارشِ معکوسِ نوبت: کمانی که در طولِ مهلتِ نوبت خالی می‌شود.
/// رنگ از طلایی به قرمز می‌رود و در ثانیه‌های پایانی هشدار می‌دهد.
class TurnTimer extends PositionComponent {
final double radius;
final double durationSeconds;
double _elapsed = 0;
TurnTimer({
required this.radius,
required this.durationSeconds,
required Vector2 position,
}) : super(
position: position,
size: Vector2.all(radius * 2),
anchor: Anchor.center,
);
static final _bg = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 3
..color = Colors.black54;
@override
void update(double dt) {
_elapsed += dt;
}
@override
void render(Canvas canvas) {
final center = Offset(radius, radius);
canvas.drawCircle(center, radius, _bg);
final frac =
durationSeconds <= 0 ? 0.0 : (1 - _elapsed / durationSeconds).clamp(0.0, 1.0);
if (frac <= 0) return;
final color = frac > 0.3 ? const Color(0xFFE9B949) : const Color(0xFFE53935);
final arc = Paint()
..style = PaintingStyle.stroke
..strokeWidth = 4
..strokeCap = StrokeCap.round
..color = color;
canvas.drawArc(
Rect.fromCircle(center: center, radius: radius),
-math.pi / 2, // شروع از بالا
2 * math.pi * frac,
false,
arc,
);
}
}