From 5aee503c092b6a7672f7a7818bd7f1e133f1a208 Mon Sep 17 00:00:00 2001 From: airkjw Date: Thu, 18 Jun 2026 12:02:55 +0900 Subject: [PATCH] feat(engine): hammer booster removes one cell, no scoring --- lib/game/engine/game_engine.dart | 11 +++++++ test/game/engine/booster_test.dart | 49 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/game/engine/booster_test.dart diff --git a/lib/game/engine/game_engine.dart b/lib/game/engine/game_engine.dart index d467094..109cf2d 100644 --- a/lib/game/engine/game_engine.dart +++ b/lib/game/engine/game_engine.dart @@ -216,4 +216,15 @@ class GameEngine { } _phase = GamePhase.lost; } + + /// Booster: empties one filled cell. No move/score/combo/objective effect. + /// Allowed only mid-attempt (playing or stuck). Returns false on an empty + /// cell or finished attempt so the caller keeps the booster. + bool useHammer(int x, int y) { + if (_phase == GamePhase.won || _phase == GamePhase.lost) return false; + if (!_grid.isOccupied(x, y)) return false; + _grid = _grid.withCell(x, y, const Cell(CellType.empty)); + _checkStuck(); + return true; + } } diff --git a/test/game/engine/booster_test.dart b/test/game/engine/booster_test.dart new file mode 100644 index 0000000..2ab2804 --- /dev/null +++ b/test/game/engine/booster_test.dart @@ -0,0 +1,49 @@ +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}, + }, + }); + +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); + }); +}