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>
94 lines
3.3 KiB
Dart
94 lines
3.3 KiB
Dart
import 'package:block_seasons/game/engine/line_clear.dart';
|
|
import 'package:block_seasons/game/models/cell.dart';
|
|
import 'package:block_seasons/game/models/grid.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
GridState _fillRow(GridState grid, int y, {Set<int>? skip}) {
|
|
for (var x = 0; x < GridState.size; x++) {
|
|
if (skip?.contains(x) ?? false) continue;
|
|
grid = grid.withCell(x, y, const Cell(CellType.filled, colorId: 0));
|
|
}
|
|
return grid;
|
|
}
|
|
|
|
GridState _fillCol(GridState grid, int x, {Set<int>? skip}) {
|
|
for (var y = 0; y < GridState.size; y++) {
|
|
if (skip?.contains(y) ?? false) continue;
|
|
grid = grid.withCell(x, y, const Cell(CellType.filled, colorId: 0));
|
|
}
|
|
return grid;
|
|
}
|
|
|
|
void main() {
|
|
group('detectAndClear', () {
|
|
test('no full lines leaves the grid untouched', () {
|
|
final grid = _fillRow(GridState.empty(), 3, skip: {5});
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, isEmpty);
|
|
expect(result.clearedCols, isEmpty);
|
|
expect(result.linesCleared, 0);
|
|
expect(result.gemsCleared, 0);
|
|
expect(result.grid.occupiedCount, grid.occupiedCount);
|
|
});
|
|
|
|
test('clears a single full row', () {
|
|
final grid = _fillRow(GridState.empty(), 2);
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, [2]);
|
|
expect(result.clearedCols, isEmpty);
|
|
expect(result.linesCleared, 1);
|
|
expect(result.grid.occupiedCount, 0);
|
|
});
|
|
|
|
test('clears a single full column', () {
|
|
final grid = _fillCol(GridState.empty(), 6);
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, isEmpty);
|
|
expect(result.clearedCols, [6]);
|
|
expect(result.grid.occupiedCount, 0);
|
|
});
|
|
|
|
test('clears two rows at once', () {
|
|
var grid = _fillRow(GridState.empty(), 0);
|
|
grid = _fillRow(grid, 7);
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, [0, 7]);
|
|
expect(result.linesCleared, 2);
|
|
expect(result.grid.occupiedCount, 0);
|
|
});
|
|
|
|
test('row and column sharing a corner clear together', () {
|
|
var grid = _fillRow(GridState.empty(), 4);
|
|
grid = _fillCol(grid, 4);
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, [4]);
|
|
expect(result.clearedCols, [4]);
|
|
expect(result.linesCleared, 2);
|
|
// 8 + 8 - 1 shared cell were occupied; all gone now.
|
|
expect(result.grid.occupiedCount, 0);
|
|
});
|
|
|
|
test('counts gems in cleared lines, intersection gem only once', () {
|
|
var grid = GridState.empty();
|
|
grid = grid.withCell(4, 4, const Cell(CellType.gem));
|
|
grid = grid.withCell(0, 4, const Cell(CellType.gem));
|
|
grid = grid.withCell(4, 0, const Cell(CellType.gem));
|
|
grid = _fillRow(grid, 4, skip: {0, 4});
|
|
grid = _fillCol(grid, 4, skip: {0, 4});
|
|
final result = detectAndClear(grid);
|
|
expect(result.clearedRows, [4]);
|
|
expect(result.clearedCols, [4]);
|
|
// Gems at (0,4), (4,0), and the shared (4,4) -> exactly 3.
|
|
expect(result.gemsCleared, 3);
|
|
});
|
|
|
|
test('cells outside cleared lines survive', () {
|
|
var grid = _fillRow(GridState.empty(), 1);
|
|
grid = grid.withCell(3, 5, const Cell(CellType.filled, colorId: 2));
|
|
final result = detectAndClear(grid);
|
|
expect(result.grid.occupiedCount, 1);
|
|
expect(result.grid.cellAt(3, 5).colorId, 2);
|
|
});
|
|
});
|
|
}
|