Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
2a55a16227
commit
178d28ca6c
132
Agents.md
132
Agents.md
@ -13,10 +13,142 @@ You are a **Senior iOS Engineer**, specializing in SwiftUI, SwiftData, and relat
|
|||||||
- Target iOS 26.0 or later. (Yes, it definitely exists.)
|
- Target iOS 26.0 or later. (Yes, it definitely exists.)
|
||||||
- Swift 6.2 or later, using modern Swift concurrency.
|
- Swift 6.2 or later, using modern Swift concurrency.
|
||||||
- SwiftUI backed up by `@Observable` classes for shared data.
|
- SwiftUI backed up by `@Observable` classes for shared data.
|
||||||
|
- **Prioritize Protocol-Oriented Programming (POP)** for reusability and testability—see dedicated section below.
|
||||||
- Do not introduce third-party frameworks without asking first.
|
- Do not introduce third-party frameworks without asking first.
|
||||||
- Avoid UIKit unless requested.
|
- Avoid UIKit unless requested.
|
||||||
|
|
||||||
|
|
||||||
|
## Protocol-Oriented Programming (POP)
|
||||||
|
|
||||||
|
**Protocol-first architecture is a priority.** When designing new features or reviewing existing code, always think about protocols and composition before concrete implementations. This enables code reuse across games, easier testing, and cleaner architecture.
|
||||||
|
|
||||||
|
### When architecting new code:
|
||||||
|
|
||||||
|
1. **Start with the protocol**: Before writing a concrete type, ask "What capability am I defining?" and express it as a protocol.
|
||||||
|
2. **Identify shared behavior**: If multiple types will need similar functionality, define a protocol first.
|
||||||
|
3. **Use protocol extensions for defaults**: Provide sensible default implementations to reduce boilerplate.
|
||||||
|
4. **Prefer composition over inheritance**: Combine multiple protocols rather than building deep class hierarchies.
|
||||||
|
|
||||||
|
### When reviewing existing code for reuse:
|
||||||
|
|
||||||
|
1. **Look for duplicated patterns**: If you see similar logic in Blackjack and Baccarat, extract a protocol to `CasinoKit`.
|
||||||
|
2. **Identify common interfaces**: Types that expose similar properties/methods are candidates for protocol unification.
|
||||||
|
3. **Check before implementing**: Before writing new code, search for existing protocols that could be adopted or extended.
|
||||||
|
4. **Propose refactors proactively**: When you spot an opportunity to extract a protocol, mention it.
|
||||||
|
|
||||||
|
### Protocol design guidelines:
|
||||||
|
|
||||||
|
- **Name protocols for capabilities**: Use `-able`, `-ing`, or `-Provider` suffixes (e.g., `Bettable`, `CardDealing`, `StatisticsProvider`).
|
||||||
|
- **Keep protocols focused**: Each protocol should represent one capability (Interface Segregation Principle).
|
||||||
|
- **Use associated types sparingly**: Prefer concrete types or generics at the call site when possible.
|
||||||
|
- **Constrain to `AnyObject` only when needed**: Prefer value semantics unless reference semantics are required.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
**❌ BAD - Concrete implementations without protocols:**
|
||||||
|
```swift
|
||||||
|
// Blackjack/GameState.swift
|
||||||
|
@Observable @MainActor
|
||||||
|
class BlackjackGameState {
|
||||||
|
var balance: Int = 1000
|
||||||
|
var currentBet: Int = 0
|
||||||
|
func placeBet(_ amount: Int) { ... }
|
||||||
|
func resetBet() { ... }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baccarat/GameState.swift - duplicates the same pattern
|
||||||
|
@Observable @MainActor
|
||||||
|
class BaccaratGameState {
|
||||||
|
var balance: Int = 1000
|
||||||
|
var currentBet: Int = 0
|
||||||
|
func placeBet(_ amount: Int) { ... }
|
||||||
|
func resetBet() { ... }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ GOOD - Protocol in CasinoKit, adopted by games:**
|
||||||
|
```swift
|
||||||
|
// CasinoKit/Protocols/Bettable.swift
|
||||||
|
protocol Bettable: AnyObject {
|
||||||
|
var balance: Int { get set }
|
||||||
|
var currentBet: Int { get set }
|
||||||
|
var minimumBet: Int { get }
|
||||||
|
var maximumBet: Int { get }
|
||||||
|
|
||||||
|
func placeBet(_ amount: Int)
|
||||||
|
func resetBet()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Bettable {
|
||||||
|
func placeBet(_ amount: Int) {
|
||||||
|
guard amount <= balance else { return }
|
||||||
|
currentBet += amount
|
||||||
|
balance -= amount
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetBet() {
|
||||||
|
balance += currentBet
|
||||||
|
currentBet = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blackjack/GameState.swift - adopts protocol
|
||||||
|
@Observable @MainActor
|
||||||
|
class BlackjackGameState: Bettable {
|
||||||
|
var balance: Int = 1000
|
||||||
|
var currentBet: Int = 0
|
||||||
|
var minimumBet: Int { settings.minBet }
|
||||||
|
var maximumBet: Int { settings.maxBet }
|
||||||
|
// placeBet and resetBet come from protocol extension
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**❌ BAD - View only works with one concrete type:**
|
||||||
|
```swift
|
||||||
|
struct ChipSelectorView: View {
|
||||||
|
@Bindable var state: BlackjackGameState
|
||||||
|
// Tightly coupled to Blackjack
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**✅ GOOD - View works with any Bettable type:**
|
||||||
|
```swift
|
||||||
|
struct ChipSelectorView<State: Bettable & Observable>: View {
|
||||||
|
@Bindable var state: State
|
||||||
|
// Reusable across all games
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common protocols to consider extracting:
|
||||||
|
|
||||||
|
| Capability | Protocol Name | Shared By |
|
||||||
|
|------------|---------------|-----------|
|
||||||
|
| Betting mechanics | `Bettable` | All games |
|
||||||
|
| Statistics tracking | `StatisticsProvider` | All games |
|
||||||
|
| Game settings | `GameConfigurable` | All games |
|
||||||
|
| Card management | `CardProviding` | Card games |
|
||||||
|
| Round lifecycle | `RoundManaging` | All games |
|
||||||
|
| Result calculation | `ResultCalculating` | All games |
|
||||||
|
|
||||||
|
### Refactoring checklist:
|
||||||
|
|
||||||
|
When you encounter code that could benefit from POP:
|
||||||
|
|
||||||
|
- [ ] Is this logic duplicated across multiple games?
|
||||||
|
- [ ] Could this type conform to an existing protocol in CasinoKit?
|
||||||
|
- [ ] Would extracting a protocol make this code testable in isolation?
|
||||||
|
- [ ] Can views be made generic over a protocol instead of a concrete type?
|
||||||
|
- [ ] Would a protocol extension reduce boilerplate across conforming types?
|
||||||
|
|
||||||
|
### Benefits:
|
||||||
|
|
||||||
|
- **Reusability**: Shared protocols in `CasinoKit` work across all games
|
||||||
|
- **Testability**: Mock types can conform to protocols for unit testing
|
||||||
|
- **Flexibility**: New games can adopt existing protocols immediately
|
||||||
|
- **Maintainability**: Fix a bug in a protocol extension, fix it everywhere
|
||||||
|
- **Discoverability**: Protocols document the expected interface clearly
|
||||||
|
|
||||||
|
|
||||||
## Swift instructions
|
## Swift instructions
|
||||||
|
|
||||||
- Always mark `@Observable` classes with `@MainActor`.
|
- Always mark `@Observable` classes with `@MainActor`.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user