Level Difficulty Analyzer scoring breakdown
Bu döküman, scripts/difficulty_analyzer/ai_player.py içindeki AI'nın bir hamleyi nasıl seçtiğini özetler. AI, oyunun kendisini değil, oynanabilirlik analizi için kullanılan otomatik oyuncudur: her seferinde standalone engine'den bir board snapshot alır, legal_swaps listesindeki her aday hamleye bir skor atar, en yüksek skorlu hamleyi seçer.
engine snapshot ──► her aday swap için skor hesapla ──► en yüksek skor kazanır
(legal_swaps) (score function) (AIPlayer.choose)
Önemli: AI cascade simülasyonu yapmaz. Sadece şu anki board üzerinde bir swap'in *anlık* etkisini tahmin eder. Tasarımdaki tercih: hızlı + "yeterince akıllı" sezgisel skor.
Bir aday swap için skor:
score = (1) match-size bonusu
+ (2) collectable katkısı
+ (3 + 4 + 5) × urgency çarpanı
+ (7) look-ahead bonusu
Komponent (3), (4) ve (5) sadece power-up ile ilgili olanlar; urgency çarpanı yalnızca onlara uygulanır. Match ve collectable katkıları her zaman 1.0 ağırlıkta. Look-ahead bonusu ayrı bir bileşen — bir hamle sonrasını dikkate alır, kendi içinde 0.75 ile ölçeklenir, urgency çarpanı dolaylı olarak içsel skor üzerinden uygulanır.
Swap sonrası oluşacak match deseni → sabit puan tablosu. Tablodaki sıralama, oluşan power-up'ın gücüyle paraleldir:
| Desen | Spawn olan PU | Bonus |
|---|---|---|
3'lü satır (h-3 / v-3) | yok | 1 |
4'lü satır (h-4 / v-4) | knife | 8 |
| L veya T şekli | falcon | 12 |
| 2×2 kare | bomb | 18 |
5+ satır (h-5 / v-5) | lightning | 30 |
Lightning en yüksek puanı alır, çünkü kombo potansiyeli oyun içindeki en güçlü PU'yu üretir.
Swap'in temizleyeceği her hücre için:
regular_type'ı aktif bir tile-collectable ile eşleşiyorsa → +3.0İki katkı bağımsız ve birikimli. Levellarımızın çoğu special-collectable kullandığı için ikincisi pratikte daha sık devreye girer.
Swap'in iki ucundan biri veya ikisi PU ise:
power_up_score += ACTIVATION_BASE_BY_TYPE[pu]
+ 2.0 × (PU'nun blast footprint'inde kalan collectable hücre sayısı)
Tip-bazlı base değerleri (PU'ların temizleme gücüne göre):
| PU tipi | Yaklaşık temizleme | Base |
|---|---|---|
| falcon | 5 + 1 garanti | 10 |
| verticalKnife / horizontalKnife | bir satır/sütun (~9) | 12 |
| bomb | 5×5 = 25 hücre | 20 |
| lightning | tek tip tüm hücreler + kombo | 25 |
Blast footprint yaklaşımları:
| Kombinasyon | Bonus |
|---|---|
| lightning + lightning (tüm board temizlenir) | +120 |
| lightning + diğer PU (10–15 PU saçar) | +70 |
| non-lightning kombo (bomb+bomb, knife+falcon, vs.) | +30 |
Falcon'un ayırt edici özelliği: gittiği son hedefte garanti vuruş. Şu üç koşul birden sağlanırsa, her falcon ucu için +20 flat bonus eklenir:
moves_remaining / moves_total < 0.3 (geç oyun)Sadece power-up portion'ına (3 + 4 + 5) uygulanır. Match-size ve collectable katkıları ölçeklenmez.
f = moves_remaining / moves_total
f ≥ 0.40 ─► çarpan = 1.0 (early/mid game, biriktirme serbest)
0.20 ≤ f < 0.40 ─► 1.0 → 2.0 lineer (urgency, PU'ları kullanmaya yönel)
0.00 ≤ f < 0.20 ─► 2.0 → 4.0 lineer (very-urgency, bitirme zamanı)
Yani: erken ve orta oyunda AI yeni PU üretmeye serbestçe karar verebilir; hamle sayısı azaldıkça eldeki PU'ları kullanmak ve aktive etmek dramatik olarak daha cazip hale gelir.
Mevcut hamlenin direkt etkisinin yanı sıra, AI bir hamle sonrasında ortaya çıkabilecek yeni PU↔PU komşuluklarını da hesaba katar. Mantık:
1. Aday hamleyi sanal olarak uygula:
r2, c2) yeni bir PU spawn olur (h-4/v-4→knife, L/T→falcon, 2×2→bomb, 5+→lightning).2. Sadece vertical gravity uygulanır (diagonal slide göz ardı edilir, basitleştirme). 3. Gravity-blocker hücreler (B/DB/TB/Box renkli varyantları/W/DW/BW/I/DI/TI/J/DJ) kolonu segmentlere böler; her segment kendi içinde compact edilir. 4. Mobil tile'lar (regular tile'lar ve PU'lar, special'sız) bottom-up sıralanır ve her segmentin altına yerleştirilir; segmentin üstünde kalan hücreler placeholder regular tile ile doldurulur (refill'in PU üretmediği gerçeğini yansıtır). 5. Sonrasında board'da, hamle öncesinde olmayan yeni PU↔PU 4-komşulukları taranır. 6. Her yeni komşuluk için, sanal post-board üzerinde o iki PU'nun swap olması durumunda alacağı skor hesaplanır (apply_lookahead=False ile, sonsuz özyineleme önlenir; içsel skorda match-size sıfır olur, ama aktivasyon + kombo + falcon late-game + urgency normalde uygulanır). 7. Her yeni komşuluğun katkısı 0.75 ile çarpılıp toplanır.
Bonus, aday skorun yanına eklenir. Bonus 0.0 olabilir (yeni komşuluk yoksa, look-ahead atlanırsa, ya da aday hamle inelegible ise — örneğin PU↔PU ya da falcon aktivasyonu).
| Konu | Davranış | Not |
|---|---|---|
| Diagonal gravity | Yok | XH gap'leri etrafında küçük hata payı |
| Refill cells | regular_type=-1 placeholder, no PU | Engine'in random regular refill'i ile uyumlu |
| Stokastik PU aktivasyonları (falcon) | Atlanır | Random hedefler tahmin edilemez |
| Stokastik PU aktivasyonları (lightning) | Partner'ın regular tipindeki tüm hücreler | Engine'in lightning davranışına yaklaşım |
| Secondary cascade'in spawn ettiği PU'lar | Yok | "Sadece PU↔PU komşuluk" kuralının doğal sonucu |
| Special'ların collectable katkı progression'ı | Modellenmez | İçsel skor azca overestimate edebilir; 0.75 ile dengeleniyor |
| Determinism | Sağlanır | Saf hesaplama, RNG yok |
AIPlayer.chooselegal_swaps üzerinde tek geçişli linear tarama:
1. Her aday için score(swap, state) hesaplanır. 2. Strict maksimum kazanır. 3. Beraberlikte kural: lexicographic olarak (row, col) en küçük "alt uç"a sahip swap kazanır. Swap mantıksal olarak sırasız bir çift olduğundan, tie-break tuple içindeki sıralamadan bağımsızdır. 4. Tam beraberlikte (aynı skor + aynı alt uç) legal_swaps sırasında ilk gelen kazanır.
Engine legal_swaps'i deterministik board-scan sırasında üretir; aynı board → aynı seçim. Bu, run'ların tekrarlanabilir olmasını sağlar.
legal_swapsAI'ın asla "engine'in sessizce reddedeceği" bir swap görmemesi kritik. Bunu sağlamak için engine tarafındaki enumerateLegalSwaps doğrudan BoardLogic.isSwapValid'e (engine'in _trySwap içinde hamle harcamadan önce kullandığı predicate'in kendisi) delege eder. Bu sayede şu kurallar otomatik olarak filtrelenir:
canSwapWithTiles: false olan special tile'lar (Box, DoubleBox, Stone, Wall, ColoredBox, Enemy…) — bir uç PU bile olsa swap dışlanır.AI'ın gördüğü legal_swaps = engine'in hamle harcayacağı legal_swaps. Bu eşitlik infinite-loop bug'ını önleyen anahtar invariant.
Bilinçli olarak şu an dahil edilmedi, ileride eklenebilir:
AI her hamlede
legal_swaps'teki tüm adayları skorlar; skor = match-size bonusu + collectable katkısı + (PU aktivasyon + kombo + falcon late-game) × urgency çarpanı + look-ahead bonusu. En yüksek skoru deterministik tie-break ile seçer. Lightning > Bomb > Knife/Falcon hiyerarşisi, special-collectable farkındalığı ve smooth iki-tier urgency eğrisi (0.4 ve 0.2 eşikleri) AI'nın hem PU üretmesini hem de zamanı geldiğinde kullanmasını dengeler. Look-ahead bonusu (1-kademelik vertical-only cascade) hamle sonrasında ortaya çıkacak yeni PU↔PU komşuluklarını tespit eder; her yeni komşuluk için sanal swap skoru × 0.75 eklenir, böylece AI gravity ile düşecek PU'ların bir araya gelme potansiyelini de değerlendirir.