| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /var/www/clients/client2/web31/web/slots/includes/ |
Upload File : |
<?php
/**
* Reel Royale – Generische Slot-Engine.
* Eine Engine bedient alle Spiele. Jedes Spiel liefert nur seine Konfiguration
* (Symbole, Gewichte, Auszahlungen, Paylines, Feature). Die komplette
* Gewinn- und Feature-Auswertung läuft hier – serverautoritativ.
*/
/** Master-Set von 25 Paylines für ein 5x3 Raster. Koordinaten: [reel, row]. */
function master_paylines(): array {
return [
1 => [[0,1],[1,1],[2,1],[3,1],[4,1]],
2 => [[0,0],[1,0],[2,0],[3,0],[4,0]],
3 => [[0,2],[1,2],[2,2],[3,2],[4,2]],
4 => [[0,0],[1,1],[2,2],[3,1],[4,0]],
5 => [[0,2],[1,1],[2,0],[3,1],[4,2]],
6 => [[0,0],[1,0],[2,1],[3,2],[4,2]],
7 => [[0,2],[1,2],[2,1],[3,0],[4,0]],
8 => [[0,1],[1,0],[2,0],[3,0],[4,1]],
9 => [[0,1],[1,2],[2,2],[3,2],[4,1]],
10 => [[0,0],[1,1],[2,1],[3,1],[4,0]],
11 => [[0,2],[1,1],[2,1],[3,1],[4,2]],
12 => [[0,1],[1,1],[2,0],[3,1],[4,1]],
13 => [[0,1],[1,1],[2,2],[3,1],[4,1]],
14 => [[0,0],[1,1],[2,0],[3,1],[4,0]],
15 => [[0,2],[1,1],[2,2],[3,1],[4,2]],
16 => [[0,1],[1,0],[2,1],[3,0],[4,1]],
17 => [[0,1],[1,2],[2,1],[3,2],[4,1]],
18 => [[0,0],[1,2],[2,0],[3,2],[4,0]],
19 => [[0,2],[1,0],[2,2],[3,0],[4,2]],
20 => [[0,0],[1,1],[2,2],[3,2],[4,2]],
21 => [[0,0],[1,0],[2,1],[3,0],[4,0]],
22 => [[0,2],[1,2],[2,1],[3,2],[4,2]],
23 => [[0,1],[1,0],[2,2],[3,0],[4,1]],
24 => [[0,1],[1,2],[2,0],[3,2],[4,1]],
25 => [[0,0],[1,2],[2,2],[3,2],[4,0]],
];
}
function game_paylines(array $game): array {
$max = (int)($game['max_lines'] ?? 25);
return array_slice(master_paylines(), 0, $max, true);
}
function game_active_paylines(array $game, int $count): array {
return array_slice(game_paylines($game), 0, $count, true);
}
function rr_random_float(): float {
return mt_rand() / mt_getrandmax();
}
function rr_weighted_pick(array $weights): string {
$total = array_sum($weights);
if ($total <= 0) {
return (string)array_key_first($weights);
}
$pick = rr_random_float() * $total;
$running = 0.0;
foreach ($weights as $code => $weight) {
$running += $weight;
if ($pick <= $running) {
return (string)$code;
}
}
return (string)array_key_last($weights);
}
/** Symbole, deren Typ in der Liste enthalten ist. */
function game_symbols_of_type(array $game, array $types): array {
$out = [];
foreach ($game['symbols'] as $code => $sym) {
if (in_array($sym['type'], $types, true)) {
$out[] = (string)$code;
}
}
return $out;
}
/** Erzeugt ein 5x3 Raster nach Walzengewichten. $grid[$row][$col]. */
function game_generate_grid(array $game, array $state = []): array {
$reels = (int)($game['reels'] ?? 5);
$rows = (int)($game['rows'] ?? 3);
$isFree = !empty($state['is_free_spin']);
$grid = [];
for ($row = 0; $row < $rows; $row++) {
$grid[$row] = [];
}
for ($col = 0; $col < $reels; $col++) {
$weights = [];
foreach ($game['symbols'] as $code => $sym) {
$w = (float)($sym['weights'][$col] ?? 1);
// Im Free-Spin abweichende Gewichte erlaubt (z.B. mehr Money-Symbole)
if ($isFree && isset($sym['weights_free'][$col])) {
$w = (float)$sym['weights_free'][$col];
}
$weights[$code] = max($w, 0.0);
}
// Pro Walze ziehen – Scatter/Bonus/Collector höchstens einmal je Walze
$usedOnce = [];
for ($row = 0; $row < $rows; $row++) {
$local = $weights;
foreach ($usedOnce as $u) {
unset($local[$u]);
}
$code = rr_weighted_pick($local);
$type = $game['symbols'][$code]['type'] ?? 'normal';
if (in_array($type, ['scatter', 'bonus', 'collector', 'money'], true)) {
$usedOnce[] = $code;
}
$grid[$row][$col] = $code;
}
}
return $grid;
}
function game_count_type(array $game, array $grid, string $type): int {
$n = 0;
foreach ($grid as $rowArr) {
foreach ($rowArr as $code) {
if (($game['symbols'][$code]['type'] ?? '') === $type) {
$n++;
}
}
}
return $n;
}
function game_positions_of(array $game, array $grid, string $type): array {
$pos = [];
foreach ($grid as $row => $rowArr) {
foreach ($rowArr as $col => $code) {
if (($game['symbols'][$code]['type'] ?? '') === $type) {
$pos[] = [$col, $row];
}
}
}
return $pos;
}
function symbol_is_wild(array $game, string $code): bool {
return ($game['symbols'][$code]['type'] ?? '') === 'wild';
}
function symbol_can_pay(array $game, string $code): bool {
$t = $game['symbols'][$code]['type'] ?? 'normal';
return in_array($t, ['normal', 'wild'], true);
}
/** Skalierte Auszahlung eines Symbols (RTP-Tuning über game['pay_scale']). */
function game_pay(array $game, string $code, int $count): float {
$base = (float)($game['symbols'][$code]['payouts'][$count] ?? 0);
return round($base * (float)($game['pay_scale'] ?? 1), 4);
}
/** Bestimmt den Liniengewinn (links nach rechts) für eine Payline. */
function game_evaluate_line(array $game, array $grid, array $line, float $betPerLine, ?string $expand = null): ?array {
$cells = [];
foreach ($line as [$col, $row]) {
$cells[] = ['code' => $grid[$row][$col], 'col' => $col, 'row' => $row];
}
// Mögliche Zielsymbole: alle zahlbaren Nicht-Wilds der Linie + reines Wild
$targets = [];
foreach ($cells as $c) {
if (symbol_can_pay($game, $c['code']) && !symbol_is_wild($game, $c['code'])) {
$targets[$c['code']] = true;
}
}
$targetCodes = array_keys($targets);
if (!$targetCodes) {
$targetCodes = game_symbols_of_type($game, ['wild']);
}
$best = null;
foreach ($targetCodes as $target) {
$positions = [];
$count = 0;
foreach ($cells as $c) {
$code = $c['code'];
$match = ($code === $target) || symbol_is_wild($game, $code);
if ($match) {
$positions[] = [$c['col'], $c['row']];
$count++;
} else {
break;
}
}
$payouts = $game['symbols'][$target]['payouts'] ?? [];
if ($count < 2 || !isset($payouts[$count])) {
continue;
}
$mult = game_pay($game, $target, $count);
if ($expand && $target === $expand) {
$mult = round($mult * 1.5, 2);
}
$candidate = [
'symbol' => $target,
'count' => $count,
'multiplier'=> $mult,
'win' => round($mult * $betPerLine, 2),
'positions' => $positions,
'path' => $line,
];
if ($best === null || $candidate['win'] > $best['win']) {
$best = $candidate;
}
}
return $best;
}
/** Buch-Feature: Spezialsymbol expandiert über volle Walzen (nur Free Spins).
* $onWildToo=true: eine Walze expandiert auch, wenn dort (nur) ein Wild liegt. */
function game_apply_expanding(array $game, array $grid, string $special, bool $onWildToo = true): array {
$reels = (int)$game['reels'];
$rows = (int)$game['rows'];
$expanded = [];
for ($col = 0; $col < $reels; $col++) {
$has = false;
for ($row = 0; $row < $rows; $row++) {
if ($grid[$row][$col] === $special || ($onWildToo && symbol_is_wild($game, $grid[$row][$col]))) {
$has = true;
break;
}
}
if ($has) {
for ($row = 0; $row < $rows; $row++) {
$grid[$row][$col] = $special;
$expanded[] = [$col, $row];
}
}
}
return [$grid, $expanded];
}
/** Wählt ein zufälliges zahlendes Normalsymbol als Glückssymbol (Book-of-Ra-Stil).
* Gilt für die gesamte Freispiel-Runde. */
function game_pick_lucky(array $game): ?string {
$normals = game_symbols_of_type($game, ['normal']);
return $normals ? $normals[array_rand($normals)] : null;
}
/** Wild-Transformation: zufällige Walze wird komplett Wild (Free Spins). */
function game_apply_wild_transform(array $game, array $grid): array {
$wilds = game_symbols_of_type($game, ['wild']);
if (!$wilds) {
return [$grid, []];
}
$wild = $wilds[0];
$reels = (int)$game['reels'];
$rows = (int)$game['rows'];
// Walze transformieren, wenn dort bereits ein hohes Symbol/Wild liegt
$candidates = [];
for ($col = 0; $col < $reels; $col++) {
for ($row = 0; $row < $rows; $row++) {
if (symbol_is_wild($game, $grid[$row][$col])) {
$candidates[] = $col;
break;
}
}
}
if (!$candidates && rr_random_float() < 0.5) {
$candidates[] = mt_rand(0, $reels - 1);
}
$transformed = [];
foreach (array_unique($candidates) as $col) {
for ($row = 0; $row < $rows; $row++) {
$grid[$row][$col] = $wild;
$transformed[] = [$col, $row];
}
}
return [$grid, $transformed];
}
/**
* Hauptauswertung eines Spins.
* $state: is_free_spin, special, feature_state (mult, collected, walk)
*/
function game_evaluate(array $game, array $grid, int $lineCount, float $betPerLine, array $state): array {
$feature = $game['feature'] ?? ['type' => 'expanding'];
$type = $feature['type'] ?? 'expanding';
$isFree = !empty($state['is_free_spin']);
$special = $state['special'] ?? null;
$fs = $state['feature_state'] ?? [];
$globalMult= (float)($fs['mult'] ?? 1);
$meta = [
'expanded' => [],
'transformed'=> [],
'collected' => [],
'walk' => [],
'global_mult'=> $globalMult,
'collect_win'=> 0.0,
'bonus' => null,
];
// --- Feature Vor-Effekte (verändern das Grid) ---
if ($isFree && $type === 'expanding' && $special) {
[$grid, $meta['expanded']] = game_apply_expanding($game, $grid, $special);
}
if ($isFree && $type === 'wild_transform') {
[$grid, $meta['transformed']] = game_apply_wild_transform($game, $grid);
}
if ($isFree && $type === 'walking_wild' && !empty($fs['walk'])) {
// bestehende Walking-Wild-Spalten erneut als Wild setzen
$wilds = game_symbols_of_type($game, ['wild']);
$wild = $wilds[0] ?? null;
if ($wild) {
foreach ($fs['walk'] as $col) {
if ($col >= 0 && $col < (int)$game['reels']) {
for ($row = 0; $row < (int)$game['rows']; $row++) {
$grid[$row][$col] = $wild;
$meta['walk'][] = [$col, $row];
}
}
}
}
}
// --- Glückssymbol (Freispiele): liegt es in einer Spalte, füllt es die ganze Spalte
// (oben→unten); sind ALLE Walzen gefüllt = Vollbild-Bonus. Bei 'expanding'-Spielen IST
// das Spiel-Spezialsymbol das Glückssymbol (kein zweites Expanding → geringere Varianz).
$lucky = ($type === 'expanding' && $special) ? $special : ($state['lucky'] ?? null);
$fullscreen = false;
if ($isFree && $lucky) {
if ($type !== 'expanding') {
[$grid, $luckyExpanded] = game_apply_expanding($game, $grid, $lucky, false);
if ($luckyExpanded) $meta['expanded'] = array_merge($meta['expanded'], $luckyExpanded);
}
$meta['lucky'] = $lucky;
$fullscreen = true; // Vollbild? (alle Zellen = Glückssymbol)
foreach ($grid as $rowArr) {
foreach ($rowArr as $c) { if ($c !== $lucky) { $fullscreen = false; break 2; } }
}
}
// --- Linienauswertung ---
$expandSym = ($type === 'expanding') ? $special : null;
$wins = [];
$totalWin = 0.0;
foreach (game_active_paylines($game, $lineCount) as $lineNo => $line) {
$res = game_evaluate_line($game, $grid, $line, $betPerLine, $expandSym);
if ($res) {
// globaler Multiplikator (Free Spins) auf Liniengewinne
if ($globalMult > 1) {
$res['multiplier'] = round($res['multiplier'] * $globalMult, 2);
$res['win'] = round($res['win'] * $globalMult, 2);
$res['boosted'] = $globalMult;
}
$res['line'] = $lineNo;
$wins[] = $res;
$totalWin += (float)$res['win'];
}
}
// --- Scatter & Free Spins ---
$scatterCodes = game_symbols_of_type($game, ['scatter']);
$scatterCount = 0;
$scatterCode = $scatterCodes[0] ?? null;
if ($scatterCode) {
$scatterCount = game_count_type($game, $grid, 'scatter');
}
$scatterWin = 0.0;
$freeSpinsWon = 0;
if ($scatterCode) {
$sPayouts = $game['symbols'][$scatterCode]['payouts'] ?? [];
if (isset($sPayouts[$scatterCount])) {
$scatterWin = round(game_pay($game, $scatterCode, $scatterCount) * ($betPerLine * $lineCount), 2);
$totalWin += $scatterWin;
}
$trigger = $feature['free_spins'] ?? ($game['symbols'][$scatterCode]['free_spins'] ?? []);
if (isset($trigger[$scatterCount])) {
$freeSpinsWon = (int)$trigger[$scatterCount];
}
if ($scatterCount >= 3) {
$wins[] = [
'line' => 0, 'symbol' => $scatterCode, 'count' => $scatterCount,
'multiplier' => $scatterWin > 0 ? round($scatterWin / max(1, $betPerLine * $lineCount), 2) : 0,
'win' => $scatterWin, 'positions' => game_positions_of($game, $grid, 'scatter'),
'path' => [], 'free_spins' => $freeSpinsWon, 'is_scatter' => true,
];
}
}
// --- Money / Collector Feature (Fishing-Style) ---
if (in_array($type, ['collector'], true)) {
$collectorCodes = game_symbols_of_type($game, ['collector', 'wild']);
$hasCollector = false;
foreach ($collectorCodes as $cc) {
foreach ($grid as $rowArr) {
if (in_array($cc, $rowArr, true)) { $hasCollector = true; break 2; }
}
}
$moneyPositions = game_positions_of($game, $grid, 'money');
// Im Basisspiel: Money zahlt nur mit Collector. In Free Spins: immer mit Collector.
if ($moneyPositions && ($hasCollector || $isFree && $hasCollector)) {
$collectSum = 0.0;
$scale = (float)($game['pay_scale'] ?? 1);
foreach ($moneyPositions as [$col, $row]) {
$code = $grid[$row][$col];
$val = (float)($game['symbols'][$code]['value'] ?? 0);
// Münzwert skaliert mit dem GESAMTeinsatz (betPerLine * lineCount), damit die
// RTP unabhängig von der Linienanzahl bleibt (sonst sinkt sie bei mehr Linien).
$valued = round($val * $betPerLine * max(1, $lineCount) * $scale * mt_rand(8, 30) / 10, 2);
$collectSum += $valued;
$meta['collected'][] = ['col' => $col, 'row' => $row, 'win' => $valued];
}
$collectSum = round($collectSum * max(1, $globalMult), 2);
$meta['collect_win'] = $collectSum;
$totalWin += $collectSum;
}
}
// --- Bonus-Symbol Trigger (z.B. El Matador Rad) ---
if (!empty($feature['bonus'])) {
$bonusCodes = game_symbols_of_type($game, ['bonus']);
if ($bonusCodes) {
$bonusCount = game_count_type($game, $grid, 'bonus');
if ($bonusCount >= 3 && !$isFree) {
$meta['bonus'] = game_resolve_bonus($game, $betPerLine * $lineCount);
$totalWin += (float)$meta['bonus']['win'];
}
}
}
// --- Vollbild-Bonus des Glückssymbols („extra viel Punkte") ---
// Fixer Aufschlag auf den Gesamteinsatz (NICHT zusätzlich mit dem FS-Multiplikator
// gekoppelt – die vollflächigen Liniengewinne tragen den Multiplikator bereits; das
// vermeidet extreme Ausreißer und macht die RTP verlässlich tunebar).
if ($fullscreen) {
$fsMult = (float)($feature['fullscreen_mult'] ?? $game['fullscreen_mult'] ?? 50);
$fsWin = round($fsMult * $betPerLine * max(1, $lineCount), 2);
$meta['fullscreen'] = true;
$meta['fullscreen_win'] = $fsWin;
$totalWin += $fsWin;
}
return [
'grid' => $grid,
'wins' => $wins,
'total_win' => round($totalWin, 2),
'scatter_count' => $scatterCount,
'free_spins_won' => $freeSpinsWon,
'meta' => $meta,
];
}
/**
* Multiplikator für den aktuellen Freispin (geteilt von Live-Spin & Simulator).
* $fs = Feature-State der laufenden Freispiele.
*/
function game_fs_spin_mult(array $game, array $fs): float {
$feature = $game['feature'] ?? [];
if (!empty($feature['multipliers'])) {
$m = $feature['multipliers'];
return (float)$m[array_rand($m)];
}
if (!empty($feature['rising_mult'])) {
$rm = $feature['rising_mult'];
$tier = (int)($fs['tier'] ?? 0);
return (float)$rm[min($tier, count($rm) - 1)];
}
return 1.0;
}
/**
* Schreibt den Feature-State eines Freispins fort (Sammler-Anzahl, Tier, Walking Wilds).
* Liefert den neuen State.
*/
function game_advance_fs(array $game, array $fs, array $result, array $grid): array {
$feature = $game['feature'] ?? [];
$type = $feature['type'] ?? 'expanding';
$newFs = $fs;
$newFs['collected'] = (int)($fs['collected'] ?? 0) + count($result['meta']['collected'] ?? []);
if (!empty($feature['rising_mult'])) {
$c = $newFs['collected'];
$tier = $c >= 10 ? 3 : ($c >= 6 ? 2 : ($c >= 4 ? 1 : 0));
$newFs['tier'] = min($tier, count($feature['rising_mult']) - 1);
}
if ($type === 'walking_wild') {
$walk = $fs['walk'] ?? [];
$wilds = game_symbols_of_type($game, ['wild']);
$wild = $wilds[0] ?? null;
if ($wild) {
for ($col = 0; $col < (int)$game['reels']; $col++) {
for ($row = 0; $row < (int)$game['rows']; $row++) {
if ($grid[$row][$col] === $wild && !in_array($col, $walk, true)) {
$walk[] = $col;
}
}
}
}
$newFs['active_wilds'] = count($walk);
$moved = [];
foreach ($walk as $col) { if ($col - 1 >= 0) { $moved[] = $col - 1; } }
$newFs['walk'] = array_values(array_unique($moved));
$rm = $feature['rising_mult'] ?? [1, 2, 3, 5];
$newFs['tier'] = min(count($walk), count($rm) - 1);
}
$newFs['special'] = $fs['special'] ?? null;
$newFs['lucky'] = $fs['lucky'] ?? null; // Glückssymbol bleibt die ganze FS-Runde gleich
return $newFs;
}
/** Serverautoritatives Bonus-Rad-Ergebnis. */
function game_resolve_bonus(array $game, float $totalBet): array {
$cfg = $game['feature']['bonus'];
$segments = $cfg['segments'] ?? [2, 5, 8, 12, 20, 50];
$idx = mt_rand(0, count($segments) - 1);
$mult = (float)$segments[$idx];
return [
'type' => $cfg['kind'] ?? 'wheel',
'segments' => $segments,
'index' => $idx,
'multiplier' => $mult,
'win' => round($mult * $totalBet, 2),
];
}
/**
* Erzeugt die clientseitige (gewichtsfreie) Konfiguration eines Spiels.
* Walzengewichte werden bewusst NICHT übertragen (Anti-Cheat).
*/
function game_client_config(array $game): array {
$symbols = [];
$scale = (float)($game['pay_scale'] ?? 1);
$overrides = function_exists('game_symbol_overrides') ? game_symbol_overrides($game['id'] ?? '') : [];
foreach ($game['symbols'] as $code => $sym) {
$scaled = [];
foreach (($sym['payouts'] ?? []) as $count => $mult) {
$scaled[$count] = round($mult * $scale, 2);
}
$symbols[$code] = [
'name' => $sym['name'],
'icon' => $sym['icon'],
'type' => $sym['type'],
'payouts' => $scaled,
'free_spins' => $sym['free_spins'] ?? [],
'value' => $sym['value'] ?? null,
'color' => $sym['color'] ?? null,
'img' => $sym['img'] ?? null,
];
// Admin-Symbol-Override (eigenes Emoji/Text oder hochgeladenes Bild)
if (isset($overrides[$code])) {
$ov = $overrides[$code];
if (array_key_exists('img', $ov)) {
$symbols[$code]['img'] = $ov['img'] !== '' ? $ov['img'] : null;
}
if (!empty($ov['icon'])) {
$symbols[$code]['icon'] = $ov['icon'];
}
}
}
return [
'id' => $game['id'],
'title' => $game['title'],
'subtitle' => $game['subtitle'] ?? '',
'reels' => (int)($game['reels'] ?? 5),
'rows' => (int)($game['rows'] ?? 3),
'rtp' => $game['rtp'] ?? 0.96,
'volatility'=> $game['volatility'] ?? 'Mittel',
'hit_frequency' => $game['hit_frequency'] ?? null,
'max_lines' => (int)($game['max_lines'] ?? 25),
'paylines' => array_values(game_paylines($game)),
'symbols' => $symbols,
'feature' => [
'type' => $game['feature']['type'] ?? 'expanding',
'name' => $game['feature']['name'] ?? 'Freispiele',
'free_spins' => $game['feature']['free_spins'] ?? [],
'bonus' => isset($game['feature']['bonus']) ? ['kind' => $game['feature']['bonus']['kind'] ?? 'wheel'] : null,
],
'theme' => $game['theme'] ?? [],
];
}