← poker-tutor
import Foundation
import PokerCore
/// The chips in front of the player, and what they are allowed to build with them.
///
/// Betting is done by picking chips up rather than dragging a slider: click the hundred twice and
/// the fifty once and you have bet 250. It is slower than a slider by a click or two and far more
/// deliberate, which is the point — a bet you had to count out is a bet you thought about.
public struct ChipTray: Sendable, Equatable {
/// The largest legal bet: everything the player has.
public let maximum: Int
/// The smallest legal raise. Chips cannot always hit it exactly, so anything at or above it
/// counts — a stack of chips that overshoots a strange minimum is still a legal bet.
public let minimum: Int
/// The denominations worth showing. A chip larger than the player's stack is a button that can
/// never be pressed.
public let offered: [Chip]
public init(minimum: Int, maximum: Int) {
self.minimum = minimum
self.maximum = maximum
self.offered = Chip.allCases.filter { $0.value <= maximum }
}
public init?(legal: LegalActions) {
guard legal.canBetOrRaise, legal.maxRaiseTo > 0 else { return nil }
self.init(minimum: legal.minRaiseTo, maximum: legal.maxRaiseTo)
}
/// Whether there is room in the stack for one more of these.
public func canAdd(_ chip: Chip, to amount: Int) -> Bool {
amount + chip.value <= maximum
}
public func adding(_ chip: Chip, to amount: Int) -> Int {
canAdd(chip, to: amount) ? amount + chip.value : amount
}
/// A bet is good to go once it is worth at least the minimum raise and no more than the stack.
public func isLegal(_ amount: Int) -> Bool {
amount >= minimum && amount <= maximum
}
/// What is still needed before the bet can be made, or nil once it is legal.
public func shortfall(_ amount: Int) -> Int? {
amount >= minimum ? nil : minimum - amount
}
/// The fewest chips that make the minimum legal raise, for the button that counts it out for
/// you. Greedy from the largest chip down, then topped up — chip values divide into each other,
/// so greedy is optimal here.
public func quickest(_ target: Int) -> [Chip] {
var remaining = min(max(target, 0), maximum)
var out: [Chip] = []
for chip in offered.sorted(by: >) {
while remaining >= chip.value {
out.append(chip)
remaining -= chip.value
}
}
// Chip values cannot always hit an odd minimum exactly, so round up with one more of the
// smallest chip rather than leaving the player one short of a legal bet.
if remaining > 0, let smallest = offered.first, total(out) + smallest.value <= maximum {
out.append(smallest)
}
return out
}
public func total(_ chips: [Chip]) -> Int {
chips.reduce(0) { $0 + $1.value }
}
}