import 'package:block_seasons/game/engine/game_engine.dart'; import 'package:block_seasons/game/models/stage.dart'; import 'package:flutter_test/flutter_test.dart'; StageConfig _stage() => StageConfig.fromJson({ 'id': 'b_test', 'seed': 1, 'moveLimit': 20, 'preset': [ {'x': 0, 'y': 0, 't': 'filled', 'c': 3}, {'x': 1, 'y': 0, 't': 'filled', 'c': 4}, ], 'objectives': [ {'type': 'reachScore', 'target': 100000}, ], 'stars': { 'two': {'movesLeft': 5}, 'three': {'movesLeft': 10}, }, }); const _stars = { 'two': {'movesLeft': 5}, 'three': {'movesLeft': 10}, }; // Two gems plus one plain filled cell; objective is to clear both gems. StageConfig _gemStage() => StageConfig.fromJson({ 'id': 'b_gem', 'seed': 1, 'moveLimit': 20, 'preset': [ {'x': 0, 'y': 0, 't': 'gem'}, {'x': 3, 'y': 3, 't': 'gem'}, {'x': 5, 'y': 5, 't': 'filled', 'c': 2}, ], 'objectives': [ {'type': 'clearGems', 'count': 2}, ], 'stars': _stars, }); // Two gems sitting on row 2; objective is to clear both gems. StageConfig _gemRowStage() => StageConfig.fromJson({ 'id': 'b_gemrow', 'seed': 1, 'moveLimit': 20, 'preset': [ {'x': 1, 'y': 2, 't': 'gem'}, {'x': 4, 'y': 2, 't': 'gem'}, ], 'objectives': [ {'type': 'clearGems', 'count': 2}, ], 'stars': _stars, }); // Objective is to clear one line. StageConfig _lineStage() => StageConfig.fromJson({ 'id': 'b_line', 'seed': 1, 'moveLimit': 20, 'preset': [ {'x': 0, 'y': 0, 't': 'filled', 'c': 1}, ], 'objectives': [ {'type': 'clearLines', 'count': 1}, ], 'stars': _stars, }); void main() { test('useHammer empties a filled cell without scoring or spending a move', () { final e = GameEngine(_stage()); final score0 = e.score; final moves0 = e.movesUsed; expect(e.grid.isOccupied(0, 0), isTrue); final ok = e.useHammer(0, 0); expect(ok, isTrue); expect(e.grid.isOccupied(0, 0), isFalse); expect(e.score, score0, reason: 'no points from a booster'); expect(e.movesUsed, moves0, reason: 'no move spent'); }); test('useHammer on an empty cell returns false and changes nothing', () { final e = GameEngine(_stage()); expect(e.grid.isOccupied(5, 5), isFalse); expect(e.useHammer(5, 5), isFalse); expect(e.grid.isOccupied(5, 5), isFalse); }); test('useHammer is rejected once the stage is won or lost', () { final e = GameEngine(_stage()); e.declineAndLose(); expect(e.useHammer(0, 0), isFalse); }); test('useShuffle replaces the tray without spending a move or scoring', () { final e = GameEngine(_stage()); final before = List.of(e.tray); final score0 = e.score; final moves0 = e.movesUsed; final ok = e.useShuffle(); expect(ok, isTrue); expect(e.tray.length, before.length); expect(e.score, score0); expect(e.movesUsed, moves0); }); test('useShuffle is rejected after the attempt finishes', () { final e = GameEngine(_stage()); e.declineAndLose(); expect(e.useShuffle(), isFalse); }); test('useLineBomb(row:) empties that row, no scoring or move', () { final e = GameEngine(_stage()); // row 0 has cells at x=0,1 final score0 = e.score; final moves0 = e.movesUsed; final ok = e.useLineBomb(row: 0); expect(ok, isTrue); for (var x = 0; x < 8; x++) { expect(e.grid.isOccupied(x, 0), isFalse, reason: 'col $x'); } expect(e.score, score0); expect(e.movesUsed, moves0); }); test('useLineBomb(col:) empties that column', () { final e = GameEngine(_stage()); // (0,0) filled expect(e.useLineBomb(col: 0), isTrue); for (var y = 0; y < 8; y++) { expect(e.grid.isOccupied(0, y), isFalse, reason: 'row $y'); } }); test('useLineBomb requires exactly one of row/col', () { final e = GameEngine(_stage()); expect(e.useLineBomb(), isFalse); expect(e.useLineBomb(row: 0, col: 0), isFalse); }); test('useLineBomb is rejected after the attempt finishes', () { final e = GameEngine(_stage()); e.declineAndLose(); expect(e.useLineBomb(row: 0), isFalse); }); // --- Boosters count toward objectives (owner decision 2026-06-18) --- test('useHammer on a gem counts toward the gem objective and wins the stage ' 'when it clears the last one', () { final e = GameEngine(_gemStage()); // 2 gems, objective clearGems(2) expect(e.objectives.first.current, 0); expect(e.useHammer(0, 0), isTrue); // first gem expect(e.objectives.first.current, 1); expect(e.phase, GamePhase.playing); expect(e.useHammer(3, 3), isTrue); // last gem -> objective complete expect(e.objectives.first.current, 2); expect(e.phase, GamePhase.won, reason: 'clearing the last gem wins'); }); test('useHammer on a plain filled cell does not change the gem objective', () { final e = GameEngine(_gemStage()); expect(e.useHammer(5, 5), isTrue); // a non-gem filled cell expect(e.objectives.first.current, 0); expect(e.phase, GamePhase.playing); }); test('useLineBomb counts the gems in the cleared line toward the objective', () { final e = GameEngine(_gemRowStage()); // 2 gems on row 2, clearGems(2) expect(e.useLineBomb(row: 2), isTrue); expect(e.objectives.first.current, 2); expect(e.phase, GamePhase.won); }); test('useLineBomb counts as a cleared line toward the line objective', () { final e = GameEngine(_lineStage()); // clearLines(1) expect(e.objectives.first.current, 0); expect(e.useLineBomb(row: 0), isTrue); expect(e.objectives.first.current, 1); expect(e.phase, GamePhase.won); }); }