Metodología de las pruebas del agente
Las «pruebas del agente» de este sitio son simulaciones por computadora, no partidas en el juego real. Usan un modelo de Fortune Tiger construido con tres datos publicados (RTP, frecuencia de premio y tabla de pagos) y un supuesto nuestro: con qué probabilidad sale cada premio, porque el proveedor no lo publica. Aquí está todo lo necesario para revisarlas o repetirlas, incluido el código.
1. Datos de entrada (con fuente)
| RTP | 96.81 %[1][2] |
|---|---|
| Frecuencia de premio | 23.15 % de los giros[1] (22.16 % en el juego base + 0.99 % de la función Fortune Tiger) |
| Premio máximo | 2,500x la apuesta[1][2] |
| Tabla de pagos | 50x, 20x, 5x, 2x, 1.6x, 1x, 0.6x y su versión x10 (pantalla llena)[2][1] |
2. Lo que suponemos (no está publicado)
- Probabilidad de cada premio. Es proporcional a
x−a(los premios grandes salen menos). El exponentease ajusta para que el retorno esperado sea exactamente 96.81 %. Con los datos actuales,a= 0.819. - Premio máximo. Fijamos su probabilidad en 1 de cada 1,000,000 de giros.
- Independencia. Cada giro es independiente del anterior y de la hora, como en cualquier tragamonedas con generador de números aleatorios. Por eso el modelo no tiene memoria.
- Simplificación. El modelo no simula los carretes ni la función especial paso a paso: solo el resultado final de cada giro.
Probabilidad supuesta de cada resultado
- 0.60x — 1 de cada 13 giros
- 1.00x — 1 de cada 20 giros
- 1.60x — 1 de cada 30 giros
- 2.00x — 1 de cada 36 giros
- 5.00x — 1 de cada 75 giros
- 6.00x — 1 de cada 87 giros
- 10.00x — 1 de cada 133 giros
- 16.00x — 1 de cada 195 giros
- 20.00x — 1 de cada 234 giros
- 50.00x — 1 de cada 496 giros
- 200.00x — 1 de cada 1,545 giros
- 500.00x — 1 de cada 3,273 giros
- 2,500.00x — 1 de cada 1,000,000 giros
Retorno esperado del modelo: 96.8100 %.
3. Experimentos y tamaño de muestra
| Experimento | Muestra | Semilla |
|---|---|---|
| Calculadora: sesiones de 100 y 1,000 giros | 10,000 sesiones por tamaño, apuesta de $1 | 20260927 |
| Horarios: 24 horas con las mismas probabilidades | 24 × 20,000 giros = 480,000 giros, apuesta de $1; repetido con una segunda semilla | 20260929 y 7 |
| Rachas: premio después de giros sin premio | 2,000,000 giros seguidos | 20260929 |
| Martingala contra apuesta fija | 10,000 sesiones de hasta 500 giros, presupuesto de $200, apuesta inicial de $1, tope de $250 | 20260929 |
Usamos un generador pseudoaleatorio con semilla fija (mulberry32), así que cualquiera que corra el código con la misma semilla obtiene exactamente las mismas tablas. Las simulaciones se calculan al construir el sitio, no en tu navegador (salvo el botón «Simula tu propia sesión» de la calculadora, que usa una semilla aleatoria).
4. Límites
- No es el juego real. Si las probabilidades reales de cada premio son distintas a las supuestas, cambian la forma de los resultados (por ejemplo, qué tan seguido se pierde en 100 giros). No cambian las conclusiones que dependen solo del RTP y de la independencia de los giros: que la hora no importa, que las rachas no anuncian premios y que la martingala no reduce la pérdida esperada.
- Una sola versión. Modelamos la versión con RTP de 96.81 %. Algunos casinos pueden ofrecer versiones con otro RTP.
- Sin auditoría externa. Nadie fuera de este sitio ha revisado el modelo. Si encuentras un error, escríbenos.
5. Código del modelo
Este es el código completo que genera todas las pruebas (JavaScript, sin dependencias). Puedes copiarlo y correrlo con Node.js.
Ver el código (sim.mjs)
// Illustrative slot model for the "1,000 giros" simulator.
// Only two inputs come from sources: RTP and hit frequency. The shape of the prize
// distribution is OUR assumption (stated on the page); it is not the real game's paytable.
// Relative prize tiers (x bet) and their share of all winning spins. Values are scaled
// so the model's expected return equals the RTP exactly.
const TIERS = [
{ x: 0.4, w: 0.30 },
{ x: 1, w: 0.25 },
{ x: 2, w: 0.18 },
{ x: 4, w: 0.12 },
{ x: 8, w: 0.09 },
{ x: 15, w: 0.045 },
{ x: 40, w: 0.012 },
{ x: 150, w: 0.0025 },
{ x: 500, w: 0.0003 },
];
const MAX_WIN_P = 1e-6; // probability per spin of the max win (x maxWin), fixed, not scaled
export function buildModel({ rtp, hitRate, maxWin, payouts = null }) {
// With a published paytable, prize sizes are real (each 3-of-a-kind value and its x10 full-screen
// version); only their probabilities are assumed: weight ∝ x^-a, with `a` solved so E[return] = RTP.
// Without one, fall back to the generic tiers scaled to the RTP.
if (payouts?.length) {
const xs = [...new Set(payouts.flatMap((x) => [x, x * 10]))].filter((x) => x < maxWin).sort((a, b) => a - b);
const target = rtp / 100 - MAX_WIN_P * maxWin;
const pHit = hitRate - MAX_WIN_P;
const mean = (a) => { const w = xs.map((x) => x ** -a); const W = w.reduce((s, v) => s + v, 0); return xs.reduce((s, x, i) => s + (w[i] / W) * pHit * x, 0); };
let lo = 0, hi = 6;
for (let i = 0; i < 80; i++) { const mid = (lo + hi) / 2; if (mean(mid) > target) lo = mid; else hi = mid; }
const a = (lo + hi) / 2;
const w = xs.map((x) => x ** -a); const W = w.reduce((s, v) => s + v, 0);
return finish([...xs.map((x, i) => ({ x, p: (w[i] / W) * pHit })), { x: maxWin, p: MAX_WIN_P }], { rtp, hitRate, maxWin, exponent: a });
}
const wSum = TIERS.reduce((s, t) => s + t.w, 0);
const tiers = TIERS.map((t) => ({ x: t.x, p: (t.w / wSum) * (hitRate - MAX_WIN_P) }));
const fixed = MAX_WIN_P * maxWin;
const raw = tiers.reduce((s, t) => s + t.p * t.x, 0);
const k = (rtp / 100 - fixed) / raw; // scale so E[return] = rtp
return finish([...tiers.map((t) => ({ x: +(t.x * k).toFixed(4), p: t.p })), { x: maxWin, p: MAX_WIN_P }], { rtp, hitRate, maxWin });
}
function finish(outcomes, meta) {
const cdf = [];
let acc = 0;
for (const o of outcomes) { acc += o.p; cdf.push(acc); }
return { ...meta, outcomes, cdf, expected: outcomes.reduce((s, o) => s + o.p * o.x, 0) };
}
// mulberry32: small seeded PRNG so build output is reproducible.
export function rng(seed) {
let a = seed >>> 0;
return () => {
a = (a + 0x6d2b79f5) >>> 0;
let t = a;
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
}
export function spin(model, r) {
const u = r();
for (let i = 0; i < model.cdf.length; i++) if (u < model.cdf[i]) return model.outcomes[i].x;
return 0;
}
// Many sessions of `spins` spins at `bet`; returns summary stats of net result (won - bet).
export function simulate(model, { sessions = 10000, spins = 1000, bet = 1, seed = 20260927 } = {}) {
const r = rng(seed);
const nets = new Float64Array(sessions);
let wins = 0;
for (let s = 0; s < sessions; s++) {
let won = 0;
for (let i = 0; i < spins; i++) {
const x = spin(model, r);
if (x > 0) wins++;
won += x * bet;
}
nets[s] = won - spins * bet;
}
const sorted = Array.from(nets).sort((a, b) => a - b);
const q = (p) => sorted[Math.min(sorted.length - 1, Math.floor(p * sorted.length))];
return {
sessions, spins, bet,
staked: spins * bet,
lossShare: sorted.filter((n) => n < 0).length / sessions,
mean: sorted.reduce((s, n) => s + n, 0) / sessions,
p05: q(0.05), p25: q(0.25), median: q(0.5), p75: q(0.75), p95: q(0.95),
hitRateObserved: wins / (sessions * spins),
};
}
// "Horarios": the same model played for 24 simulated hours. Every hour uses the same odds,
// so any difference between hours is chance. Returns per-hour return % and hit rate %.
export function simulateHours(model, { spinsPerHour = 20000, seed = 20260929 } = {}) {
const r = rng(seed);
return Array.from({ length: 24 }, (_, hour) => {
let won = 0, hits = 0;
for (let i = 0; i < spinsPerHour; i++) { const x = spin(model, r); if (x > 0) hits++; won += x; }
return { hour, returnPct: (won / spinsPerHour) * 100, hitPct: (hits / spinsPerHour) * 100 };
});
}
// "Rachas": does a long run without prizes make the next prize more likely?
// Hit rate after `k` consecutive non-winning spins vs. the overall hit rate.
export function streakTest(model, { spins = 2_000_000, ks = [5, 10, 20], seed = 20260929 } = {}) {
const r = rng(seed);
const after = new Map(ks.map((k) => [k, { n: 0, hits: 0 }]));
let dry = 0, hits = 0;
for (let i = 0; i < spins; i++) {
const hit = spin(model, r) > 0;
for (const k of ks) if (dry >= k) { const a = after.get(k); a.n++; if (hit) a.hits++; }
if (hit) { hits++; dry = 0; } else dry++;
}
return { spins, overallPct: (hits / spins) * 100, after: ks.map((k) => ({ k, n: after.get(k).n, hitPct: (after.get(k).hits / after.get(k).n) * 100 })) };
}
// "Martingala": double the bet after each losing spin, back to the base bet after a win.
// Compared with a flat bet over the same number of spins and the same bankroll.
export function martingaleTest(model, { sessions = 10000, spins = 500, base = 1, bankroll = 200, maxBet = 250, seed = 20260929 } = {}) {
const run = (strategy) => {
const r = rng(seed);
const nets = [];
let busted = 0, wagered = 0;
for (let s = 0; s < sessions; s++) {
let cash = bankroll, bet = base;
for (let i = 0; i < spins; i++) {
if (bet > cash) { busted++; break; }
const win = spin(model, r) * bet;
wagered += bet;
cash += win - bet;
if (strategy === 'martingala') bet = win > bet ? base : Math.min(bet * 2, maxBet);
}
nets.push(cash - bankroll);
}
nets.sort((a, b) => a - b);
return {
lossShare: nets.filter((n) => n < 0).length / sessions,
median: nets[Math.floor(sessions / 2)],
wagered: wagered / sessions,
expectedLoss: (wagered / sessions) * (1 - model.rtp / 100),
p05: nets[Math.floor(0.05 * sessions)],
p95: nets[Math.floor(0.95 * sessions)],
bustShare: busted / sessions,
};
};
return { sessions, spins, base, bankroll, flat: run('plana'), martingala: run('martingala') };
}
Contacto y uso de los datos
Puedes citar estas simulaciones si las presentas como simulaciones y enlazas esta página. Para correcciones, preguntas de medios o los datos en bruto: contacto@juegofortunetiger.com.
Fuentes
- SlotCatalog – Fortune Tiger (PG Soft). Consultado el .
- BigWinBoard – Fortune Tiger (PG Soft) review. Consultado el .
- SlotCatalog – Fortune Tiger (Triple Profits Games). Consultado el .
- Resultados de Google México para «fortune tiger». Consultado el .