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

TechnologyVersionPurpose
Swift5.9+Primary language — value types for models, classes for state
SwiftUIiOS 17+Declarative UI with @StateObject / @Published
FoundationiOS 17+UUID, standard library
AudioToolboxiOS 17+System sound effects (no bundled audio assets)
UserDefaults / @AppStorageBuilt-inSettings persistence with poker. prefix
XCTestBuilt-inUnit tests — 80 tests across 5 files
XcodeLatestBuild 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: SoundManager singleton 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

DecisionRationaleOutcome
Value types for modelsPredictable state, easy testingGood
Single GameState classCentralized state for SwiftUI bindingGood
No external dependenciesSimplicity, no package managementGood
HandRank Int rawValueNatural Comparable orderingGood
Cards in significance orderCorrect hand comparison (triplet before pair)Good
determineWinners returns arraySplit pot supportGood
hasActedThisRound on PlayerPreflop big blind option handling — set before switchGood
Remainder chips to first winnerArbitrary but consistent tiebreakerGood
HandStrength enum (weak/medium/strong)Simple AI decision categoriesGood
0.7s AI turn delayVisual feedback without feeling slowGood
Human at index 0Consistent bottom position in UIGood
Zero/negative raises rejectedValidation at processAction levelGood
minRaise = currentBet + bigBlindEnsures valid minimum raiseGood
AI all-in when raise > chipsChip-aware betting decisionsGood
@AppStorage for settingsSimple key-value pairs — no CoreData overheadGood
SoundManager singleton with AudioToolboxSystem sounds are lightweight, no audio files to bundleGood
CardSizing via SwiftUI EnvironmentResponsive sizing propagated through view hierarchyGood
Enum-backed theming (TableColor, CardBack)Type-safe, CaseIterable for picker UIGood

Patterns Established

PatternDescriptionReusable
@AppStorage with enum rawValue bridgePersisting enum settings via String rawValueYes
EnvironmentKey for layout metricsCardSizing injected via SwiftUI environment — avoids passing sizing through every viewYes
SystemSoundID constants enumNamed system sound mappings (reference: iphonedev.wiki/AudioServices)Yes
Value types for game modelsCard, Hand, Deck as structsYes
Centralized GameStateSingle ObservableObject as source of truth for SwiftUIYes

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:

MetricStatusNotes
CI/CDPassingXcode builds clean
DependenciesCurrentPure Swift, no external deps
Test Coverage>80%80 tests across 5 files — hand evaluation, comparison, betting, showdown, AI

Recent milestones:

DateUpdate
2026-02-15Compliance audit: corrected test count from 87 to 80 (verified across 5 test files)
2026-01-12Completed v1.1 Polish & Stability
2026-01-11Shipped v1.0 MVP

Technical Debt

ItemPriorityNotes
SoundManager uses AudioToolbox (no volume control)MediumVolume slider in settings is non-functional — migrate to AVAudioPlayer
No side pot logic for all-in playersHighCurrently one main pot only
No game state persistence between sessionsMediumRestart clears game
AI doesn’t bluffMediumPredictable behavior

Lessons Learned

  • AudioToolbox system sounds ignore app volume — AudioServicesPlaySystemSound responds to system volume only. Migrate to AVAudioPlayer for volume control.
  • @AppStorage doesn’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).
  • hasActedThisRound must be set before the action switch, not after — preflop big blind option handling depends on this ordering.