Merge pull request GH-2 from gh/bowling-game

bowling game
This commit is contained in:
fiatcode 2026-02-10 12:48:01 +07:00
commit 561e85217c
2 changed files with 136 additions and 0 deletions

58
lib/bowling_game.dart Normal file
View file

@ -0,0 +1,58 @@
class BowlingGame {
static const _totalFrames = 10;
static const _allPins = 10;
static const _rollsInNormalFrame = 2;
static const _rollsInStrike = 1;
final List<int> _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;
}
}

View file

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