style: ui perfection

This commit is contained in:
2026-06-24 09:09:00 +03:30
parent 108a972761
commit 08937a66cb
4 changed files with 536 additions and 172 deletions
@@ -9,12 +9,10 @@ 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 ماندگارند و با افکت حرکت جابه‌جا می‌شوند.
@@ -38,7 +36,6 @@ class HokmGame extends FlameGame {
final Map<String, CardComponent> _trick = {};
final List<Component> _backs = [];
final List<Component> _info = [];
final Map<String, Sprite> _badge = {}; // نشان‌های رتبه (پیش‌بارگذاری)
// ابعادِ کارت‌های روی میز (بر اساس عرض صفحه محاسبه می‌شود).
double _cardW = 60;
@@ -72,17 +69,11 @@ class HokmGame extends FlameGame {
HokmGame(this.cubit, {this.skin = 'simple'});
@override
Color backgroundColor() => const Color(0xFF2C0A10);
Color backgroundColor() => const Color(0xFF0C3A1B);
@override
Future<void> onLoad() async {
add(Felt());
// پیش‌بارگذاریِ نشان‌های رتبه (اگر فایلی نبود، بی‌نشان رد می‌شود).
for (final t in const ['bronze', 'silver', 'gold', 'diamond', 'king']) {
try {
_badge[t] = await loadSprite('badge/$t.png');
} catch (_) {}
}
_sub = cubit.stream.listen((ui) {
if (ui.state == null) return;
final s = ui.state!;
@@ -534,8 +525,8 @@ class HokmGame extends FlameGame {
_addBacks(_rel(seat), count);
}
_addTricksWon(s);
_addScorePucks(s);
_addInfo(s);
// امتیاز/نام/نشان/تاج/شمارنده‌ی نوبت اکنون در اوورلیِ Flutter (TableHud) کشیده
// می‌شوند تا متن واضح‌تر و چیدمان نزدیک به تصویرِ مرجع باشد.
}
// شمارنده ۱: دست‌های برده‌ی این هَند (tricks_won) — دسته‌کارتِ پشت‌رو + عدد.
@@ -561,24 +552,6 @@ class HokmGame extends FlameGame {
}
}
// شمارنده ۲: امتیاز بازی (scores = هندهای برده) — دیسکِ هر تیم، ۰ تا ۷.
// یکی جلوی شما (پایین)، دیگری جلوی حریف (راست) — نه جلوی یارِ بالا.
void _addScorePucks(GameState s) {
final radius = size.x * 0.05;
for (var team = 0; team < 2; team++) {
final score = team < s.scores.length ? s.scores[team] : 0;
final mine = team == s.yourSeat % 2;
final pos =
mine
? Vector2(size.x * 0.50, size.y * 0.74)
: Vector2(size.x * 0.84, size.y * 0.44);
final puck = ScorePuck(score, radius: radius, position: pos)
..priority = 22;
_info.add(puck);
add(puck);
}
}
// پشت‌کارت‌های یک حریف به‌صورت بادبزنی.
void _addBacks(int rel, int count) {
if (count <= 0) return;
@@ -605,99 +578,6 @@ class HokmGame extends FlameGame {
}
}
void _addInfo(GameState s) {
if (s.trump != null) {
final (sym, col) = _trumpGlyph(s.trump!);
_addLabel(
'حکم: $sym',
Vector2(size.x * 0.04, size.y * 0.04),
size.x * 0.05,
color: col,
anchor: Anchor.topLeft,
bold: true,
);
}
final a = s.scores.isNotEmpty ? s.scores[0] : 0;
final b = s.scores.length > 1 ? s.scores[1] : 0;
_addLabel(
'$a - $b',
Vector2(size.x / 2, size.y * 0.04),
size.x * 0.05,
anchor: Anchor.topCenter,
);
for (final p in s.players) {
final pos = _seatLabelPos(_rel(p.seat));
final isTurn = s.turn == p.seat;
_addLabel(
'${p.name}${p.bot ? ' (ربات)' : ''}${p.connected ? '' : ''}',
pos,
size.x * 0.035,
color: isTurn ? const Color(0xFFE9B949) : Colors.white70,
bold: isTurn,
);
// نشانِ رتبه (راستِ نام) و سکه (چپِ نام). جایگاهِ پایین روی همان خطِ نام،
// بقیه کمی زیرِ نام تا خارج از صفحه نرود.
if (!p.bot) {
final isBottom = pos.y > size.y * 0.75;
final y = isBottom ? pos.y : pos.y + size.y * 0.032;
// سکه: چپِ نام (متن به سمتِ چپ کشیده می‌شود).
_addLabel(
'${p.coins}',
Vector2(pos.x - size.x * 0.06, y),
size.x * 0.034,
color: const Color(0xFFE9B949),
anchor: Anchor.centerRight,
);
// نشانِ رتبه: راستِ نام.
final spr = _badge[p.rankTier];
if (spr != null) {
final b = SpriteComponent(
sprite: spr,
anchor: Anchor.centerLeft,
size: Vector2.all(size.x * 0.06),
position: Vector2(pos.x + size.x * 0.07, y),
)..priority = 22;
_info.add(b);
add(b);
}
}
// تاجِ حاکم (روی همه‌ی فازها نشان داده می‌شود تا حاکم مشخص باشد).
// y را داخلِ صفحه نگه می‌داریم؛ برای بازیکنِ بالا، تاج زیرِ نام می‌نشیند.
if (s.hakem == p.seat) {
final above = pos.y > size.y * 0.5; // پایین/کنار ⇒ تاج بالای نام
final cy = above
? pos.y - size.y * 0.066
: pos.y + size.y * 0.05; // بازیکنِ بالا ⇒ تاج پایینِ نام
final crown = CrownBadge(size.x * 0.07, Vector2(pos.x, cy))
..priority = 25;
_info.add(crown);
add(crown);
}
if (isTurn) {
final tpos = pos - Vector2(0, size.y * 0.03);
final human = !p.bot && p.connected;
// حلقه‌ی شمارشِ نوبت برای بازیکنِ انسان. اگر سرور مهلت را نفرستد،
// پیش‌فرضِ ۳۰ ثانیه استفاده می‌شود تا شمارنده همیشه نمایش داده شود.
if (human && (s.phase == 'playing' || s.phase == 'choose_trump')) {
final dur = s.turnTimeoutMs > 0 ? s.turnTimeoutMs / 1000.0 : 30.0;
final timer = TurnTimer(
radius: size.x * 0.045,
durationSeconds: dur,
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);
}
}
}
// ===== کمکی‌های ساختِ عنصر (ثبت در فهرستِ بازساخته‌شونده) =====
void _addBack(Vector2 pos, Vector2 sz, {required int priority}) {
@@ -740,19 +620,6 @@ class HokmGame extends FlameGame {
add(t);
}
Vector2 _seatLabelPos(int rel) {
switch (rel) {
case 1:
return Vector2(size.x * 0.07, size.y * 0.30);
case 2:
return Vector2(size.x / 2, size.y * 0.07);
case 3:
return Vector2(size.x * 0.93, size.y * 0.30);
default:
return Vector2(size.x / 2, size.y * 0.975);
}
}
(String, Color) _trumpGlyph(String suit) {
switch (suit) {
case 'hearts':