diff --git a/lib/bowling_game.dart b/lib/bowling_game.dart new file mode 100644 index 0000000..0d7a54a --- /dev/null +++ b/lib/bowling_game.dart @@ -0,0 +1,58 @@ +class BowlingGame { + static const _totalFrames = 10; + static const _allPins = 10; + static const _rollsInNormalFrame = 2; + static const _rollsInStrike = 1; + + final List _rolls = []; + + void roll(int pins) => _rolls.add(pins); + + int score() { + int total = 0; + int rollIndex = 0; + + for (int frame = 0; frame < _totalFrames; frame++) { + if (_isStrike(rollIndex)) { + total += _strikeScore(rollIndex); + rollIndex += _rollsInStrike; + } else if (_isSpare(rollIndex)) { + total += _spareScore(rollIndex); + rollIndex += _rollsInNormalFrame; + } else { + total += _normalScore(rollIndex); + rollIndex += _rollsInNormalFrame; + } + } + + return total; + } + + int _strikeScore(int rollIndex) { + return _allPins + _nextTwoRollsBonus(rollIndex); + } + + int _spareScore(int rollIndex) { + return _allPins + _nextRollBonus(rollIndex); + } + + int _normalScore(int rollIndex) { + return _rolls[rollIndex] + _rolls[rollIndex + 1]; + } + + int _nextTwoRollsBonus(int rollIndex) { + return _rolls[rollIndex + 1] + _rolls[rollIndex + 2]; + } + + int _nextRollBonus(int rollIndex) { + return _rolls[rollIndex + 2]; + } + + bool _isSpare(int rollIndex) { + return _rolls[rollIndex] + _rolls[rollIndex + 1] == _allPins; + } + + bool _isStrike(int rollIndex) { + return _rolls[rollIndex] == _allPins; + } +} diff --git a/test/bowling_game_test.dart b/test/bowling_game_test.dart new file mode 100644 index 0000000..cc2f463 --- /dev/null +++ b/test/bowling_game_test.dart @@ -0,0 +1,78 @@ +import 'package:tdd_katas/bowling_game.dart'; +import 'package:test/test.dart'; + +void main() { + group('Bowling Game Scoring', () { + late BowlingGame game; + + setUp(() { + game = BowlingGame(); + }); + + void rollMany(int times, int pins) { + for (int i = 0; i < times; i++) { + game.roll(pins); + } + } + + group('Basic Scoring', () { + test('gutter game - no pins knocked', () { + rollMany(20, 0); + expect(game.score(), 0); + }); + + test('all ones - simple addition', () { + rollMany(20, 1); + expect(game.score(), 20); + }); + }); + + group('Spare Bonus (next 1 roll)', () { + test('one spare in first frame', () { + game.roll(5); + game.roll(5); // spare + game.roll(3); // bonus for spare + rollMany(17, 0); + + expect(game.score(), 16); // 10 + 3 (bonus) + 3 + }); + + test('all spares with 5 pins each', () { + rollMany(21, 5); // 10 frames of 5,5 + 1 bonus roll + expect(game.score(), 150); + }); + }); + + group('Strike Bonus (next 2 rolls)', () { + test('one strike in first frame', () { + game.roll(10); // Strike! + game.roll(3); + game.roll(4); // Next 2 rolls are bonus + rollMany(16, 0); + + expect(game.score(), 24); // 10 + 3 + 4 (bonus) + 7 + }); + + test('perfect game - twelve consecutive strikes', () { + rollMany(12, 10); + expect(game.score(), 300); + }); + }); + + group('Complex Scenarios', () { + test('combination of strikes, spares, and normal frames', () { + game.roll(10); // Frame 1: Strike + game.roll(5); + game.roll(5); // Frame 2: Spare + game.roll(7); + game.roll(2); // Frame 3: Normal + rollMany(15, 0); + + // Frame 1: 10 + 5 + 5 = 20 + // Frame 2: 10 + 7 = 17 + // Frame 3: 7 + 2 = 9 + expect(game.score(), 46); + }); + }); + }); +}