Poker
A full Texas Hold’em game for iOS 17+ with 2-5 AI opponents, complete hand evaluation, betting rounds, split pot support, audio feedback, and settings customization. ~5,108 lines of Swift across 21 source files. Zero third-party dependencies.
GitHub: irishrocker1125/Poker
Overview
Poker implements standard Texas Hold’em rules for iPhone and iPad. The human player competes against 1-4 AI opponents with all betting rounds (preflop → flop → turn → river → showdown), blind rotation, fold/check/call/raise/all-in actions, and accurate hand evaluation from high card through royal flush.
The app graduated from v1.0 (MVP), v1.1 (polish and stability), through v1.2 (settings and customization). v1.3 and v1.4 introduced audio (AudioToolbox) and betting UX improvements respectively. Active development continues.
Tech Stack
| Technology | Version | Purpose |
|---|---|---|
| Swift | 5.9+ | Primary language — value types for models, classes for state |
| SwiftUI | iOS 17+ | Declarative UI with @StateObject / @Published |
| Foundation | iOS 17+ | UUID, standard library |
| AudioToolbox | iOS 17+ | System sound effects (no bundled audio assets) |
| UserDefaults / @AppStorage | Built-in | Settings persistence with poker. prefix |
| XCTest | Built-in | Unit tests — 80 tests across 5 files |
| Xcode | Latest | Build system (Poker.xcodeproj, no SPM packages) |
No external packages. Pure Apple frameworks.
Architecture
MVVM with GameState as the central ObservableObject. Value types (structs) for all game models; reference types (classes) for state and services.
Game flow state machine:
Idle → Dealing Blinds → Preflop Betting → Flop → Flop Betting
→ Turn → Turn Betting → River → River Betting → Showdown → Idle
Layer breakdown:
- View layer:
GameView,TableView,PlayerView,CardView,BettingControlsView,SettingsView - State layer:
GameState(@ObservableObject+@Published),AppSettings(@AppStorage) - Model layer:
Card,Deck,Player,Hand,EvaluatedHand(structs);HandEvaluator,AIPlayer(static method enums) - Audio:
SoundManagersingleton using AudioToolbox system sound IDs - App entry:
PokerApp.swift → ContentView → GameView(5-zone layout)
Key enums: Suit, Rank, HandRank, BettingRound, PlayerAction, HandStrength, TableColor, CardBack
Key Decisions
| Decision | Rationale | Outcome |
|---|---|---|
| Value types for models | Predictable state, easy testing | Good |
Single GameState class | Centralized state for SwiftUI binding | Good |
| No external dependencies | Simplicity, no package management | Good |
HandRank Int rawValue | Natural Comparable ordering | Good |
| Cards in significance order | Correct hand comparison (triplet before pair) | Good |
determineWinners returns array | Split pot support | Good |
hasActedThisRound on Player | Preflop big blind option handling — set before switch | Good |
| Remainder chips to first winner | Arbitrary but consistent tiebreaker | Good |
HandStrength enum (weak/medium/strong) | Simple AI decision categories | Good |
| 0.7s AI turn delay | Visual feedback without feeling slow | Good |
| Human at index 0 | Consistent bottom position in UI | Good |
| Zero/negative raises rejected | Validation at processAction level | Good |
minRaise = currentBet + bigBlind | Ensures valid minimum raise | Good |
| AI all-in when raise > chips | Chip-aware betting decisions | Good |
@AppStorage for settings | Simple key-value pairs — no CoreData overhead | Good |
SoundManager singleton with AudioToolbox | System sounds are lightweight, no audio files to bundle | Good |
CardSizing via SwiftUI Environment | Responsive sizing propagated through view hierarchy | Good |
Enum-backed theming (TableColor, CardBack) | Type-safe, CaseIterable for picker UI | Good |
Patterns Established
| Pattern | Description | Reusable |
|---|---|---|
@AppStorage with enum rawValue bridge | Persisting enum settings via String rawValue | Yes |
EnvironmentKey for layout metrics | CardSizing injected via SwiftUI environment — avoids passing sizing through every view | Yes |
SystemSoundID constants enum | Named system sound mappings (reference: iphonedev.wiki/AudioServices) | Yes |
| Value types for game models | Card, Hand, Deck as structs | Yes |
Centralized GameState | Single ObservableObject as source of truth for SwiftUI | Yes |
See also: @AppStorage with Enum RawValue Bridge, EnvironmentKey for Layout Metrics, AudioService Singleton
Current Status
Version: v1.2 shipped. v1.3 (audio) complete. v1.4 (betting UX) in progress (~10% of current milestone).
Health:
| Metric | Status | Notes |
|---|---|---|
| CI/CD | Passing | Xcode builds clean |
| Dependencies | Current | Pure Swift, no external deps |
| Test Coverage | >80% | 80 tests across 5 files — hand evaluation, comparison, betting, showdown, AI |
Recent milestones:
| Date | Update |
|---|---|
| 2026-02-15 | Compliance audit: corrected test count from 87 to 80 (verified across 5 test files) |
| 2026-01-12 | Completed v1.1 Polish & Stability |
| 2026-01-11 | Shipped v1.0 MVP |
Technical Debt
| Item | Priority | Notes |
|---|---|---|
SoundManager uses AudioToolbox (no volume control) | Medium | Volume slider in settings is non-functional — migrate to AVAudioPlayer |
| No side pot logic for all-in players | High | Currently one main pot only |
| No game state persistence between sessions | Medium | Restart clears game |
| AI doesn’t bluff | Medium | Predictable behavior |
Lessons Learned
AudioToolboxsystem sounds ignore app volume —AudioServicesPlaySystemSoundresponds to system volume only. Migrate toAVAudioPlayerfor volume control.@AppStoragedoesn’t support enums directly — requires rawValue bridge with computed property. See @AppStorage with Enum RawValue Bridge.- Always verify test counts with
grep -c "func test"before reporting — status.md had overstated count (87 → actual 80). hasActedThisRoundmust be set before the action switch, not after — preflop big blind option handling depends on this ordering.
Related
- @AppStorage with Enum RawValue Bridge
- EnvironmentKey for Layout Metrics
- AudioService Singleton
- MadLibs — sibling iOS game app, similar MVVM architecture
- DiceRoller — sibling iOS app