Build an iOS Farkle dice game scorekeeper using SwiftUI, SwiftData, Hexagonal Architecture, and strict TDD with 100% line/branch coverage. Includes configurable house rules and score input pad UX.
Build an iOS app for keeping score of Farkle dice game with configurable house rules, strict TDD practices, and hexagonal architecture.
Guides you through developing a production-quality iOS Farkle scorekeeper app using:
The app follows Hexagonal Architecture with dependencies pointing inward:
```
ADAPTERS (Outside) → PORTS (Interfaces) → DOMAIN (Core)
```
| Combination | Dice | Points | Notes |
|-------------|------|--------|-------|
| Single 1 | 1 | 100 | |
| Single 5 | 1 | 50 | |
| 3 of a Kind | 3 | 100×die (1s=1000) | |
| 4 of a Kind | 4 | 2000 | Flat value |
| 5 of a Kind | 5 | 3000 | Flat value |
| 6 of a Kind (2-6) | 6 | 10000 | |
| 6 of a Kind (1s) | 6 | INSTANT WIN | |
| Full House (3+2) | 5 | 3-of-kind + 250 | |
| Full Mansion (4+2) | 6 | 2250 | |
| Three Pairs | 6 | 1500 | |
| Two Triplets | 6 | 2500 | |
| Small Straight | 5 | 1500 | 1-5 or 2-6 |
| Large Straight | 6 | 1500 | 1-2-3-4-5-6 |
| Six-Dice Farkle | 6 | 500 | First roll only |
Follow Uncle Bob's 3 Rules of TDD strictly:
1. **Write NO production code unless it makes a failing test pass**
2. **Write NO MORE test code than sufficient to fail** (compilation errors count as failure)
3. **Write NO MORE production code than sufficient to pass the one failing test**
4. **Refactor ONLY when tests are green**
1. **Create `ScoringCombination` entity** (TDD cycle)
- Write failing test for simple scoring (single 1 = 100 points)
- Implement minimum code to pass
- Write failing test for single 5 = 50 points
- Implement, refactor
- Continue for all combinations in the scoring table
- Test edge cases: invalid combinations, empty rolls
2. **Create `Turn` entity** (TDD cycle)
- Write failing test for adding a scoring combination
- Implement
- Write failing test for calculating turn total
- Implement
- Write failing test for tracking dice remaining
- Implement
- Test Hot Dice detection (all 6 dice used)
3. **Create `Game` entity** (TDD cycle)
- Write failing test for adding players
- Implement
- Write failing test for starting a turn
- Implement
- Write failing test for banking points
- Implement
- Write failing test for farkle handling
- Test win conditions
4. **Create `Player` entity** (TDD cycle)
- Write failing test for player creation
- Implement
- Write failing test for score tracking
- Implement
5. **Define `GameRepositoryProtocol`** (TDD cycle)
- Write failing test with mock repository
- Define protocol methods: save, load, delete
- Implement mock for testing
6. **Define `ScoringConfigProtocol`** (TDD cycle)
- Write failing test with mock config
- Define protocol for house rules
- Implement mock for testing
7. **Create `AddScoreUseCase`** (TDD cycle)
- Write failing test for adding valid score
- Implement
- Write failing test for invalid score rejection
- Implement
- Test dice remaining calculation
- Test Hot Dice triggering
8. **Create `BankPointsUseCase`** (TDD cycle)
- Write failing test for valid banking (1-2 dice remaining)
- Implement
- Write failing test for invalid banking (3+ dice or Hot Dice)
- Implement
- Test score transfer to player total
9. **Create `FarkleUseCase`** (TDD cycle)
- Write failing test for farkle handling
- Implement
- Write failing test for six-dice farkle bonus (500 points)
- Implement
- Test turn ending
10. **Create `GameRecord` SwiftData model** (Integration tests)
- Write failing integration test for saving game
- Implement `@Model` class
- Write failing test for loading game
- Implement
- Test persistence across app launches
11. **Create `SwiftDataGameRepository`** (Integration tests)
- Write failing test for repository saving
- Implement adapter conforming to `GameRepositoryProtocol`
- Write failing test for loading
- Implement
- Test error handling
12. **Create `UserDefaultsScoringConfig`** (Integration tests)
- Write failing test for saving house rules
- Implement adapter conforming to `ScoringConfigProtocol`
- Write failing test for loading
- Implement
13. **Create `GameViewModel`** (Medium tests)
- Write failing test for score input handling
- Implement using use cases via ports
- Write failing test for banking action
- Implement
- Write failing test for farkle action
- Implement
- Test state publishing (@Published properties)
14. **Create `ScoreInputPadView`** (UI tests)
- Write failing UI test for button taps
- Implement SwiftUI button grid:
- 50 (5), 100 (1), 150 (5+1), 200 (1+1)
- 3 of a Kind, 4 of a Kind
- Straight, 3 Pairs, 2 Trips
- Write failing test for score display update
- Implement turn score binding
- Test button enable/disable logic
15. **Create `GameView`** (UI tests)
- Write failing UI test for player list display
- Implement player scores list
- Write failing test for current turn display
- Implement
- Write failing test for Farkle/Bank buttons
- Implement
- Test navigation and state
16. **Create `SetupView`** (UI tests)
- Write failing UI test for adding players
- Implement player name input
- Write failing test for starting game
- Implement navigation to GameView
- Test validation (min 2 players)
17. **Create `FarkleScorekeeperApp`**
- Wire up SwiftData model container
- Inject real adapters (SwiftDataGameRepository, UserDefaultsScoringConfig)
- Set up navigation
18. **Run full test suite**
```bash
xcodebuild test -scheme FarkleScorekeeper \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-enableCodeCoverage YES
```
19. **Verify coverage requirements**
- Check Xcode coverage report: 100% line coverage
- Check Xcode coverage report: 100% branch coverage
- Run mutation testing tool (if available): 90% mutation score
20. **Add stats view** (TDD cycle)
- Write failing test for player stats calculation
- Implement StatsView
- Write failing test for game history
- Implement
21. **Add house rules configuration screen** (TDD cycle)
- Write failing test for toggling Hot Dice rule
- Implement settings view
- Write failing test for banking restriction rules
- Implement
```
FarkleScorekeeper/
├── App/
│ └── FarkleScorekeeperApp.swift
├── Domain/
│ ├── Entities/
│ │ ├── ScoringCombination.swift
│ │ ├── Turn.swift
│ │ ├── Game.swift
│ │ └── Player.swift
│ ├── Ports/
│ │ ├── GameRepositoryProtocol.swift
│ │ └── ScoringConfigProtocol.swift
│ └── UseCases/
│ ├── AddScoreUseCase.swift
│ ├── BankPointsUseCase.swift
│ └── FarkleUseCase.swift
├── Data/
│ ├── Models/
│ │ ├── GameRecord.swift
│ │ └── PlayerStats.swift
│ └── Adapters/
│ ├── SwiftDataGameRepository.swift
│ └── UserDefaultsScoringConfig.swift
├── Presentation/
│ ├── Game/
│ │ ├── GameView.swift
│ │ ├── GameViewModel.swift
│ │ └── ScoreInputPadView.swift
│ ├── Setup/
│ │ └── SetupView.swift
│ └── Stats/
│ └── StatsView.swift
└── Resources/
└── Assets.xcassets
```
```bash
xcodebuild -scheme FarkleScorekeeper \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro'
xcodebuild test -scheme FarkleScorekeeper \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-enableCodeCoverage YES
xcodegen generate
```
1. **Minimum iOS version**: iOS 17+
2. **No third-party dependencies** for core domain logic
3. **All domain entities must be testable without XCTest mocking frameworks** (use protocol-based dependency injection)
4. **Every feature MUST be test-driven** - no exceptions
5. **Refactor only when all tests pass** (green phase)
6. **UI tests should use accessibility identifiers** for reliable element selection
```swift
// 1. RED: Write failing test
func testSingleOneScoreIs100Points() {
let combination = ScoringCombination(dice: [1])
XCTAssertEqual(combination.points, 100) // FAILS - type doesn't exist
}
// 2. GREEN: Minimum code to pass
struct ScoringCombination {
let dice: [Int]
var points: Int { 100 }
}
// 3. RED: Write next failing test
func testSingleFiveScoreIs50Points() {
let combination = ScoringCombination(dice: [5])
XCTAssertEqual(combination.points, 50) // FAILS
}
// 4. GREEN: Implement logic
struct ScoringCombination {
let dice: [Int]
var points: Int {
if dice == [1] { return 100 }
if dice == [5] { return 50 }
return 0
}
}
// 5. REFACTOR: Improve when green
struct ScoringCombination {
let dice: [Int]
var points: Int {
switch dice.first {
case 1: return 100
case 5: return 50
default: return 0
}
}
}
```
Leave a review
No reviews yet. Be the first to review this skill!
# Download SKILL.md from killerskills.ai/api/skills/ios-farkle-scorekeeper-tdd/raw