Files
BlockSeasons/lib/game/engine/scoring.dart
T
airkjw 0210c14858 Add pure-Dart engine core: RNG, grid, placement, line clear, scoring, piece generator
PCG32 seeded RNG; immutable 8x8 GridState with occupancy bitmask;
placement legality + anyPlacementExists; simultaneous row/col clears
with single-count gem credit; combo scoring with one-move grace;
weighted-bag generator with pity bias and depth-3 solvability nudge.
All TDD, 51 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:05:55 +09:00

51 lines
1.6 KiB
Dart

/// Pure scoring math: placement points, line-clear bonuses, combo streaks.
library;
/// Base score for clearing [lines] rows/columns simultaneously.
/// 1 -> 100, 2 -> 300, 3 -> 600, 4 -> 1000: simultaneous clears are
/// superlinearly rewarded.
int lineClearBase(int lines) => 100 * lines + 50 * (lines - 1) * lines;
/// Multiplier applied to clear score at combo [streak], capped at streak 8
/// (x5.0) so late-game scores stay bounded.
double comboMultiplier(int streak) => 1 + 0.5 * (streak > 8 ? 8 : streak);
/// Combo streak with one dry-move grace before reset.
class ComboState {
const ComboState({required this.streak, required this.dryMoves});
static const initial = ComboState(streak: 0, dryMoves: 0);
final int streak;
final int dryMoves;
/// One dry move keeps the streak alive (grace); the second resets it.
ComboState advance({required bool cleared}) {
if (cleared) return ComboState(streak: streak + 1, dryMoves: 0);
if (dryMoves + 1 >= 2) return initial;
return ComboState(streak: streak, dryMoves: dryMoves + 1);
}
}
class ScoreDelta {
const ScoreDelta({required this.points, required this.combo});
final int points;
final ComboState combo;
}
/// Computes the score delta for one placement and the resulting combo state.
ScoreDelta scorePlacement({
required int cellsPlaced,
required int linesCleared,
required ComboState combo,
}) {
final next = combo.advance(cleared: linesCleared > 0);
var points = cellsPlaced;
if (linesCleared > 0) {
points += (lineClearBase(linesCleared) * comboMultiplier(next.streak))
.round();
}
return ScoreDelta(points: points, combo: next);
}