Cluster pays

TL;DR

Выплата идёт за связные группы из N+ одинаковых символов, где «связно» означает 4-связное соседство по reel/row (без диагоналей). Минимальный кластер обычно 5. Чаще всего совмещается с tumbling reels — победившие кластеры исчезают, новые символы падают сверху, повторно evaluate.

Note

Ту же 4-связность использует Connect&Collect, но там это не pay-table по размеру группы, а путь спецсимволов к reward-узлам.

Как работает

Конфиг

Вместо paytable с фиксированными kind обычно используется pay group с диапазонами:

pay_group = {
  ((5,5), "H1"):    50,
  ((6,7), "H1"):    100,
  ((8,10), "H1"):   500,
  ((11,30), "H1"):  5000,
  ...
}
paytable = convert_range_table(pay_group)  # раскрывает в обычный {(5,"H1"):50, (6,"H1"):100, (7,"H1"):100, ...}

Зачем: на 6×5 поле возможны кластеры до 30 символов; вручную писать 25 строк per symbol — нечитаемо.

Алгоритм evaluate (BFS)

visited = matrix[num_reels][num_rows] = false
wins = []
для каждой позиции (r,c):
  если visited[r][c] или symbol(r,c) — special-non-paying: continue
  base_sym = symbol(r,c)
  если base_sym — wild: continue (wild сам не стартует кластер)

  cluster = BFS_4connected(r, c, base_sym, allow_wild=True)
  для каждой позиции в cluster: visited[pos] = true

  size = len(cluster)
  если size >= MIN_CLUSTER и (size, base_sym) в paytable:
    win = paytable[(size, base_sym)] × multiplier
    wins.append({symbol, kind: size, win, positions: cluster})

return {totalWin, wins}

BFS — стандартный, очередь, 4 соседа. Сложность O(num_reels × num_rows).

Wild — особый случай

Wild может одновременно принадлежать нескольким кластерам разных базовых символов. Алгоритм: при BFS из base_sym X wild считается членом кластера X, но visited[wild_pos] не ставится для других итераций. Wild не «потребляется» одним кластером.

H1 H1 W  H2 H2
H1 W  W  H2 H2
H1 W  H2 H2 L1
  • Кластер H1 (с реки 0–1, ряды 0–2) включает все H1 и три W → размер 7.
  • Кластер H2 включает все H2 и те же три W → размер 8.

Wild сам не стартует кластер (иначе wild-only «кластеры» зачитывались бы как (N, "W"), что обычно в paytable нет или явно запрещено).

Мат-эффект

  • Hit frequency — сильно вариативная: на 6×5 поле выпадение 5+ соседних — типичная задача, цифра зависит от reelstrip-композиции.
  • Volatility — по умолчанию ниже paylines, но с tumble loop становится высокой (комбо-цепочки могут давать огромные wins при удачном raid).
  • State space для cert-симуляции — размер board (N reels × M rows независимых позиций по reelstrip-стопу).
  • Часто RTP сильно весит во freegame (с multiplier-grid и global mult).

Варианты и подвиды

  • Чистый cluster без tumble (редко).
  • Cluster + tumble (стандарт, см. tumble).
  • Cluster + multiplier grid — в freegame каждая позиция «активируется» при первом win и накапливает множитель (часто 2× → 4× → … → 512×). Sweet Bonanza, Sugar Rush.
  • Global multiplier во freegame, инкрементируемый каждым tumble.
  • Diagonal-allowed — редко, обычно строго 4-связно.

Реализационные заметки

Бэк (TS)

function evaluateCluster(
  board: SymbolName[][],
  paytable: Map<string, number>,
  wildSyms: SymbolName[],
  minCluster: number,
): WinData {
  const numReels = board.length;
  const numRows = board[0].length;
  const visited = matrix(numReels, numRows, false);
  const wins: WinDetail[] = [];
 
  for (let r = 0; r < numReels; r++) {
    for (let c = 0; c < numRows; c++) {
      if (visited[r][c]) continue;
      const sym = board[r][c];
      if (wildSyms.includes(sym) || !isPayingSymbol(sym)) continue;
 
      const cluster = bfs4Connected(board, r, c, sym, wildSyms);
      // mark visited only for non-wild positions
      cluster.filter(p => !wildSyms.includes(board[p.reel][p.row])).forEach(p => {
        visited[p.reel][p.row] = true;
      });
 
      if (cluster.length >= minCluster) {
        const key = `${cluster.length}:${sym}`;
        const basePay = paytable.get(key) ?? 0;
        if (basePay > 0) {
          wins.push({symbol: sym, kind: cluster.length, win: basePay, positions: cluster, meta: {}});
        }
      }
    }
  }
  return {totalWin: sum(wins, w => w.win), wins};
}
 
function bfs4Connected(board, startR, startC, sym, wildSyms): Position[] {
  const queue = [{reel: startR, row: startC}];
  const cluster: Position[] = [];
  const seen = new Set<string>();
  seen.add(`${startR}:${startC}`);
 
  while (queue.length) {
    const {reel, row} = queue.shift()!;
    cluster.push({reel, row});
    for (const [dr, dc] of [[-1,0],[1,0],[0,-1],[0,1]]) {
      const nr = reel + dr, nc = row + dc;
      const key = `${nr}:${nc}`;
      if (nr < 0 || nr >= board.length || nc < 0 || nc >= board[0].length) continue;
      if (seen.has(key)) continue;
      const ns = board[nr][nc];
      if (ns === sym || wildSyms.includes(ns)) {
        seen.add(key);
        queue.push({reel: nr, row: nc});
      }
    }
  }
  return cluster;
}

Edge cases

  • Wild-only board — никаких выигрышей (wild не стартует кластер). Разве что мы в paytable явно прописали (N, "W").
  • Вложенные/перекрывающиеся кластеры через wild — учтены тем, что мы visited только non-wild позиции.
  • Большой кластер (>чем последнее значение в pay_group) — берём максимальный диапазон. convert_range_table это автоматизирует.

Фронт

Эмитится winInfo с positions всех клеток кластера + meta.overlay (центр кластера для размещения badge с win-amount):

'meta': {
  'overlay': {'reel': float, 'row': float}  # «центр масс» кластера
}

Фронт рисует pulsing-highlight всего кластера, badge посередине, потом tumbleBoard event запускает анимацию падения новых символов.

Compliance / cert

  • Help / paytable: раскрывать paytable с диапазонами и правило wild-substitution.
  • Visualization кластера должна быть читаемой — UKGC RTS требует «clear and accurate» представления выигрыша.
  • Tumble-механика и multiplier-grid требуют чёткого описания «как накапливается множитель».

Примеры реализаций

  • Pragmatic Play Sweet Bonanza (6×5, cluster + tumble + grid mult).
  • Pragmatic Play Sugar Rush (7×7, cluster + tumble).
  • NetEnt Aloha! Cluster Pays (родоначальник нынешней волны).
  • Stake math-sdk sample: games/0_0_cluster/.

Связанные