← poker-tutor
import Foundation
/// A chip you can pick up and put in the pot.
public enum Chip: Int, CaseIterable, Sendable, Identifiable, Comparable {
case twentyFive = 25
case fifty = 50
case hundred = 100
case fiveHundred = 500
case thousand = 1_000
case fiveThousand = 5_000
case tenThousand = 10_000
public var value: Int { rawValue }
public var id: Int { rawValue }
/// What is printed on the face. Anything from a thousand up is written the way it is said.
public var label: String {
value >= 1_000 ? "\(value / 1_000)K" : "\(value)"
}
public static func < (a: Chip, b: Chip) -> Bool { a.rawValue < b.rawValue }
}
/// Keeps every bet at the table to a whole number of chips.
///
/// Nobody at a real table bets 437. They push out four hundreds, a twenty-five and a... no, they
/// push out 450, because that is what the chips in front of them make. The opponents round the
/// same way the player's chip tray does, so one side of the table cannot bet in denominations the
/// other side has never seen.
public enum ChipRounding {
/// The smallest chip still worth having at this blind level.
///
/// Real tournaments colour the small chips up and take them off the table as the blinds climb,
/// which is why late-stage bets are round thousands rather than round twenty-fives. This is the
/// same idea in one line: the smallest chip in play is the largest one that still divides a
/// small blind sensibly.
public static func smallestChip(bigBlind: Int) -> Chip {
let ceiling = max(Chip.twentyFive.value, bigBlind / 4)
return Chip.allCases.last { $0.value <= ceiling } ?? .twentyFive
}
/// Rounds a bet to whole chips without ever making it illegal.
///
/// Legality wins over tidiness every time: a bet rounded below the minimum raise would be
/// rejected by the engine and quietly turned into a check, which would change how the
/// opponents play rather than just how their bets look.
public static func round(_ amount: Int, bigBlind: Int, minimum: Int, maximum: Int) -> Int {
guard maximum > 0, minimum <= maximum else { return amount }
// All in is exact — a stack is whatever it is, and shoving it is always legal.
if amount >= maximum { return maximum }
let unit = smallestChip(bigBlind: bigBlind).value
var rounded = ((amount + unit / 2) / unit) * unit
if rounded < minimum {
// Round up to the first whole number of chips that clears the minimum.
rounded = ((minimum + unit - 1) / unit) * unit
}
if rounded > maximum { return maximum }
return max(rounded, minimum)
}
}