Files
BlockSeasons/test/game/engine/line_clear_test.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

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);
});
});
}