#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