mjn
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
@@ -6,7 +7,7 @@ class ShotRecord {
|
||||
final double relativeX;
|
||||
final double relativeY;
|
||||
final bool isMake;
|
||||
final String playerName; // Bónus: Agora guardamos quem foi o jogador!
|
||||
final String playerName;
|
||||
|
||||
ShotRecord({
|
||||
required this.relativeX,
|
||||
@@ -32,7 +33,6 @@ class PlacarController {
|
||||
bool isLoading = true;
|
||||
bool isSaving = false;
|
||||
|
||||
// 👇 TRINCO DE SEGURANÇA: Evita contar vitórias duas vezes se clicares no Guardar repetidamente!
|
||||
bool gameWasAlreadyFinished = false;
|
||||
|
||||
int myScore = 0;
|
||||
@@ -67,7 +67,12 @@ class PlacarController {
|
||||
Timer? timer;
|
||||
bool isRunning = false;
|
||||
|
||||
// --- 🔄 CARREGAMENTO COMPLETO (DADOS REAIS + ESTATÍSTICAS SALVAS) ---
|
||||
// 👇 VARIÁVEIS DE CALIBRAÇÃO DO CAMPO (OS TEUS NÚMEROS!) 👇
|
||||
bool isCalibrating = false;
|
||||
double hoopBaseX = 0.088;
|
||||
double arcRadius = 0.459;
|
||||
double cornerY = 0.440;
|
||||
|
||||
Future<void> loadPlayers() async {
|
||||
final supabase = Supabase.instance.client;
|
||||
try {
|
||||
@@ -95,7 +100,6 @@ class PlacarController {
|
||||
opponentTimeoutsUsed = int.tryParse(gameResponse['opp_timeouts']?.toString() ?? '0') ?? 0;
|
||||
currentQuarter = int.tryParse(gameResponse['current_quarter']?.toString() ?? '1') ?? 1;
|
||||
|
||||
// 👇 Verifica se o jogo já tinha acabado noutra sessão
|
||||
gameWasAlreadyFinished = gameResponse['status'] == 'Terminado';
|
||||
|
||||
final teamsResponse = await supabase.from('teams').select('id, name').inFilter('name', [myTeam, opponentTeam]);
|
||||
@@ -269,29 +273,34 @@ class PlacarController {
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
// =========================================================================
|
||||
// 👇 A MÁGICA DOS PONTOS ACONTECE AQUI 👇
|
||||
// =========================================================================
|
||||
void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
if (pendingAction == null || pendingPlayer == null) return;
|
||||
|
||||
bool is3Pt = pendingAction!.contains("_3");
|
||||
bool is2Pt = pendingAction!.contains("_2");
|
||||
|
||||
// O ÁRBITRO MATEMÁTICO COM AS TUAS VARIÁVEIS CALIBRADAS
|
||||
if (is3Pt || is2Pt) {
|
||||
bool isValid = _validateShotZone(position, size, is3Pt);
|
||||
|
||||
// SE A JOGADA FOI NO SÍTIO ERRADO
|
||||
if (!isValid) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('🛑 Local incompatível com a pontuação.'), backgroundColor: Colors.red, duration: Duration(seconds: 2)));
|
||||
return;
|
||||
|
||||
return; // <-- ESTE RETURN BLOQUEIA A GRAVAÇÃO DO PONTO!
|
||||
}
|
||||
}
|
||||
|
||||
// SE A JOGADA FOI VÁLIDA:
|
||||
bool isMake = pendingAction!.startsWith("add_pts_");
|
||||
|
||||
// 👇 A MÁGICA DAS COORDENADAS RELATIVAS (0.0 a 1.0) 👇
|
||||
double relX = position.dx / size.width;
|
||||
double relY = position.dy / size.height;
|
||||
|
||||
// Extrai só o nome do jogador
|
||||
String name = pendingPlayer!.replaceAll("player_my_", "").replaceAll("player_opp_", "");
|
||||
|
||||
// Guarda na lista!
|
||||
matchShots.add(ShotRecord(
|
||||
relativeX: relX,
|
||||
relativeY: relY,
|
||||
@@ -307,18 +316,36 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
onUpdate();
|
||||
}
|
||||
|
||||
bool _validateShotZone(Offset pos, Size size, bool is3Pt) {
|
||||
double w = size.width; double h = size.height;
|
||||
Offset leftHoop = Offset(w * 0.12, h * 0.5);
|
||||
Offset rightHoop = Offset(w * 0.88, h * 0.5);
|
||||
double threePointRadius = w * 0.28;
|
||||
Offset activeHoop = pos.dx < w / 2 ? leftHoop : rightHoop;
|
||||
double distanceToHoop = (pos - activeHoop).distance;
|
||||
bool isCorner3 = (pos.dy < h * 0.15 || pos.dy > h * 0.85) && (pos.dx < w * 0.20 || pos.dx > w * 0.80);
|
||||
bool _validateShotZone(Offset position, Size size, bool is3Pt) {
|
||||
double relX = position.dx / size.width;
|
||||
double relY = position.dy / size.height;
|
||||
|
||||
if (is3Pt) return distanceToHoop >= threePointRadius || isCorner3;
|
||||
else return distanceToHoop < threePointRadius && !isCorner3;
|
||||
bool isLeftHalf = relX < 0.5;
|
||||
double hoopX = isLeftHalf ? hoopBaseX : (1.0 - hoopBaseX);
|
||||
double hoopY = 0.50;
|
||||
|
||||
double aspectRatio = size.width / size.height;
|
||||
double distFromCenterY = (relY - hoopY).abs();
|
||||
|
||||
bool isInside2Pts;
|
||||
|
||||
// Lógica das laterais (Cantos)
|
||||
if (distFromCenterY > cornerY) {
|
||||
double distToBaseline = isLeftHalf ? relX : (1.0 - relX);
|
||||
isInside2Pts = distToBaseline <= hoopBaseX;
|
||||
}
|
||||
// Lógica da Curva Frontal
|
||||
else {
|
||||
double dx = (relX - hoopX) * aspectRatio;
|
||||
double dy = (relY - hoopY);
|
||||
double distanceToHoop = math.sqrt((dx * dx) + (dy * dy));
|
||||
isInside2Pts = distanceToHoop < arcRadius;
|
||||
}
|
||||
|
||||
if (is3Pt) return !isInside2Pts;
|
||||
return isInside2Pts;
|
||||
}
|
||||
// 👆 ===================================================================== 👆
|
||||
|
||||
void cancelShotLocation() {
|
||||
isSelectingShotLocation = false; pendingAction = null; pendingPlayer = null; onUpdate();
|
||||
@@ -368,7 +395,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 💾 FUNÇÃO PARA GUARDAR DADOS NA BD ---
|
||||
Future<void> saveGameStats(BuildContext context) async {
|
||||
final supabase = Supabase.instance.client;
|
||||
isSaving = true;
|
||||
@@ -378,14 +404,12 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
bool isGameFinishedNow = currentQuarter >= 4 && duration.inSeconds == 0;
|
||||
String newStatus = isGameFinishedNow ? 'Terminado' : 'Pausado';
|
||||
|
||||
// 👇👇👇 0. CÉREBRO: CALCULAR OS LÍDERES E MVP DO JOGO 👇👇👇
|
||||
String topPtsName = '---'; int maxPts = -1;
|
||||
String topAstName = '---'; int maxAst = -1;
|
||||
String topRbsName = '---'; int maxRbs = -1;
|
||||
String topDefName = '---'; int maxDef = -1;
|
||||
String mvpName = '---'; int maxMvpScore = -1;
|
||||
|
||||
// Passa por todos os jogadores e calcula a matemática
|
||||
playerStats.forEach((playerName, stats) {
|
||||
int pts = stats['pts'] ?? 0;
|
||||
int ast = stats['ast'] ?? 0;
|
||||
@@ -393,19 +417,16 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
int stl = stats['stl'] ?? 0;
|
||||
int blk = stats['blk'] ?? 0;
|
||||
|
||||
int defScore = stl + blk; // Defesa: Roubos + Cortes
|
||||
int mvpScore = pts + ast + rbs + defScore; // Impacto Total (MVP)
|
||||
int defScore = stl + blk;
|
||||
int mvpScore = pts + ast + rbs + defScore;
|
||||
|
||||
// Compara com o máximo atual e substitui se for maior
|
||||
if (pts > maxPts && pts > 0) { maxPts = pts; topPtsName = '$playerName ($pts)'; }
|
||||
if (ast > maxAst && ast > 0) { maxAst = ast; topAstName = '$playerName ($ast)'; }
|
||||
if (rbs > maxRbs && rbs > 0) { maxRbs = rbs; topRbsName = '$playerName ($rbs)'; }
|
||||
if (defScore > maxDef && defScore > 0) { maxDef = defScore; topDefName = '$playerName ($defScore)'; }
|
||||
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = playerName; } // MVP não leva nº à frente, fica mais limpo
|
||||
if (mvpScore > maxMvpScore && mvpScore > 0) { maxMvpScore = mvpScore; mvpName = playerName; }
|
||||
});
|
||||
// 👆👆👆 FIM DO CÉREBRO 👆👆👆
|
||||
|
||||
// 1. Atualizar o Jogo na BD (Agora inclui os Reis da partida!)
|
||||
await supabase.from('games').update({
|
||||
'my_score': myScore,
|
||||
'opponent_score': opponentScore,
|
||||
@@ -414,8 +435,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
'opp_timeouts': opponentTimeoutsUsed,
|
||||
'current_quarter': currentQuarter,
|
||||
'status': newStatus,
|
||||
|
||||
// ENVIA A MATEMÁTICA PARA A TUA BASE DE DADOS
|
||||
'top_pts_name': topPtsName,
|
||||
'top_ast_name': topAstName,
|
||||
'top_rbs_name': topRbsName,
|
||||
@@ -423,7 +442,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
'mvp_name': mvpName,
|
||||
}).eq('id', gameId);
|
||||
|
||||
// 2. LÓGICA DE VITÓRIAS, DERROTAS E EMPATES
|
||||
if (isGameFinishedNow && !gameWasAlreadyFinished && myTeamDbId != null && oppTeamDbId != null) {
|
||||
|
||||
final teamsData = await supabase.from('teams').select('id, wins, losses, draws').inFilter('id', [myTeamDbId, oppTeamDbId]);
|
||||
@@ -458,7 +476,6 @@ void registerShotLocation(BuildContext context, Offset position, Size size) {
|
||||
gameWasAlreadyFinished = true;
|
||||
}
|
||||
|
||||
// 3. Atualizar as Estatísticas dos Jogadores
|
||||
List<Map<String, dynamic>> batchStats = [];
|
||||
playerStats.forEach((playerName, stats) {
|
||||
String? memberDbId = playerDbIds[playerName];
|
||||
|
||||
Reference in New Issue
Block a user