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>
84 lines
2.9 KiB
Dart
84 lines
2.9 KiB
Dart
import 'package:block_seasons/game/engine/placement.dart';
|
|
import 'package:block_seasons/game/models/cell.dart';
|
|
import 'package:block_seasons/game/models/grid.dart';
|
|
import 'package:block_seasons/game/models/piece.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
const mono = Piece(id: 'mono', colorId: 0, offsets: [(0, 0)]);
|
|
const dominoH = Piece(id: 'domino_h', colorId: 1, offsets: [(0, 0), (1, 0)]);
|
|
const square2 = Piece(
|
|
id: 'square2',
|
|
colorId: 2,
|
|
offsets: [(0, 0), (1, 0), (0, 1), (1, 1)],
|
|
);
|
|
|
|
void main() {
|
|
group('canPlace', () {
|
|
test('any piece fits anywhere legal on an empty grid', () {
|
|
final grid = GridState.empty();
|
|
expect(canPlace(grid, mono, 0, 0), isTrue);
|
|
expect(canPlace(grid, mono, 7, 7), isTrue);
|
|
expect(canPlace(grid, square2, 6, 6), isTrue);
|
|
});
|
|
|
|
test('rejects out-of-bounds placements', () {
|
|
final grid = GridState.empty();
|
|
expect(canPlace(grid, square2, 7, 7), isFalse);
|
|
expect(canPlace(grid, dominoH, 7, 0), isFalse);
|
|
expect(canPlace(grid, mono, -1, 0), isFalse);
|
|
expect(canPlace(grid, mono, 0, 8), isFalse);
|
|
});
|
|
|
|
test('rejects overlap with occupied cells', () {
|
|
final grid = GridState.empty()
|
|
.withCell(1, 0, const Cell(CellType.filled, colorId: 0));
|
|
expect(canPlace(grid, dominoH, 0, 0), isFalse);
|
|
expect(canPlace(grid, dominoH, 2, 0), isTrue);
|
|
});
|
|
});
|
|
|
|
group('place', () {
|
|
test('fills cells with the piece color and keeps original grid', () {
|
|
final grid = GridState.empty();
|
|
final next = place(grid, square2, 3, 3);
|
|
|
|
expect(grid.occupiedCount, 0);
|
|
expect(next.occupiedCount, 4);
|
|
for (final (dx, dy) in square2.offsets) {
|
|
expect(next.cellAt(3 + dx, 3 + dy).type, CellType.filled);
|
|
expect(next.cellAt(3 + dx, 3 + dy).colorId, square2.colorId);
|
|
}
|
|
});
|
|
});
|
|
|
|
group('anyPlacementExists', () {
|
|
test('true on an empty grid', () {
|
|
expect(anyPlacementExists(GridState.empty(), [square2]), isTrue);
|
|
});
|
|
|
|
test('detects when only a small piece fits', () {
|
|
// Fill everything except (7, 7).
|
|
var grid = GridState.empty();
|
|
for (var y = 0; y < GridState.size; y++) {
|
|
for (var x = 0; x < GridState.size; x++) {
|
|
if (x == 7 && y == 7) continue;
|
|
grid = grid.withCell(x, y, const Cell(CellType.filled, colorId: 0));
|
|
}
|
|
}
|
|
expect(anyPlacementExists(grid, [mono]), isTrue);
|
|
expect(anyPlacementExists(grid, [dominoH]), isFalse);
|
|
expect(anyPlacementExists(grid, [dominoH, mono]), isTrue);
|
|
});
|
|
|
|
test('false when the grid is completely full', () {
|
|
var grid = GridState.empty();
|
|
for (var y = 0; y < GridState.size; y++) {
|
|
for (var x = 0; x < GridState.size; x++) {
|
|
grid = grid.withCell(x, y, const Cell(CellType.filled, colorId: 0));
|
|
}
|
|
}
|
|
expect(anyPlacementExists(grid, [mono]), isFalse);
|
|
});
|
|
});
|
|
}
|