290 lines
10 KiB
Go
290 lines
10 KiB
Go
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("матч не завершился за отведённое число раздач")
|
||
}
|