0210c14858
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>
51 lines
1.6 KiB
Dart
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);
|
|
}
|