#Requires AutoHotkey v2.0 #SingleInstance Force #WinActivateForce ; ===== CONFIGURACIÓN ACELERADA ===== SendMode "Input" SetWorkingDir A_ScriptDir ; ===== DISTRIBUCIÓN POR PERFIL ===== global symbols := Map("cherry", 32, "bell", 26, "lemon", 17, "star", 13, "diamond", 7, "seven", 4, "joker", 1) global symbolValues := Map("cherry", 2, "bell", 4, "lemon", 7, "star", 12, "diamond", 24, "seven", 48, "joker", 0, "three_jokers", 80) global spinning := false global imgPath := "imgs\" global soundPath := "sounds\" global spinDuration := 500 global spinInterval := 30 global img1, img2, img3 global lastWin := false global statsFile := "slot_stats.ini" global detailedStatsFile := "detailed_stats.ini" global configFile := "slot_config.ini" global savedSettingsFile := "AfinacionesGuardadas.ini" ; <<< NUEVA VARIABLE PARA AFINACIONES global autoSpin := false global autoSpinCount := 0 global maxAutoSpins := 1000 global lastUpdateCount := 0 global jackpotValue := 100 global jackpotCounter := 0 global testingMode := true ; ← ACTIVADO PARA PRUEBAS global DetailedStatsGui := "" global ReportEdit := "" global symbolWin := "" ; ===== ESTADÍSTICAS DE SISTEMAS DE AYUDA (SIMPLIFICADAS) ===== global pityActivations := 0, pityWins := 0, pityPesos := 0 global multiplierActivationsCount := 0, multiplierWins := 0, multiplierPesosExtra := 0 global jokerActivations := 0, jokerWins := 0, jokerPesos := 0 global jackpotActivations := 0, jackpotWins := 0, jackpotPesos := 0 ; ===== NUEVAS VARIABLES PARA ANÁLISIS AVANZADO ===== global symbolFrequency := Map() global combinationStats := Map() global volatilityStats := Map() global rtpBreakdown := Map() ; ===== SISTEMA DE MULTIPLICADORES ===== global multiplierProbability := 25 global multiplierValue := 2.0 global multiplierActivations := 0 global totalMultiplierPesos := 0 ; ===== SISTEMA DE PERFILES RTP ===== global RTPProfiles := Map("RTP35", 35.0, "RTP40", 40.0, "RTP45", 45.0, "RTP50", 50.0) global enableJokerSounds := false global currentProfile := "RTP40" global targetRTP := RTPProfiles[currentProfile] ; ===== SISTEMA PITY COUNTER ===== global pityCounter := 0 global pityThreshold := 25 global originalPityThreshold := 20 ; ===== SISTEMA BONUS MEJORADO ===== global bonusLights := 0 global maxBonusGiros := 20 global bonusLetters := ["B", "O", "N", "U", "S"] global letterOrder := [] global bonusThresholds := [] ; ===== INICIALIZAR ESTADISTICAS ===== global totalSpins := 0 global totalWins := 0 global totalPesos := 0 global winStats := Map() global pesosStats := Map() ; ===== ESTADISTICAS DETALLADAS ===== global detailedTotalSpins := 0 global detailedTotalWins := 0 global detailedTotalPesos := 0 global detailedJackpotsWon := 0 global detailedCompensations := 0 global detailedCompensationCount := 0 global detailedWinStats := Map() global detailedPesosStats := Map() global detailedJokerRespines := 0 global detailedJokerRespinWins := 0 global detailedJokerRespinPesos := 0 ; ===== NUEVAS ESTADISTICAS AVANZADAS ===== global currentLosingStreak := 0 global longestLosingStreak := 0 global symbolWinCounts := Map() global symbolRTPContributions := Map() global jokerRTPContribution := 0 ; ===== INICIALIZAR LISTA DE SÍMBOLOS ===== global symbolList := [] ; ===== VARIABLES RTP MEJORADO ===== global safetyNetThreshold := 40 global safetyNetActive := false global safetyNetBoost := false global jokerEffectivenessRTP35 := 35 global jokerEffectivenessRTP40 := 25 global jokerEffectivenessRTP45 := 45 global jokerEffectivenessRTP50 := 65 ; ===== SISTEMA DE RESCATE DE VOLATILIDAD MEJORADO ===== global rescueSpinsWithoutWin := 0 global lastRescueSpin := 0 global rescueMinSpinsBetween := 200 global rescueActivations := 0 global rescuePesos := 0 global rescueSymbols := ["lemon", "star", "bell"] ; Símbolos naturales para rescates global rescueActive := false ; Nueva variable para controlar estado ; ===== INTERFAZ GRAFICA ===== MyGui := Gui("+AlwaysOnTop +Resize +OwnDialogs -MaximizeBox", "Tragamonedas JACKPOT 5000 giros") MyGui.MarginX := 10 MyGui.MarginY := 10 MyGui.SetFont("s12", "Arial") MyGui.Opt("+OwnDialogs") ; Slot machine frame MyGui.Add("GroupBox", "w340 h150 Center", "Tragamonedas") MyGui.Add("Picture", "vSlot1 xp+10 yp+25 w100 h100 Border", imgPath . "question.png") MyGui.Add("Picture", "vSlot2 x+10 w100 h100 Border", imgPath . "question.png") MyGui.Add("Picture", "vSlot3 x+10 w100 h100 Border", imgPath . "question.png") ; Mostrar jackpot actual en una línea MyGui.Add("Text", "xm y+10 w180 Center", "Contador Jackpot:") JackpotCounterText := MyGui.Add("Text", "x+0 yp w160 Center", "0/5000") ; Logo Joker Giro Gratis - CENTRADO MyGui.Add("Picture", "vJokerLogo xm+80 y+5 w180 h35", imgPath . "joker_giro_gratis_apagado.png") ; Display de BONUS MyGui.Add("Picture", "vBonusSlot1 xm+125 y+10 w20 h30", imgPath . "bonus_empty.png") MyGui.Add("Picture", "vBonusSlot2 x+2 w20 h30", imgPath . "bonus_empty.png") MyGui.Add("Picture", "vBonusSlot3 x+2 w20 h30", imgPath . "bonus_empty.png") MyGui.Add("Picture", "vBonusSlot4 x+2 w20 h30", imgPath . "bonus_empty.png") MyGui.Add("Picture", "vBonusSlot5 x+2 w20 h30", imgPath . "bonus_empty.png") ; Botones de giro en una línea SpinBtn := MyGui.Add("Button", "xm y+20 w100 h25", "1 Moneda") SpinBtn.OnEvent("Click", StartSpin) SpinBtn.Opt("+Default") Spin1000Btn := MyGui.Add("Button", "x+10 w100 h25", "1000") Spin1000Btn.OnEvent("Click", Start1000Spins) Spin10000Btn := MyGui.Add("Button", "x+10 w100 h25", "10000") Spin10000Btn.OnEvent("Click", Start10000Spins) ; === NUEVO BOTÓN ULTRA-RÁPIDO === UltraTestBtn := MyGui.Add("Button", "xm y+10 w340 h30 cFFFFFF Background009900", "🔥 PRUEBA ULTRA-RÁPIDA 50000") UltraTestBtn.OnEvent("Click", StartUltraTest) ; === NUEVO BOTÓN EXPORTAR DATOS === ExportBtn := MyGui.Add("Button", "xm y+5 w165 h25 cFFFFFF Background3366FF", "📊 EXPORTAR CSV") ExportBtn.OnEvent("Click", ExportTestData) ; === NUEVO BOTÓN EXPORTAR TXT === ExportTxtBtn := MyGui.Add("Button", "x+10 yp w165 h25 cFFFFFF BackgroundFF6600", "📝 EXPORTAR TXT") ExportTxtBtn.OnEvent("Click", ExportTestResults) ; Boton de seleccion de perfil ProfileBtn := MyGui.Add("Button", "xm y+10 w340 h30", "Seleccionar Perfil RTP") ProfileBtn.OnEvent("Click", ShowProfileMenu) ; Indicador de perfil actual CurrentProfileText := MyGui.Add("Text", "xm y+5 w340 Center cFF9900", "Perfil Actual: RTP 40%") ; Estadisticas MyGui.Add("GroupBox", "xm y+10 w340 h190 Center", "Estadisticas") ; ← Reducir altura MyGui.Add("Text", "xp+10 yp+25 w120", "Monedas totales:") TotalSpinsText := MyGui.Add("Text", "vTotalSpinsText x+5 w50", "0") MyGui.Add("Text", "xp-125 y+5 w120", "Pesos ganados:") TotalPesosText := MyGui.Add("Text", "vTotalPesosText x+5 w50", "0") MyGui.Add("Text", "xp-125 y+5 w120", "Jugador gana:") PlayerWinPercent := MyGui.Add("Text", "vPlayerWinPercent x+5 w70", "0%") MyGui.Add("Text", "xp-125 y+5 w120", "Maquina gana:") MachineWinPercent := MyGui.Add("Text", "vMachineWinPercent x+5 w70", "100%") MyGui.Add("Text", "xp-125 y+5 w120", "Giros sin ganar:") PityCounterText := MyGui.Add("Text", "vPityCounterText x+5 w70", "0/20") ; === NUEVO INDICADOR DE RESCATE === MyGui.Add("Text", "xp-125 y+5 w120", "Rescates:") RescueCounterText := MyGui.Add("Text", "vRescueCounterText x+5 w70", "0") StatsBtn := MyGui.Add("Button", "xm+20 y+15 w140 h35", "Ver Detalles") StatsBtn.OnEvent("Click", ShowStats) ResetBtn := MyGui.Add("Button", "x+20 w140 h35", "Resetear Stats") ResetBtn.OnEvent("Click", ResetStats) ; Atajo de teclado para abrir el menú de estadísticas detalladas 9::ShowDetailedStats MyGui.OnEvent("Close", GuiClose) MyGui.Show("AutoSize Center") ; Inicializar después de crear la GUI UpdateSymbolList() LoadStats() LoadDetailedStats() InitializeAdvancedStats() RandomizeBonusOrder() RandomizeBonusThresholds() LoadConfig() LoadAfinacionesGuardadas() ; <--- ¡NUEVA LLAMADA PARA CARGAR AFINACIONES! ; Inicializar RTP de sesión UpdateJokerStatus() ; ; ===== FUNCIÓN PARA CARGAR AFINACIONES GUARDADAS (AfinacionesGuardadas.ini) ===== ; LoadAfinacionesGuardadas() { ; Declarar variables globales necesarias global savedSettingsFile, currentProfile, symbols, pityThreshold, jokerEffectivenessRTP35 global jokerEffectivenessRTP40, jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability global multiplierValue, safetyNetThreshold, targetRTP, RTPProfiles, CurrentProfileText global PityCounterText, MyGui, testingMode, symbolList ; 1. Comprobar si el archivo de afinaciones existe if (!FileExist(savedSettingsFile)) { if (testingMode) { OutputDebug("⚠️ Archivo de afinaciones '" . savedSettingsFile . "' no encontrado. Usando configuración por defecto/slot_config.ini.`n") } return } ; 2. Leer el perfil actualmente activo del archivo de afinaciones. ; Asumimos que el perfil activo está guardado en la sección "Afinacion" bajo la clave "CurrentProfile". savedProfile := IniRead(savedSettingsFile, "Afinacion", "CurrentProfile", currentProfile) ; 3. Cargar configuraciones guardadas si el perfil existe como sección en el INI. ; Los valores se leerán bajo una sección que coincide con el nombre del perfil (e.g., [RTP40]) sectionName := savedProfile ; Leer valores, usando "" como default para saber si fueron encontrados loadedPity := IniRead(savedSettingsFile, sectionName, "PityThreshold", "") loadedProb := IniRead(savedSettingsFile, sectionName, "MultiplierProbability", "") loadedValue := IniRead(savedSettingsFile, sectionName, "MultiplierValue", "") ; 4. Aplicar los nuevos valores SOLO si se encontraron if (loadedPity != "") { pityThreshold := Integer(loadedPity) } if (loadedProb != "") { multiplierProbability := Integer(loadedProb) } ; Asegurarse de que el valor del multiplicador se lea como Float if (loadedValue != "") { multiplierValue := Float(loadedValue) } ; 5. Si el perfil guardado es diferente al actual, sobrescribir y recargar la configuración base. ; Esto asegura que los defaults de símbolos y otros parámetros se actualicen correctamente if (savedProfile != currentProfile) { currentProfile := savedProfile LoadConfig() ; Se re-ejecuta LoadConfig() para aplicar los defaults del nuevo perfil (símbolos, etc.). } ; 6. Mensaje de depuración y actualización de interfaz if (testingMode) { OutputDebug("✅ Afinaciones Guardadas cargadas. Perfil: " . currentProfile . ". Pity: " . pityThreshold . ". Prob Multi: " . multiplierProbability . ". Valor Multi: " . multiplierValue . "`n") } ; Re-actualizar display después de posibles cambios en pityThreshold y targetRTP targetRTP := RTPProfiles.Has(currentProfile) ? RTPProfiles[currentProfile] : 0.0 CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)" PityCounterText.Value := pityCounter . "/" . pityThreshold UpdateSymbolList() ; Asegurar que la lista de símbolos se actualice. } ; ===== FUNCIONES ===== ; ===== FUNCIÓN EXPORTAR RESULTADOS TXT CORREGIDA ===== ExportTestResults(*) { global currentProfile, targetRTP, totalSpins, totalPesos, detailedTotalSpins, detailedTotalPesos global multiplierProbability, multiplierValue, testingMode if (totalSpins = 0) { MsgBox("No hay datos para exportar. Realiza algunos giros primero.", "Sin Datos", "48") return } ; ✅ CALCULAR RTP CON SEGURIDAD currentRTP := 0 if (totalSpins > 0) { currentRTP := (totalPesos / totalSpins) * 100 } deviation := currentRTP - targetRTP ; Crear nombre de archivo con timestamp timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss") filename := "Resultado_Precision_" . currentProfile . "_" . timestamp . ".txt" content := "=== RESULTADO PRECISIÓN " . currentProfile . " ===`n" content .= "Timestamp: " . timestamp . "`n" content .= "Perfil: " . currentProfile . " (Objetivo: " . targetRTP . "%)`n" content .= "Giros totales: " . totalSpins . "`n" content .= "RTP Final: " . Round(currentRTP, 3) . "%`n" content .= "Desviación: " . Round(deviation, 3) . "%`n" content .= "Multiplicador final: " . multiplierProbability . "% / " . multiplierValue . "x`n" content .= "Precisión: " . (Abs(deviation) <= 0.5 ? "✅ EXCELENTE" : (Abs(deviation) <= 1.0 ? "⚠️ ACEPTABLE" : "❌ INACEPTABLE")) . "`n" content .= "`n" content .= "=== DETALLES ADICIONALES ===`n" content .= "Giros detallados: " . detailedTotalSpins . "`n" content .= "Pesos detallados: " . detailedTotalPesos . "`n" content .= "Fecha de análisis: " . FormatTime(, "dd/MM/yyyy HH:mm:ss") . "`n" try { FileAppend(content, filename) if (testingMode) { OutputDebug("📊 RESULTADO EXPORTADO: " . filename . "`n") } MsgBox("✅ Archivo TXT exportado correctamente:`n" . filename, "Exportación Exitosa", "64") } catch Error as e { OutputDebug("❌ ERROR exportando: " . e.Message . "`n") MsgBox("❌ Error al exportar archivo TXT:`n" . e.Message, "Error de Exportación", "16") } } ; ===== SISTEMA DE RESCATE DE VOLATILIDAD MEJORADO ===== CheckVolatilityRescue() { global rescueSpinsWithoutWin, lastRescueSpin, rescueMinSpinsBetween, totalSpins global currentProfile, rescueActivations, rescuePesos, rescueSymbols global img1, img2, img3, symbolWin, symbolValues, totalPesos, detailedTotalPesos global totalWins, detailedTotalWins, winStats, detailedWinStats, pesosStats, detailedPesosStats global pityCounter, currentLosingStreak, MyGui, imgPath, rescueActive ; Evitar activación múltiple if (rescueActive) { return false } ; Umbral de activación: 80 giros sin victoria y mínimo 200 giros entre rescates if (rescueSpinsWithoutWin >= 45 && (totalSpins - lastRescueSpin) >= 120) { rescueActive := true ; 🎯 MODIFICACIÓN CRÍTICA IMPLEMENTADA: SOLO SÍMBOLOS DE BAJO VALOR lowValueRescueSymbols := ["cherry", "bell"] ; Solo símbolos de 2-4 pesos ; Seleccionar símbolo de bajo valor para el rescate rescueSymbol := lowValueRescueSymbols[Random(1, lowValueRescueSymbols.Length)] ; El premio será el valor natural del símbolo (2-4 pesos) rescuePrize := symbolValues[rescueSymbol] if (rescuePrize > 0) { ; Forzar combinación ganadora img1 := rescueSymbol img2 := rescueSymbol img3 := rescueSymbol symbolWin := rescueSymbol ; Actualizar display visual inmediatamente MyGui["Slot1"].Value := imgPath . rescueSymbol . ".png" MyGui["Slot2"].Value := imgPath . rescueSymbol . ".png" MyGui["Slot3"].Value := imgPath . rescueSymbol . ".png" ; Procesar la victoria HandleRescueWin(rescueSymbol, rescuePrize) ; Actualizar contadores rescueActivations++ rescuePesos += rescuePrize lastRescueSpin := totalSpins rescueSpinsWithoutWin := 0 pityCounter := 0 currentLosingStreak := 0 ; Actualizar display de estadísticas UpdateStatsDisplay() ; Mostrar notificación en modo testing if (testingMode) { OutputDebug("🎯 RESCATE ACTIVADO: " . rescueSymbol . " - " . rescuePrize . " pesos`n") } rescueActive := false return true } rescueActive := false } return false } GetRescuePrize() { global rescueSymbols, symbolValues ; Seleccionar símbolo natural para el rescate rescueSymbol := rescueSymbols[Random(1, rescueSymbols.Length)] ; Pagar el valor EXACTO de la figura return symbolValues[rescueSymbol] } HandleRescueWin(symbol, prize) { global totalWins, detailedTotalWins, winStats, detailedWinStats global totalPesos, detailedTotalPesos, pesosStats, detailedPesosStats global lastWin, pityCounter, currentLosingStreak, rescueSpinsWithoutWin lastWin := true totalWins++ detailedTotalWins++ pityCounter := 0 currentLosingStreak := 0 rescueSpinsWithoutWin := 0 ; Registrar estadísticas winStats[symbol] := winStats.Has(symbol) ? winStats[symbol] + 1 : 1 detailedWinStats[symbol] := detailedWinStats.Has(symbol) ? detailedWinStats[symbol] + 1 : 1 pesosStats[symbol] := pesosStats.Has(symbol) ? pesosStats[symbol] + prize : prize detailedPesosStats[symbol] := detailedPesosStats.Has(symbol) ? detailedPesosStats[symbol] + prize : prize totalPesos += prize detailedTotalPesos += prize ; Actualizar display UpdateStatsDisplay() UpdateJokerStatus() } ; ===== NUEVA FUNCIÓN: INICIALIZAR ESTADÍSTICAS AVANZADAS ===== InitializeAdvancedStats() { global symbolFrequency, combinationStats, volatilityStats, rtpBreakdown, symbols global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos, rescueActive ; Inicializar frecuencia de símbolos symbolFrequency := Map() for symbol in symbols { symbolFrequency[symbol] := 0 } ; Inicializar estadísticas de combinaciones combinationStats := Map() ; Inicializar métricas de volatilidad volatilityStats := Map() volatilityStats["CurrentLosingStreak"] := 0 volatilityStats["LongestLosingStreak"] := 0 volatilityStats["CurrentWinningStreak"] := 0 volatilityStats["LongestWinningStreak"] := 0 volatilityStats["SpinsBetweenWins"] := [] volatilityStats["WinDistribution"] := Map() ; Inicializar desglose de RTP rtpBreakdown := Map() rtpBreakdown["Base_Symbols"] := 0 rtpBreakdown["Pity_System"] := 0 rtpBreakdown["Joker_Respins"] := 0 rtpBreakdown["Multipliers"] := 0 rtpBreakdown["Jackpot"] := 0 rtpBreakdown["Rescue_System"] := 0 rtpBreakdown["Total"] := 0 ; Inicializar sistema de rescate rescueSpinsWithoutWin := 0 lastRescueSpin := 0 rescueActivations := 0 rescuePesos := 0 rescueActive := false } UpdateSymbolList() { global symbols, symbolList symbolList := [] for symbol, count in symbols { Loop count { symbolList.Push(symbol) } } } LoadStats() { global statsFile, totalSpins, totalWins, totalPesos, winStats, pesosStats, symbols, jackpotCounter, pityCounter global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos if FileExist(statsFile) { totalSpins := Integer(IniRead(statsFile, "Stats", "TotalSpins", "0")) totalWins := Integer(IniRead(statsFile, "Stats", "TotalWins", "0")) totalPesos := Integer(IniRead(statsFile, "Stats", "TotalPesos", "0")) jackpotCounter := Integer(IniRead(statsFile, "Stats", "JackpotCounter", "0")) pityCounter := Integer(IniRead(statsFile, "Stats", "PityCounter", "0")) rescueSpinsWithoutWin := Integer(IniRead(statsFile, "Rescue", "SpinsWithoutWin", "0")) lastRescueSpin := Integer(IniRead(statsFile, "Rescue", "LastRescueSpin", "0")) rescueActivations := Integer(IniRead(statsFile, "Rescue", "Activations", "0")) rescuePesos := Integer(IniRead(statsFile, "Rescue", "TotalPesos", "0")) for symbol in symbols { winStats[symbol] := Integer(IniRead(statsFile, "Symbols", symbol, "0")) pesosStats[symbol] := Integer(IniRead(statsFile, "Pesos", symbol, "0")) } winStats["three_jokers"] := Integer(IniRead(statsFile, "Symbols", "three_jokers", "0")) pesosStats["three_jokers"] := Integer(IniRead(statsFile, "Pesos", "three_jokers", "0")) } else { ; Inicializar valores si no existen totalSpins := 0 totalWins := 0 totalPesos := 0 jackpotCounter := 0 pityCounter := 0 rescueSpinsWithoutWin := 0 lastRescueSpin := 0 rescueActivations := 0 rescuePesos := 0 for symbol in symbols { winStats[symbol] := 0 pesosStats[symbol] := 0 } winStats["three_jokers"] := 0 pesosStats["three_jokers"] := 0 } ; Actualizar la interfaz UpdateStatsDisplay() } SaveStats() { global statsFile, totalSpins, totalWins, totalPesos, winStats, pesosStats, jackpotCounter, pityCounter global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos IniWrite(totalSpins, statsFile, "Stats", "TotalSpins") IniWrite(totalWins, statsFile, "Stats", "TotalWins") IniWrite(totalPesos, statsFile, "Stats", "TotalPesos") IniWrite(jackpotCounter, statsFile, "Stats", "JackpotCounter") IniWrite(pityCounter, statsFile, "Stats", "PityCounter") IniWrite(rescueSpinsWithoutWin, statsFile, "Rescue", "SpinsWithoutWin") IniWrite(lastRescueSpin, statsFile, "Rescue", "LastRescueSpin") IniWrite(rescueActivations, statsFile, "Rescue", "Activations") IniWrite(rescuePesos, statsFile, "Rescue", "TotalPesos") for symbol, count in winStats { IniWrite(count, statsFile, "Symbols", symbol) IniWrite(pesosStats[symbol], statsFile, "Pesos", symbol) } } LoadDetailedStats() { global detailedStatsFile, detailedTotalSpins, detailedTotalWins, detailedTotalPesos, detailedJackpotsWon, detailedWinStats, detailedPesosStats, symbols, detailedCompensations, detailedCompensationCount, multiplierActivations, totalMultiplierPesos, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos global currentLosingStreak, longestLosingStreak global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos if FileExist(detailedStatsFile) { detailedTotalSpins := Integer(IniRead(detailedStatsFile, "Stats", "TotalSpins", "0")) detailedTotalWins := Integer(IniRead(detailedStatsFile, "Stats", "TotalWins", "0")) detailedTotalPesos := Integer(IniRead(detailedStatsFile, "Stats", "TotalPesos", "0")) detailedJackpotsWon := Integer(IniRead(detailedStatsFile, "Stats", "JackpotsWon", "0")) detailedCompensations := Integer(IniRead(detailedStatsFile, "Stats", "TotalCompensations", "0")) detailedCompensationCount := Integer(IniRead(detailedStatsFile, "Stats", "CompensationCount", "0")) multiplierActivations := Integer(IniRead(detailedStatsFile, "Multipliers", "ActivationCount", "0")) totalMultiplierPesos := Integer(IniRead(detailedStatsFile, "Multipliers", "TotalPesos", "0")) detailedJokerRespines := Integer(IniRead(detailedStatsFile, "Joker", "Respines", "0")) detailedJokerRespinWins := Integer(IniRead(detailedStatsFile, "Joker", "RespinWins", "0")) detailedJokerRespinPesos := Integer(IniRead(detailedStatsFile, "Joker", "RespinPesos", "0")) rescueSpinsWithoutWin := Integer(IniRead(detailedStatsFile, "Rescue", "SpinsWithoutWin", "0")) lastRescueSpin := Integer(IniRead(detailedStatsFile, "Rescue", "LastRescueSpin", "0")) rescueActivations := Integer(IniRead(detailedStatsFile, "Rescue", "Activations", "0")) rescuePesos := Integer(IniRead(detailedStatsFile, "Rescue", "TotalPesos", "0")) for symbol in symbols { detailedWinStats[symbol] := Integer(IniRead(detailedStatsFile, "Symbols", symbol, "0")) detailedPesosStats[symbol] := Integer(IniRead(detailedStatsFile, "Pesos", symbol, "0")) } detailedWinStats["three_jokers"] := Integer(IniRead(detailedStatsFile, "Symbols", "three_jokers", "0")) detailedPesosStats["three_jokers"] := Integer(IniRead(detailedStatsFile, "Pesos", "three_jokers", "0")) ; Cargar nuevas estadísticas avanzadas currentLosingStreak := Integer(IniRead(detailedStatsFile, "AdvancedMetrics", "CurrentLosingStreak", "0")) longestLosingStreak := Integer(IniRead(detailedStatsFile, "AdvancedMetrics", "LongestLosingStreak", "0")) } else { ; Inicializar valores si no existen detailedTotalSpins := 0 detailedTotalWins := 0 detailedTotalPesos := 0 detailedJackpotsWon := 0 detailedCompensations := 0 detailedCompensationCount := 0 multiplierActivations := 0 totalMultiplierPesos := 0 currentLosingStreak := 0 longestLosingStreak := 0 rescueSpinsWithoutWin := 0 lastRescueSpin := 0 rescueActivations := 0 rescuePesos := 0 for symbol in symbols { detailedWinStats[symbol] := 0 detailedPesosStats[symbol] := 0 } detailedWinStats["three_jokers"] := 0 detailedPesosStats["three_jokers"] := 0 } } SaveDetailedStats() { global detailedStatsFile, detailedTotalSpins, detailedTotalWins, detailedTotalPesos, detailedJackpotsWon, detailedWinStats, detailedPesosStats, detailedCompensations, detailedCompensationCount, multiplierActivations, totalMultiplierPesos, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos global longestLosingStreak, symbolRTPContributions, jokerRTPContribution, symbolValues global rescueSpinsWithoutWin, lastRescueSpin, rescueActivations, rescuePesos RTP := 0 if (detailedTotalSpins > 0) { RTP := Round((detailedTotalPesos / detailedTotalSpins) * 100, 2) } ; Calcular RTP por símbolo symbolRTPContributions := Map() for symbol, pesos in detailedPesosStats { if (detailedTotalPesos > 0) { symbolRTPContributions[symbol] := Round((pesos / detailedTotalPesos) * RTP, 2) } else { symbolRTPContributions[symbol] := 0 } } ; Calcular contribución del Joker jokerRTPContribution := 0 if (detailedTotalPesos > 0) { jokerRTPContribution := Round((detailedJokerRespinPesos / detailedTotalPesos) * RTP, 2) } IniWrite(detailedTotalSpins, detailedStatsFile, "Stats", "TotalSpins") IniWrite(detailedTotalWins, detailedStatsFile, "Stats", "TotalWins") IniWrite(detailedTotalPesos, detailedStatsFile, "Stats", "TotalPesos") IniWrite(detailedJackpotsWon, detailedStatsFile, "Stats", "JackpotsWon") IniWrite(detailedCompensations, detailedStatsFile, "Stats", "TotalCompensations") IniWrite(detailedCompensationCount, detailedStatsFile, "Stats", "CompensationCount") IniWrite(RTP, detailedStatsFile, "Stats", "RTP") IniWrite(multiplierActivations, detailedStatsFile, "Multipliers", "ActivationCount") IniWrite(totalMultiplierPesos, detailedStatsFile, "Multipliers", "TotalPesos") IniWrite(detailedJokerRespines, detailedStatsFile, "Joker", "Respines") IniWrite(detailedJokerRespinWins, detailedStatsFile, "Joker", "RespinWins") IniWrite(detailedJokerRespinPesos, detailedStatsFile, "Joker", "RespinPesos") IniWrite(rescueSpinsWithoutWin, detailedStatsFile, "Rescue", "SpinsWithoutWin") IniWrite(lastRescueSpin, detailedStatsFile, "Rescue", "LastRescueSpin") IniWrite(rescueActivations, detailedStatsFile, "Rescue", "Activations") IniWrite(rescuePesos, detailedStatsFile, "Rescue", "TotalPesos") for symbol, count in detailedWinStats { IniWrite(count, detailedStatsFile, "Symbols", symbol) IniWrite(detailedPesosStats[symbol], detailedStatsFile, "Pesos", symbol) } ; Escribir nuevas métricas avanzadas IniWrite(longestLosingStreak, detailedStatsFile, "AdvancedMetrics", "LongestLosingStreak") IniWrite(currentLosingStreak, detailedStatsFile, "AdvancedMetrics", "CurrentLosingStreak") ; Escribir contribución RTP por símbolo for symbol, rtp in symbolRTPContributions { IniWrite(rtp, detailedStatsFile, "RTPBySymbol", symbol) } ; Escribir contribución RTP del Joker IniWrite(jokerRTPContribution, detailedStatsFile, "RTPBySymbol", "joker_respin_rtp") } LoadConfig() { ; DECLARAR EXPLÍCITAMENTE TODAS LAS VARIABLES GLOBALES global currentProfile, symbols, pityThreshold, jokerEffectivenessRTP35, jokerEffectivenessRTP40 global jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability, multiplierValue global safetyNetThreshold, targetRTP, RTPProfiles, CurrentProfileText, MyGui, PityCounterText global rescueSpinsWithoutWin, rescueMinSpinsBetween currentProfile := IniRead(configFile, "Config", "CurrentProfile", "RTP40") ; CONFIGURACIÓN MEJORADA if (currentProfile = "RTP35") { symbols := Map("cherry", 25, "bell", 26, "lemon", 17, "star", 12, "diamond", 8, "seven", 6, "joker", 1) pityThreshold := 25 jokerEffectivenessRTP35 := 20 multiplierProbability := 10 multiplierValue := 2.0 safetyNetThreshold := 45 rescueSpinsWithoutWin := 60 rescueMinSpinsBetween := 200 LoadAfinacionesGuardadas() }else if (currentProfile = "RTP40") { symbols := Map("cherry", 30, "bell", 25, "lemon", 19, "star", 12, "diamond", 7, "seven", 5, "joker", 2) pityThreshold := 25 jokerEffectivenessRTP40 := 20 multiplierProbability := 10 multiplierValue := 2.2 safetyNetThreshold := 50 rescueSpinsWithoutWin := 80 rescueMinSpinsBetween := 250 } else if (currentProfile = "RTP45") { symbols := Map("cherry", 25, "bell", 22, "lemon", 20, "star", 15, "diamond", 9, "seven", 7, "joker", 2) pityThreshold := 25 jokerEffectivenessRTP45 := 20 multiplierProbability := 6 multiplierValue := 2.5 safetyNetThreshold := 50 rescueSpinsWithoutWin := 80 rescueMinSpinsBetween := 250 } else if (currentProfile = "RTP50") { symbols := Map("cherry", 26, "bell", 22, "lemon", 22, "star", 14, "diamond", 7, "seven", 6, "joker", 3) pityThreshold := 25 jokerEffectivenessRTP50 := 65 multiplierProbability := 40 multiplierValue := 3.2 safetyNetThreshold := 25 } ; Leer valores de configuración si existen pityThreshold := Integer(IniRead(configFile, "Config", "PityThreshold", pityThreshold)) multiplierProbability := Integer(IniRead(configFile, "Multipliers", "ActivationProbability", multiplierProbability)) multiplierValue := Float(IniRead(configFile, "Multipliers", "MultiplierValue", multiplierValue)) targetRTP := RTPProfiles[currentProfile] CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)" if (currentProfile = "RTP35") CurrentProfileText.Opt("c007ACC") else if (currentProfile = "RTP40") CurrentProfileText.Opt("cFF9900") else if (currentProfile = "RTP45") CurrentProfileText.Opt("cFF3366") else if (currentProfile = "RTP50") CurrentProfileText.Opt("c00FF00") MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros" PityCounterText.Value := pityCounter . "/" . pityThreshold UpdateSymbolList() ; ← ACTUALIZAR LISTA DE SÍMBOLOS } SaveConfig() { global configFile, currentProfile, pityThreshold, multiplierProbability, multiplierValue IniWrite(currentProfile, configFile, "Config", "CurrentProfile") IniWrite(pityThreshold, configFile, "Config", "PityThreshold") IniWrite(multiplierProbability, configFile, "Multipliers", "ActivationProbability") IniWrite(multiplierValue, configFile, "Multipliers", "MultiplierValue") } RandomizeBonusOrder() { global bonusLetters, letterOrder letterOrder := ["B", "O", "N", "U", "S"] } RandomizeBonusThresholds() { global bonusThresholds bonusThresholds := [] bonusThresholds.Push(Random(3, 5)) bonusThresholds.Push(Random(7, 9)) bonusThresholds.Push(Random(11, 13)) bonusThresholds.Push(Random(15, 17)) bonusThresholds.Push(20) } UpdateBonusLights() { global pityCounter, bonusThresholds, letterOrder, imgPath, MyGui lettersToShow := 0 Loop 5 { if (pityCounter >= bonusThresholds[A_Index]) { lettersToShow := A_Index } } ; Apagar todas las luces primero Loop 5 { MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_empty.png" } ; Encender las luces en ORDEN CONSECUTIVO Loop lettersToShow { letter := letterOrder[A_Index] if (letter = "B") MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_B_red.png" else if (letter = "O") MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_O_blue.png" else if (letter = "N") MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_N_green.png" else if (letter = "U") MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_U_yellow.png" else if (letter = "S") MyGui["BonusSlot" . A_Index].Value := imgPath . "bonus_S_purple.png" } } ShowProfileMenu(*) { try { ProfileMenu := Menu() ProfileMenu.Add("RTP 35%", SetRTPProfile.Bind("RTP35")) ProfileMenu.Add("RTP 40%", SetRTPProfile.Bind("RTP40")) ProfileMenu.Add("RTP 45%", SetRTPProfile.Bind("RTP45")) ProfileMenu.Add("RTP 50%", SetRTPProfile.Bind("RTP50")) ProfileMenu.Show() } } SetRTPProfile(profileName, *) { ; DECLARAR EXPLÍCITAMENTE TODAS LAS VARIABLES GLOBALES global currentProfile, targetRTP, RTPProfiles, symbols, MyGui global pityThreshold, jokerEffectivenessRTP35, jokerEffectivenessRTP40 global jokerEffectivenessRTP45, jokerEffectivenessRTP50, multiplierProbability, multiplierValue global safetyNetThreshold global rescueSpinsWithoutWin, rescueMinSpinsBetween currentProfile := profileName targetRTP := RTPProfiles[profileName] ; === CONFIGURACIÓN OPTIMIZADA GAMIFICACIÓN === if (currentProfile = "RTP35") { symbols := Map("cherry", 25, "bell", 26, "lemon", 17, "star", 12, "diamond", 8, "seven", 6, "joker", 1) pityThreshold := 25 jokerEffectivenessRTP35 := 20 multiplierProbability := 10 multiplierValue := 2.0 safetyNetThreshold := 45 rescueSpinsWithoutWin := 60 rescueMinSpinsBetween := 200 LoadAfinacionesGuardadas() } else if (currentProfile = "RTP40") { symbols := Map("cherry", 30, "bell", 25, "lemon", 19, "star", 12, "diamond", 7, "seven", 5, "joker", 2) pityThreshold := 25 jokerEffectivenessRTP40 := 20 multiplierProbability := 10 multiplierValue := 2.2 safetyNetThreshold := 50 rescueSpinsWithoutWin := 80 rescueMinSpinsBetween := 250 } else if (currentProfile = "RTP45") { symbols := Map("cherry", 25, "bell", 22, "lemon", 20, "star", 15, "diamond", 9, "seven", 7, "joker", 2) pityThreshold := 25 jokerEffectivenessRTP45 := 20 multiplierProbability := 6 multiplierValue := 2.5 safetyNetThreshold := 50 rescueSpinsWithoutWin := 80 rescueMinSpinsBetween := 250 } else if (currentProfile = "RTP50") { symbols := Map("cherry", 26, "bell", 22, "lemon", 22, "star", 14, "diamond", 7, "seven", 6, "joker", 3) pityThreshold := 25 jokerEffectivenessRTP50 := 65 multiplierProbability := 40 multiplierValue := 3.2 safetyNetThreshold := 25 } UpdateSymbolList() UpdateProfileDisplay() ResetStats(0) ; GUARDAR CONFIGURACIÓN SaveConfig() MyGui.Opt("+OwnDialogs") MsgBox("RTP configurado al " . targetRTP . "%", "Perfil Cambiado", "64") MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros" UpdateJokerStatus() } ResetProfileSettings() { global currentProfile, pityThreshold if (currentProfile = "RTP35") { pityThreshold := 25 } else if (currentProfile = "RTP40") { pityThreshold := 25 } else if (currentProfile = "RTP45") { pityThreshold := 25 } else if (currentProfile = "RTP50") { pityThreshold := 25 } SaveConfig() } UpdateProfileDisplay() { global currentProfile, targetRTP, CurrentProfileText, MyGui CurrentProfileText.Value := "Perfil Actual: " . currentProfile . " (" . targetRTP . "%)" if (currentProfile = "RTP35") CurrentProfileText.Opt("c007ACC") else if (currentProfile = "RTP40") CurrentProfileText.Opt("cFF9900") else if (currentProfile = "RTP45") CurrentProfileText.Opt("cFF3366") else if (currentProfile = "RTP50") CurrentProfileText.Opt("c00FF00") MyGui.Title := "Tragamonedas AHK - JACKPOT a 5000 giros" } CalculatePlayerWinPercent() { global totalSpins, totalPesos if (totalSpins = 0) return "0%" playerWinPercent := (totalPesos / totalSpins) * 100 return Round(playerWinPercent, 1) . "%" } CalculateMachineWinPercent() { global totalSpins, totalPesos if (totalSpins = 0) return "100%" machineWinPercent := ((totalSpins - totalPesos) / totalSpins) * 100 return Round(machineWinPercent, 1) . "%" } UpdateWinPercentages() { PlayerWinPercent.Value := CalculatePlayerWinPercent() MachineWinPercent.Value := CalculateMachineWinPercent() } UpdateJokerStatus() { global currentRTP, totalSpins, totalPesos, MyGui, imgPath if (totalSpins > 0) { currentRTP := (totalPesos / totalSpins) * 100 } else { currentRTP := 0 } if (currentRTP < targetRTP) { MyGui["JokerLogo"].Value := imgPath . "joker_giro_gratis_prendido.png" } else { MyGui["JokerLogo"].Value := imgPath . "joker_giro_gratis_apagado.png" } } Start1000Spins(*) { global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins if spinning return autoSpin := true testingMode := true autoSpinCount := 0 maxAutoSpins := 1000 SpinBtn.Enabled := false Spin1000Btn.Enabled := false Spin10000Btn.Enabled := false SetTimer(AutoSpinLoop, 80) } Start10000Spins(*) { global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins if spinning return autoSpin := true testingMode := true autoSpinCount := 0 maxAutoSpins := 50000 SpinBtn.Enabled := false Spin1000Btn.Enabled := false Spin10000Btn.Enabled := false SetTimer(AutoSpinLoop, 80) } StartUltraTest(*) { global spinning, autoSpin, testingMode, autoSpinCount, maxAutoSpins global symbols, symbolList ; ← Asegurar que estas globales estén declaradas if spinning return ; === AGREGAR ESTA LÍNEA CRÍTICA === UpdateSymbolList() ; ← ¡ACTUALIZAR LA LISTA DE SÍMBOLOS! MyGui.Opt("+OwnDialogs") MsgBox("🚀 INICIANDO PRUEBA ULTRA-RÁPIDA`n`n• 50,000 giros en 5-9 minutos`n• RTP teórico puro (sin ajustes dinámicos)`n• Sin animaciones ni sonidos`n• Resultados válidos al 100%", "Modo Pruebas Rápido", "64") autoSpin := true testingMode := true autoSpinCount := 0 maxAutoSpins := 50000 SpinBtn.Enabled := false Spin1000Btn.Enabled := false Spin10000Btn.Enabled := false UltraTestBtn.Enabled := false UltraTestBtn.Text := "🔥 PROCESANDO... " . autoSpinCount . "/50000" SetTimer(UltraSpinLoop, 1) } UltraSpinLoop() { ; DECLARAR TODAS LAS VARIABLES GLOBALES UNA POR UNA global autoSpin, autoSpinCount, maxAutoSpins, spinning, totalSpins, detailedTotalSpins global img1, img2, img3, symbolWin, symbolList, targetRTP, totalPesos global detailedJokerRespines, detailedJokerRespinWins, UltraTestBtn global symbolFrequency, combinationStats, jokerActivations global jackpotCounter, jackpotValue, detailedJackpotsWon, pityCounter global detailedTotalPesos, testingMode, UpdateBonusLights, symbolValues global detailedJokerRespinPesos, jokerWins, jokerPesos global jackpotActivations, jackpotWins, jackpotPesos global currentLosingStreak, longestLosingStreak global currentProfile, safetyNetActive, safetyNetThreshold global rescueSpinsWithoutWin, lastRescueSpin, rescueMinSpinsBetween, rescueActivations, rescuePesos if spinning || autoSpinCount >= maxAutoSpins { if (autoSpinCount >= maxAutoSpins) { SetTimer(UltraSpinLoop, 0) autoSpin := false testingMode := false lastUpdateCount := 0 SaveStats() SaveDetailedStats() ; ✅ NUEVO: EXPORTAR RESULTADOS AUTOMÁTICAMENTE - AGREGADO if (testingMode) { ExportTestResults() } SpinBtn.Enabled := true Spin1000Btn.Enabled := true Spin10000Btn.Enabled := true UltraTestBtn.Enabled := true UltraTestBtn.Text := "🔥 PRUEBA ULTRA-RÁPIDA 50000" currentRTP := (totalPesos / totalSpins) * 100 MyGui.Opt("+OwnDialogs") MsgBox("✅ PRUEBA COMPLETADA`n`nGiros: " . maxAutoSpins . "`nRTP Final: " . Round(currentRTP, 2) . "%`nDesviación: " . Round(currentRTP - targetRTP, 2) . "%`n`n📊 Datos guardados en archivos INI", "Resultados Ultra-Rápidos", "64") } return } Critical true totalSpins++ detailedTotalSpins++ autoSpinCount++ ; ACTUALIZACIÓN GARANTIZADA CADA 100 GIROS if (Mod(autoSpinCount, 100) == 0) { UltraTestBtn.Text := "🔥 PROCESANDO... " . autoSpinCount . "/50000" UpdateStatsDisplay() } randIndex := Random(1, symbolList.Length) img1 := symbolList[randIndex] randIndex := Random(1, symbolList.Length) img2 := symbolList[randIndex] randIndex := Random(1, symbolList.Length) img3 := symbolList[randIndex] symbolFrequency[img1] := symbolFrequency.Get(img1, 0) + 1 symbolFrequency[img2] := symbolFrequency.Get(img2, 0) + 1 symbolFrequency[img3] := symbolFrequency.Get(img3, 0) + 1 symbolWin := CheckWinCombination(img1, img2, img3) ; ===== VERIFICAR RESCATE DE VOLATILIDAD MEJORADO ===== if (!symbolWin || symbolWin = false) { rescueSpinsWithoutWin++ if (CheckVolatilityRescue()) { ; El rescate ya manejó la victoria, continuar al siguiente giro Critical false return } } else { rescueSpinsWithoutWin := 0 } if (symbolWin is String && symbolWin != "") { HandleTripleWinUltra() } else if (img1 = "joker" || img2 = "joker" || img3 = "joker") { currentRTP := 0 if (totalSpins > 0) { currentRTP := (totalPesos / totalSpins) * 100 } if (currentRTP < targetRTP) { jokerActivations += 1 detailedJokerRespines++ if (img1 != "joker") { randIndex := Random(1, symbolList.Length) img1 := symbolList[randIndex] } if (img2 != "joker") { randIndex := Random(1, symbolList.Length) img2 := symbolList[randIndex] } if (img3 != "joker") { randIndex := Random(1, symbolList.Length) img3 := symbolList[randIndex] } symbolWin := CheckWinCombination(img1, img2, img3) if (symbolWin is String && symbolWin != "") { ; ✅ BLOQUE CORREGIDO - CONTADORES DE JOKER FIJOS pesosGanados := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 2 HandleTripleWinUltra() detailedJokerRespinWins += 1 ; ← SOLO 1 VICTORIA POR RESPIN detailedJokerRespinPesos += pesosGanados jokerWins += 1 jokerPesos += pesosGanados } else { HandleLossUltra() } } else { HandleLossUltra() } } else { HandleLossUltra() } ; VERIFICACIÓN JACKPOT if (jackpotCounter >= 5000) { totalPesos += jackpotValue detailedTotalPesos += jackpotValue detailedJackpotsWon++ jackpotCounter := 0 pityCounter := 0 UpdateBonusLights() ; ✅ CONTADORES DE SISTEMAS DE AYUDA jackpotActivations += 1 jackpotWins += 1 jackpotPesos += jackpotValue } Critical false } HandleTripleWinUltra() { global lastWin, totalWins, detailedTotalWins, winStats, detailedWinStats, pityCounter global totalPesos, detailedTotalPesos, pesosStats, detailedPesosStats, symbolWin, symbolValues global totalMultiplierPesos, multiplierActivations, multiplierValue, multiplierProbability global detailedJokerRespinPesos, combinationStats, multiplierActivationsCount, multiplierWins, multiplierPesosExtra global jokerWins, jokerPesos global currentLosingStreak, safetyNetActive, safetyNetBoost global rescueSpinsWithoutWin lastWin := true currentLosingStreak := 0 rescueSpinsWithoutWin := 0 totalWins++ detailedTotalWins++ pityCounter := 0 pesosGanados := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 2 combinationStats[symbolWin] := combinationStats.Get(symbolWin, 0) + 1 if (symbolWin = "three_jokers") { winStats["three_jokers"] := winStats.Has("three_jokers") ? winStats["three_jokers"] + 1 : 1 detailedWinStats["three_jokers"] := detailedWinStats.Has("three_jokers") ? detailedWinStats["three_jokers"] + 1 : 1 } else { winStats[symbolWin] := winStats.Has(symbolWin) ? winStats[symbolWin] + 1 : 1 detailedWinStats[symbolWin] := detailedWinStats.Has(symbolWin) ? detailedWinStats[symbolWin] + 1 : 1 if (Random(1, 100) <= (100 / multiplierProbability)) { multiplierActivations++ multiplierActivationsCount++ baseValue := symbolValues.Has(symbolWin) ? symbolValues[symbolWin] : 0 multipliedValue := Round(baseValue * multiplierValue) totalMultiplierPesos += (multipliedValue - baseValue) multiplierPesosExtra += (multipliedValue - baseValue) multiplierWins += 1 pesosGanados := multipliedValue } } totalPesos += pesosGanados detailedTotalPesos += pesosGanados if (symbolWin = "three_jokers") { pesosStats["three_jokers"] := pesosStats.Has("three_jokers") ? pesosStats["three_jokers"] + pesosGanados : pesosGanados detailedPesosStats["three_jokers"] := detailedPesosStats.Has("three_jokers") ? detailedPesosStats["three_jokers"] + pesosGanados : pesosGanados } else { pesosStats[symbolWin] := pesosStats.Has(symbolWin) ? pesosStats[symbolWin] + pesosGanados : pesosGanados detailedPesosStats[symbolWin] := detailedPesosStats.Has(symbolWin) ? detailedPesosStats[symbolWin] + pesosGanados : pesosGanados } } HandleLossUltra() { global lastWin, pityCounter, jackpotCounter, totalPesos, detailedTotalPesos, detailedCompensations, detailedCompensationCount, testingMode, currentProfile, totalSpins, pityThreshold, pityActivations, pityWins, pityPesos global currentLosingStreak, longestLosingStreak global safetyNetActive, safetyNetThreshold global rescueSpinsWithoutWin lastWin := false currentLosingStreak++ if (currentLosingStreak > longestLosingStreak) { longestLosingStreak := currentLosingStreak } pityCounter++ jackpotCounter++ rescueSpinsWithoutWin++ ; ← CONTAR GIROS SIN GANAR ; ✅ VERIFICAR SAFETY NET PARA RTP35 if (currentProfile = "RTP35" && currentLosingStreak >= safetyNetThreshold && !safetyNetActive) { safetyNetActive := false ; En modo ultra, simplemente forzar una victoria symbolWin := "bell" HandleTripleWinUltra() safetyNetActive := false return } if (pityCounter >= pityThreshold) { pityCounter := 0 compensation := 4 ; ← COMPENSACIÓN FIJA DE 4 PESOS if (compensation > 0) { detailedCompensations += compensation detailedCompensationCount++ totalPesos += compensation detailedTotalPesos += compensation pityActivations += 1 pityWins += 1 pityPesos += compensation } } } ExportTestData(*) { global currentProfile, targetRTP, detailedTotalSpins, detailedTotalWins, detailedTotalPesos global symbolFrequency, combinationStats, symbols, symbolValues global detailedCompensations, detailedJokerRespines, detailedJokerRespinWins, detailedJokerRespinPesos global multiplierActivations, totalMultiplierPesos, detailedJackpotsWon, jackpotValue global longestLosingStreak, currentLosingStreak global pityActivations, pityWins, pityPesos, multiplierActivationsCount, multiplierWins, multiplierPesosExtra global jokerActivations, jokerWins, jokerPesos, jackpotActivations, jackpotWins, jackpotPesos global detailedPesosStats, detailedWinStats global rescueSpinsWithoutWin, rescueActivations, rescuePesos try { timestamp := FormatTime(, "yyyy-MM-dd_HH-mm-ss") filename := "RTP_Analysis_" . currentProfile . "_" . timestamp . ".csv" ; ✅ CALCULAR RTP CON SEGURIDAD currentRTP := 0 if (detailedTotalSpins > 0) { currentRTP := (detailedTotalPesos / detailedTotalSpins) * 100 } csvContent := "" csvContent .= "ANÁLISIS RTP - PERFIL " . currentProfile . "`n" csvContent .= "Timestamp,RTP_Teórico,RTP_Calculado,Total_Giros,Total_Victorias,Total_Pesos`n" csvContent .= timestamp . "," . targetRTP . "," . Round(currentRTP, 2) . "," . detailedTotalSpins . "," . detailedTotalWins . "," . detailedTotalPesos . "`n`n" ; ✅ SECCIÓN FRECUENCIA CORREGIDA - EVITA DIVISIÓN POR CERO csvContent .= "FRECUENCIA DE SÍMBOLOS`n" csvContent .= "Símbolo,Veces_Salió,Probabilidad_Real,Probabilidad_Teórica`n" totalSymbolAppearances := 0 for symbol, count in symbolFrequency { totalSymbolAppearances += count } if (totalSymbolAppearances = 0) { ; Si no hay datos, mostrar valores en cero for symbol, count in symbolFrequency { theoreticalProbability := symbols.Has(symbol) ? (symbols[symbol] / 100) * 100 : 0 csvContent .= symbol . "," . count . ",0%," . theoreticalProbability . "%`n" } } else { ; Calcular probabilidades normalmente for symbol, count in symbolFrequency { realProbability := (count / totalSymbolAppearances) * 100 theoreticalProbability := symbols.Has(symbol) ? (symbols[symbol] / 100) * 100 : 0 csvContent .= symbol . "," . count . "," . Round(realProbability, 2) . "%," . theoreticalProbability . "%`n" } } csvContent .= "`n" ; ✅ SECCIÓN COMBINACIONES GANADORAS CON SEGURIDAD csvContent .= "COMBINACIONES GANADORAS`n" csvContent .= "Combinación,Veces,Probabilidad,Pesos_Generados,Valor_Promedio`n" totalWins := detailedTotalWins if (totalWins = 0) { ; Si no hay victorias, mostrar combinaciones en cero for combination, count in combinationStats { pesosGenerados := detailedPesosStats.Has(combination) ? detailedPesosStats[combination] : 0 csvContent .= combination . "," . count . ",0%," . pesosGenerados . ",0`n" } } else { ; Calcular probabilidades normalmente for combination, count in combinationStats { probability := (count / totalWins) * 100 pesosGenerados := detailedPesosStats.Has(combination) ? detailedPesosStats[combination] : 0 valorPromedio := (count > 0) ? (pesosGenerados / count) : 0 csvContent .= combination . "," . count . "," . Round(probability, 2) . "%," . pesosGenerados . "," . Round(valorPromedio, 2) . "`n" } } csvContent .= "`n" ; ✅ SISTEMAS DE AYUDA CON VERIFICACIONES csvContent .= "SISTEMAS DE AYUDA`n" csvContent .= "Sistema,Activaciones,Victorias,Tasa_Éxito,Pesos_Generados`n" ; Pity Counter pitySuccessRate := (pityActivations > 0) ? ((pityWins / pityActivations) * 100) : 0 csvContent .= "Pity_Counter," . pityActivations . "," . pityWins . "," . Round(pitySuccessRate, 1) . "%," . pityPesos . "`n" ; Multiplicador multiplierSuccessRate := (multiplierActivationsCount > 0) ? ((multiplierWins / multiplierActivationsCount) * 100) : 0 csvContent .= "Multiplicador," . multiplierActivationsCount . "," . multiplierWins . "," . Round(multiplierSuccessRate, 1) . "%," . multiplierPesosExtra . "`n" ; Joker Respin jokerSuccessRate := (jokerActivations > 0) ? ((jokerWins / jokerActivations) * 100) : 0 csvContent .= "Joker_Respin," . jokerActivations . "," . jokerWins . "," . Round(jokerSuccessRate, 1) . "%," . jokerPesos . "`n" ; Jackpot jackpotSuccessRate := (jackpotActivations > 0) ? ((jackpotWins / jackpotActivations) * 100) : 0 csvContent .= "Jackpot," . jackpotActivations . "," . jackpotWins . "," . Round(jokerSuccessRate, 1) . "%," . jackpotPesos . "`n" ; Sistema de Rescate rescueSuccessRate := (rescueActivations > 0) ? 100 : 0 ; Siempre tiene éxito cuando se activa csvContent .= "Rescate_Volatilidad," . rescueActivations . "," . rescueActivations . "," . rescueSuccessRate . "%," . rescuePesos . "`n" csvContent .= "`n" ; ✅ VOLATILIDAD csvContent .= "VOLATILIDAD`n" csvContent .= "Métrica,Valor`n" csvContent .= "Racha_Pérdida_Más_Larga," . longestLosingStreak . "`n" csvContent .= "Racha_Pérdida_Actual," . currentLosingStreak . "`n" csvContent .= "Giros_Sin_Victoria_Actual," . rescueSpinsWithoutWin . "`n" avgSpinsBetweenWins := (detailedTotalWins > 0) ? (detailedTotalSpins / detailedTotalWins) : 0 csvContent .= "Giros_Promedio_Entre_Victorias," . Round(avgSpinsBetweenWins, 1) . "`n" winRate := (detailedTotalSpins > 0) ? ((detailedTotalWins / detailedTotalSpins) * 100) : 0 csvContent .= "Tasa_de_Victoria," . Round(winRate, 2) . "%`n" csvContent .= "`n" ; ✅ DESGLOSE RTP CON VERIFICACIONES csvContent .= "DESGLOSE RTP`n" csvContent .= "Componente,Contribución_RTP`n" ; Calcular con seguridad contra división por cero if (detailedTotalSpins > 0) { baseRTP := ((detailedTotalPesos - detailedCompensations - detailedJokerRespinPesos - totalMultiplierPesos - (detailedJackpotsWon * jackpotValue) - rescuePesos) / detailedTotalSpins) * 100 pityRTP := (detailedCompensations / detailedTotalSpins) * 100 jokerRTP := (detailedJokerRespinPesos / detailedTotalSpins) * 100 multiplierRTP := (totalMultiplierPesos / detailedTotalSpins) * 100 jackpotRTP := ((detailedJackpotsWon * jackpotValue) / detailedTotalSpins) * 100 rescueRTP := (rescuePesos / detailedTotalSpins) * 100 } else { baseRTP := 0 pityRTP := 0 jokerRTP := 0 multiplierRTP := 0 jackpotRTP := 0 rescueRTP := 0 } csvContent .= "Símbolos_Base," . Round(baseRTP, 2) . "%`n" csvContent .= "Sistema_Pity," . Round(pityRTP, 2) . "%`n" csvContent .= "Joker_Respins," . Round(jokerRTP, 2) . "%`n" csvContent .= "Multiplicadores," . Round(multiplierRTP, 2) . "%`n" csvContent .= "Jackpot," . Round(jackpotRTP, 2) . "%`n" csvContent .= "Rescate_Volatilidad," . Round(rescueRTP, 2) . "%`n" csvContent .= "TOTAL," . Round(currentRTP, 2) . "%`n" FileAppend(csvContent, filename) MyGui.Opt("+OwnDialogs") MsgBox("📈 DATOS EXPORTADOS`n`nArchivo: " . filename . "`n`nContiene análisis completo para afinación RTP", "Exportación Completada", "64") } catch Error as e { MyGui.Opt("+OwnDialogs") MsgBox("❌ ERROR al exportar datos:`n" . e.Message . "`nLínea: " . e.Line, "Error de Exportación", "16") } } UpdateStatsDisplay() { global totalSpins, totalWins, totalPesos, jackpotCounter, pityCounter, pityThreshold, multiplierActivations, rescueActivations TotalSpinsText.Value := totalSpins TotalPesosText.Value := totalPesos JackpotCounterText.Value := jackpotCounter . "/5000" PityCounterText.Value := pityCounter . "/" . pityThreshold RescueCounterText.Value := rescueActivations ; ← NUEVO: Mostrar rescates UpdateWinPercentages() } StartSpin(*) { global spinning, totalSpins, detailedTotalSpins, SpinBtn, Spin1000Btn, Spin10000Btn, imgPath, spinInterval, spinDuration global currentProfile if spinning return spinning := true SpinBtn.Enabled := false SpinBtn.Text := "Girando..." Spin1000Btn.Enabled := false Spin10000Btn.Enabled := false totalSpins++ detailedTotalSpins++ UpdateStatsDisplay() SaveStats() Loop 3 MyGui["Slot" . A_Index].Value := imgPath . "question.png" SetTimer(SpinSlots, spinInterval) SetTimer(StopSpin, -spinDuration) } SpinSlots() { global symbolList, imgPath, MyGui, img1, img2, img3 randIndex := Random(1, symbolList.Length) img1 := symbolList[randIndex] randIndex := Random(1, symbolList.Length) img2 := symbolList[randIndex] randIndex := Random(1, symbolList.Length) img3 := symbolList[randIndex] Loop 3 { currentImg := (A_Index = 1) ? img1 : (A_Index = 2) ? img2 : img3 imgFile := imgPath . currentImg . ".png" fallback := imgPath . "question.png" if FileExist(imgFile) MyGui["Slot" . A_Index].Value := imgFile else MyGui["Slot" . A_Index].Value := fallback } } StopSpin() { global spinning, MyGui, imgPath, img1, img2, img3, symbolWin, jackpotCounter, pityCounter, pityThreshold global SpinBtn, Spin1000Btn, Spin10000Btn, lastWin global currentLosingStreak, longestLosingStreak global currentProfile, rescueSpinsWithoutWin SetTimer(SpinSlots, 0) Critical true try { MyGui["Slot1"].Value := imgPath . img1 . ".png" MyGui["Slot2"].Value := imgPath . img2 . ".png" MyGui["Slot3"].Value := imgPath . img3 . ".png" } Sleep 30 symbolWin := CheckWinCombination(img1, img2, img3) ; ===== VERIFICAR RESCATE DE VOLATILIDAD MEJORADO ===== if (!symbolWin || symbolWin = false) { rescueSpinsWithoutWin++ if (CheckVolatilityRescue()) { ; El rescate manejó la victoria completamente spinning := false SpinBtn.Enabled := true SpinBtn.Text := "1 Moneda" SpinBtn.Opt("+Default") if (!autoSpin) { Spin1000Btn.Enabled := true Spin10000Btn.Enabled := true } ; Mostrar mensaje discreto en modo normal if (!testingMode && !autoSpin) { SetTimer(ShowRescueMessage, -500) } SaveStats() SaveDetailedStats() Critical false return } } else { rescueSpinsWithoutWin := 0 } if (symbolWin is String && symbolWin != "") { HandleTripleWin() } if (jackpotCounter >= 5000) { HandleJackpotWin() } if (!symbolWin || symbolWin = false) { if (img1 = "joker" || img2 = "joker" || img3 = "joker") { HandleJokerRespin() } else { HandleLoss() } } if (!lastWin) { currentLosingStreak++ } else { if (currentLosingStreak > longestLosingStreak) { longestLosingStreak := currentLosingStreak } currentLosingStreak := 0 } PityCounterText.Value := pityCounter . "/" . pityThreshold JackpotCounterText.Value := jackpotCounter . "/5000" UpdateBonusLights() UpdateWinPercentages() UpdateJokerStatus() spinning := false SpinBtn.Enabled := true SpinBtn.Text := "1 Moneda" SpinBtn.Opt("+Default") if (!autoSpin) { Spin1000Btn.Enabled := true Spin10000Btn.Enabled := true } SaveStats() SaveDetailedStats() Critical false } ShowRescueMessage() { MyGui.Opt("+OwnDialogs") MsgBox("¡Combinación ganadora!`n`nHas obtenido una victoria.", "¡Ganaste!", "64") } CheckWinCombination(reel1, reel2, reel3) { symbolsInReels := [reel1, reel2, reel3] symbolCounts := Map() jokerCount := 0 for symbol in symbolsInReels { if (symbol = "joker") { jokerCount++ } else { symbolCounts[symbol] := symbolCounts.Has(symbol) ? symbolCounts[symbol] + 1 : 1 } } for symbol, count in symbolCounts { if (count = 3) { return symbol } } for symbol, count in symbolCounts { if (count = 2 && jokerCount >= 1) { return symbol } } for symbol, count in symbolCounts { if (count = 1 && jokerCount >= 2) { return symbol } } if (jokerCount = 3) { return "three_jokers" } return false } ; ===== NUEVAS FUNCIONES RTP35 MEJORADO ===== CheckSafetyNet() { global currentLosingStreak, safetyNetThreshold, safetyNetActive, safetyNetBoost global symbols, currentProfile, totalSpins ; Solo activar para RTP35/RTP40/RTP45/RTP50 y si no está ya activo if ((currentProfile != "RTP35" && currentProfile != "RTP40" && currentProfile != "RTP45" && currentProfile != "RTP50") || safetyNetActive || currentLosingStreak < safetyNetThreshold) { return } safetyNetActive := true safetyNetBoost := true ; ✅ DISTRIBUCIÓN DE EMERGENCIA TEMPORAL POR PERFIL if (currentProfile = "RTP35") { emergencySymbols := Map( "cherry", 25, ; -3 temporalmente "bell", 22, ; -2 "lemon", 14, ; -2 "star", 18, ; +3 (aumentar significativamente) "diamond", 12, ; +3 (más oportunidades alto valor) "seven", 8, ; +1 (más símbolos premium) "joker", 1 ; = (mantener) ) } else if (currentProfile = "RTP40") { emergencySymbols := Map( "cherry", 28, ; -3 temporalmente "bell", 23, ; -3 "lemon", 15, ; -2 "star", 15, ; +3 (aumentar significativamente) "diamond", 10, ; +2 (más oportunidades alto valor) "seven", 6, ; +1 (más símbolos premium) "joker", 1 ; = (mantener) ) } else if (currentProfile = "RTP45") { emergencySymbols := Map( "cherry", 38, ; -2 temporalmente "bell", 23, ; -2 "lemon", 14, ; -1 "star", 10, ; +2 (aumentar significativamente) "diamond", 6, ; +1 (más oportunidades alto valor) "seven", 5, ; +1 (más símbolos premium) "joker", 2 ; = (mantener) ) } else if (currentProfile = "RTP50") { emergencySymbols := Map( "cherry", 45, ; -3 temporalmente "bell", 26, ; -2 "lemon", 16, ; -2 "star", 12, ; +2 (aumentar significativamente) "diamond", 7, ; +1 (más oportunidades alto valor) "seven", 6, ; +1 (más símbolos premium) "joker", 2 ; = (mantener) ) } ; Aplicar distribución de emergencia ApplyEmergencyDistribution(emergencySymbols) ; Mostrar notificación MyGui.Opt("+OwnDialogs") MsgBox("🛡️ SISTEMA DE SEGURIDAD " . currentProfile . " ACTIVADO`n`nDespués de " . currentLosingStreak . " giros sin ganar`nProbabilidades premium aumentadas temporalmente`n¡La suerte está por cambiar!", "Red de Seguridad", "64") ; Restaurar después de 10 giros o primera victoria SetTimer(RestoreOriginalDistribution, 10000) } ApplyEmergencyDistribution(emergencySymbols) { global symbols symbols := emergencySymbols.Clone() UpdateSym