Advertisement

← poker-tutor

Sources/pokerlab/Diagnose.swift

4.0 KB Download file
import Foundation
import PokerCore
import PokerAI

/// Plays one matchup and reports where the money actually goes, so a losing bot can be diagnosed
/// instead of guessed at.
func runDiagnose(hands: Int, seed: UInt64, stack: Int, samples: Int) {
    let a = Bot(id: "reg", personality: .solidRegular, skill: 90, seed: 1)
    let b = Bot(id: "maniac", personality: .maniac, skill: 15, seed: 2)
    let level = BlindLevel(smallBlind: 50, bigBlind: 100)
    let models = OpponentModels()
    var rng = SeededRandom(seed: seed &* 31 &+ 7)

    var net = 0
    var netByStreet: [Street: Int] = [:]
    var potsByStreet: [Street: Int] = [:]
    var shown = 0

    for hand in 0..<hands {
        let deckSeed = rng.next()
        let seatSeeds = [rng.next(), rng.next()]
        let button = hand % 2

        for pass in 0..<2 {
            let heroSeat = pass == 0 ? 0 : 1
            let ids = pass == 0 ? [a.id, b.id] : [b.id, a.id]
            let lineup = [
                Bot(id: ids[0], personality: ids[0] == a.id ? a.personality : b.personality,
                    skill: ids[0] == a.id ? a.skill : b.skill, seed: seatSeeds[0]),
                Bot(id: ids[1], personality: ids[1] == a.id ? a.personality : b.personality,
                    skill: ids[1] == a.id ? a.skill : b.skill, seed: seatSeeds[1]),
            ]
            let states = ids.enumerated().map { SeatState(seat: $0.offset, playerID: $0.element, stack: stack) }
            let engine = HandEngine(seats: states,
                                    config: HandConfig(level: level, buttonSeat: button, seed: deckSeed))
            BotTable(bots: lineup, seed: deckSeed, sharedModels: models).play(engine)

            let result = engine.result!
            let delta = result.net[heroSeat] ?? 0
            net += delta
            // Attribute the hand to the street on which it ended.
            netByStreet[engine.street, default: 0] += delta
            potsByStreet[engine.street, default: 0] += 1

            if hand > 60 && shown < samples && pass == 0 && abs(delta) > stack / 4 {
                shown += 1
                print("--- hand \(hand), reg \(delta > 0 ? "won" : "lost") \(abs(delta)) ---")
                print("  reg    \(engine.seats[0].hole.shorthand)")
                print("  maniac \(engine.seats[1].hole.shorthand)")
                print("  board  \(result.board.shorthand)")
                for action in engine.actions {
                    let who = engine.seats[action.seat].playerID
                    let forced = action.isForced ? " (blind)" : ""
                    print("    \(action.street.name.padding(toLength: 8, withPad: " ", startingAt: 0)) \(who.padding(toLength: 7, withPad: " ", startingAt: 0)) \(action.action)\(forced)")
                }
                print("")
            }
        }
    }

    print("Reg vs maniac, \(hands * 2) hands at \(stack / level.bigBlind) big blinds deep")
    print(String(format: "  net %+d chips  (%+.2f bb/100)", net, Double(net) / Double(level.bigBlind) / Double(hands * 2) * 100))
    print("")
    print("Where hands ended, and what the reg made there:")
    for street in [Street.preflop, .flop, .turn, .river] {
        let count = potsByStreet[street] ?? 0
        guard count > 0 else { continue }
        let chips = netByStreet[street] ?? 0
        print(String(format: "  %-8@ %5d hands  %+8d chips  (%+.2f bb per hand)",
                     street.name as NSString, count, chips,
                     Double(chips) / Double(level.bigBlind) / Double(count)))
    }
    print("")
    for id in [a.id, b.id] {
        let s = models[id]
        func pct(_ v: Double?) -> String { v.map { String(format: "%.0f%%", $0 * 100) } ?? "—" }
        print("  \(id.padding(toLength: 8, withPad: " ", startingAt: 0)) hands \(s.handsDealt)  vpip \(pct(s.vpip))  pfr \(pct(s.pfr))  3bet \(pct(s.threeBetRate))  fold-to-bet \(pct(s.foldToBet))  aggression \(s.aggressionFactor.map { String(format: "%.1f", $0) } ?? "—")")
    }
}