package main import ( "fmt" "os" "strings" tea "github.com/charmbracelet/bubbletea" ) func main() { loadWayfarerGalaxySeed() if len(os.Args) > 1 { switch os.Args[1] { case "-h", "--help": SetLanguage(LangRU) printHelp() return case "--help-en": SetLanguage(LangEN) printHelp() return case "-sim": runSimulation() return case "-sim-durak": runDurakSimulation() return case "-sim-blackjack": runBlackjackSimulation() return case "-sim-101": runOneOhOneSimulation() return case "-sim-1000": runThousandSimulation() return } } p := tea.NewProgram(NewAppModel()) if _, err := p.Run(); err != nil { fmt.Printf("ошибка TUI: %v\n", err) os.Exit(1) } } // printHelp выводит общее описание запуска и правила всех игр из // реестра на текущем языке (см. SetLanguage) — доступно без захода // в TUI, годится для чтения через `go-games-collection --help | // less` или на непривычной к TUI системе. func printHelp() { fmt.Println(asciiBanner) fmt.Println(T("app.subtitle")) fmt.Println() if CurrentLang() == LangEN { fmt.Println("USAGE") fmt.Println(" go-games-collection open the collection menu") fmt.Println(" go-games-collection -sim headless Tonk bot simulation (no TUI)") fmt.Println(" go-games-collection -sim-durak same, for Durak") fmt.Println(" go-games-collection -sim-blackjack same, for Blackjack") fmt.Println(" go-games-collection -sim-101 same, for 101") fmt.Println(" go-games-collection -sim-1000 same, for 1000 (Thousand)") fmt.Println(" go-games-collection --help show this help and rules in Russian") fmt.Println(" go-games-collection --help-en show this help and rules in English") } else { fmt.Println("ЗАПУСК") fmt.Println(" go-games-collection открыть меню коллекции") fmt.Println(" go-games-collection -sim headless-симуляция ботов Тонка (без TUI)") fmt.Println(" go-games-collection -sim-durak headless-симуляция ботов Дурака (без TUI)") fmt.Println(" go-games-collection -sim-blackjack headless-симуляция ботов Блэкджека (без TUI)") fmt.Println(" go-games-collection -sim-101 headless-симуляция ботов 101 (без TUI)") fmt.Println(" go-games-collection -sim-1000 headless-симуляция ботов 1000 (без TUI)") fmt.Println(" go-games-collection --help показать эту справку и правила на русском") fmt.Println(" go-games-collection --help-en показать эту справку и правила на английском") } fmt.Println() for _, g := range gameRegistry { name := g.Name() separator := strings.Repeat("=", len(name)+4) fmt.Println(separator) fmt.Printf(" %s\n", name) fmt.Println(separator) fmt.Println() fmt.Println(g.Rules()) } } // runSimulation — headless-прогон партии из 4 ботов разного // уровня, без TUI. Полезно для быстрой проверки баланса и // поведения ботов: `go run . -sim`. func runSimulation() { names, bots := newBotRoster(4) g := NewGame(names, -1) const maxTurns = 500 for turn := 0; turn < maxTurns && g.Phase != PhaseGameOver; turn++ { bot := bots[g.CurrentPlayer] if err := bot.PlayTurn(g); err != nil { fmt.Printf("ошибка на ходу %s: %v\n", g.Players[g.CurrentPlayer].Name, err) break } } if g.Result == nil { fmt.Println("партия не завершилась за отведённое число ходов") return } switch { case g.Result.DeadRound: fmt.Printf("раунд закончился вничью: колода и сброс исчерпаны (банк %d возвращён игрокам)\n", g.Result.Pot) case g.Result.TonkCall: fmt.Printf("%s объявил(а) Тонк и забрал(а) банк ×%d (%d)!\n", g.Players[g.Result.Winner].Name, g.TonkMultiplier, g.Result.Pot*g.TonkMultiplier) default: fmt.Printf("%s избавился(лась) от всех карт и забрал(а) банк (%d)!\n", g.Players[g.Result.Winner].Name, g.Result.Pot) } for i, p := range g.Players { fmt.Printf(" %-20s изменение капитала: %+d (итого: %d)\n", p.Name, g.Result.BalanceDeltas[i], p.Balance) } } // runDurakSimulation — headless-прогон партии Дурака ботами, без // TUI: `go run . -sim-durak`. Число игроков фиксировано (4) для // простоты; для проверки других раскладов используйте TUI. func runDurakSimulation() { names := newDurakBotNames(4) g := NewDurakGame(names, -1) bot := DurakBot{} const maxSteps = 10000 steps := 0 for ; steps < maxSteps && g.Phase != DurakPhaseGameOver; steps++ { if err := bot.PlayFullTurn(g); err != nil { fmt.Printf("ошибка на шаге %d: %v\n", steps, err) return } } if g.Result == nil { fmt.Println("партия не завершилась за отведённое число шагов") return } switch { case g.Result.Stalemate: fmt.Println("партия зашла в тупик и принудительно завершена вничью") case g.Result.NoLoser: fmt.Println("ничья: все избавились от карт одновременно") default: fmt.Printf("%s остался(лась) с картами на руках — дурак!\n", g.Players[g.Result.Loser].Name) } for _, p := range g.Players { status := "в игре" if p.Out { status = "отбился" } fmt.Printf(" %-10s карт: %-2d (%s)\n", p.Name, len(p.Hand), status) } } // runBlackjackSimulation — headless-прогон нескольких раундов // блэкджека ботами, без TUI: `go run . -sim-blackjack`. Число // игроков фиксировано (3) для простоты. func runBlackjackSimulation() { names, _ := newBotRoster(3) g := NewBlackjackGame(names, -1, 1000, 10) bot := BlackjackBot{} const rounds = 5 for round := 0; round < rounds; round++ { const maxSteps = 100 for steps := 0; steps < maxSteps && g.Phase != BJPhaseRoundOver; steps++ { if err := bot.PlayFullTurn(g); err != nil { fmt.Printf("ошибка на шаге %d: %v\n", steps, err) return } } if g.Result == nil { fmt.Println("раунд не завершился за отведённое число шагов") return } fmt.Printf("--- Раунд %d --- дилер: %d", round+1, g.Result.DealerTotal) if g.Result.DealerBusted { fmt.Print(" (перебор)") } fmt.Println() for i, p := range g.Players { r := g.Result.PlayerResults[i] fmt.Printf(" %-10s %-10s изменение капитала: %+d (итого: %d)\n", p.Name, r.Outcome, r.Delta, p.Balance) } if round < rounds-1 { g = g.NextRound() } } } // runOneOhOneSimulation — headless-прогон полного матча 101 // ботами, без TUI: `go run . -sim-101`. Число игроков фиксировано // (4) для простоты. func runOneOhOneSimulation() { names := newOneOhOneBotNames(4) g := NewOneOhOneGame(names, -1) bot := OneOhOneBot{} const maxRounds = 200 for round := 0; round < maxRounds; round++ { const maxSteps = 6000 steps := 0 for ; steps < maxSteps && g.Phase != OneOhOnePhaseRoundOver; steps++ { if err := bot.PlayFullTurn(g); err != nil { fmt.Printf("ошибка на шаге %d: %v\n", steps, err) return } } if g.Result == nil { fmt.Println("раунд не завершился за отведённое число шагов") return } if g.Result.Stalemate { fmt.Printf("--- Раунд %d: тупик, счёт не изменился ---\n", round+1) } else { winner := g.Players[g.Result.Winner].Name fmt.Printf("--- Раунд %d: %s избавился(лась) от карт первым(ой)", round+1, winner) if g.Result.QueenBonus { fmt.Print(" (бонус за даму!)") } fmt.Println() for i, p := range g.Players { fmt.Printf(" %-10s %+-4d очков за раунд (итого: %d)\n", p.Name, g.Result.ScoreDeltas[i], p.Score) } } if g.Result.MatchOver { if g.Result.NoWinner { fmt.Println("Партия завершилась без победителя.") } else { fmt.Printf("Партия окончена! Победитель: %s\n", g.Players[g.Result.MatchWinner].Name) } return } g.NextRound() } fmt.Println("матч не завершился за отведённое число раундов") } // runThousandSimulation — headless-прогон полного матча 1000 // ботами, без TUI: `go run . -sim-1000`. func runThousandSimulation() { names := newThousandBotNames(3) g := NewThousandGame(names, -1) bot := ThousandBot{} const maxHands = 500 for hand := 0; hand < maxHands; hand++ { const maxSteps = 2000 steps := 0 for ; steps < maxSteps && g.Phase != ThousandPhaseHandOver; steps++ { if err := bot.PlayFullTurn(g); err != nil { fmt.Printf("ошибка на шаге %d (фаза %v): %v\n", steps, g.Phase, err) return } } if g.Result == nil { fmt.Println("раздача не завершилась за отведённое число шагов") return } declarer := g.Players[g.Result.DeclarerIdx].Name status := "выполнил(а)" if !g.Result.Made { status = "не выполнил(а)" } fmt.Printf("--- Раздача %d: %s %s ставку %d ---\n", hand+1, declarer, status, g.Result.FinalBid) for i, p := range g.Players { fmt.Printf(" %-10s %+-5d очков (итого: %d)\n", p.Name, g.Result.ScoreDeltas[i], p.Score) } if g.Result.MatchOver { fmt.Printf("Партия окончена! Победитель: %s\n", g.Players[g.Result.MatchWinner].Name) return } g.NextHand() } fmt.Println("матч не завершился за отведённое число раздач") }