From 25a9eed3f0d4ba1d15ca569daabaf69700117b69 Mon Sep 17 00:00:00 2001 From: Kistaro Windrider Date: Sun, 2 Apr 2023 12:44:29 -0700 Subject: [PATCH] Prompt for player choices on cards. Also handles errors in the display/prompt logic somewhat better. --- cardsim/terminalui.go | 149 +++++++++++++++++++++++++++++++++++------- 1 file changed, 126 insertions(+), 23 deletions(-) diff --git a/cardsim/terminalui.go b/cardsim/terminalui.go index 2dd56fa..3842a95 100644 --- a/cardsim/terminalui.go +++ b/cardsim/terminalui.go @@ -16,7 +16,14 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { p.ReportError(err) for p.CanAct() { - isCard, cardIdx, choiceIdx := pickNextAction(p) + isCard, cardIdx, choiceIdx, err := pickNextAction(p) + p.ReportError(err) + if IsSeriousError(err) { + if p.DebugLevel < 1 { + return err + } + continue + } var msg Message if isCard { msg, err = p.EnactCard(cardIdx, choiceIdx) @@ -24,8 +31,11 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { msg, err = p.EnactPermanentAction(cardIdx, choiceIdx) } p.ReportError(err) - if p.DebugLevel < 1 && IsSeriousError(err) { - return err + if IsSeriousError(err) { + if p.DebugLevel < 1 { + return err + } + continue } display(msg) wait() @@ -57,7 +67,7 @@ func wait() { fmt.Scanln() } -func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, choiceIdx int) { +func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, choiceIdx int, err error) { for { cls() needsDivider := displayMessageSection(p) @@ -77,22 +87,19 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, max := displayHandMenu(p, handOffset) divider() - var input string fmt.Printf("Show just (M)essages, (S)tats, (A)ctions, make a choice (1-%d), or (Q)uit? >", max+1) - fmt.Scanln(&input) - input = strings.TrimSpace(input) - input = strings.ToLower(input) + input := getResponse() switch input { // Special cases - case "m": + case "m", "msg", "message", "messages": cls() displayMessageSection(p) wait() - case "s": + case "s", "stat", "stats": statsMode(p) - case "a": + case "a", "act", "actions": actionsMode(p) - case "q": + case "q", "quit", "exit": confirmQuit() default: i, err := strconv.Atoi(input) @@ -114,16 +121,16 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, } else if i < handOffset { cls() i -= actionsOffset - option, ok := promptPermanentAction(p, i) - if ok { - return false, i, option + option, promptErr := promptCard(p, p.PermanentActions[i]) + if option >= 0 || IsSeriousError(promptErr) { + return false, i, option, promptErr } } else { cls() i -= handOffset - option, ok := promptCard(p, i) - if ok { - return true, i, option + option, promptErr := promptCard(p, p.Hand[i]) + if option >= 0 || IsSeriousError(promptErr) { + return true, i, option, nil } } } @@ -134,6 +141,14 @@ func cls() { fmt.Println("\033[H\033[2J") } +func getResponse() string { + var input string + fmt.Scanln(&input) + input = strings.TrimSpace(input) + input = strings.ToLower(input) + return input +} + func divider() { fmt.Println() fmt.Println(SectionBreak.String()) @@ -143,11 +158,8 @@ func divider() { func confirmQuit() { divider() fmt.Println("Are you sure you want to quit? (Y/N) ") - var s string - fmt.Scanln(&s) - s = strings.TrimSpace(s) - s = strings.ToLower(s) - if strings.HasPrefix(s, "y") { + s := getResponse() + if s == "y" || s == "yes" { fmt.Println("Bye!") os.Exit(0) } @@ -211,3 +223,94 @@ func displayHandMenu[C StatsCollection](p *Player[C], offset int) int { } return offset + len(p.Hand) } + +// promptCard asks the player to take an action on a card. Returns the option +// they chose, or -1 if there was a serious error or they cancelled selection. +func promptCard[C StatsCollection](p *Player[C], card Card[C]) (optionIdx int, err error) { + // Iterate until the player makes a valid choice. + for { + opts, valid, err := displayCard(p, card) + if IsSeriousError(err) { + return -1, err + } + fmt.Println() + if valid { + fmt.Printf("Go (B)ack, (Q)uit, or enact a choice (1 - %d)? >", len(opts)+1) + } else { + fmt.Print("Go (B)ack or (Q)uit? >") + } + read := getResponse() + switch read { + case "b", "back": + return -1, err + case "q", "quit": + confirmQuit() + default: + i, convErr := strconv.Atoi(read) + if convErr != nil { + fmt.Println("Sorry, I don't understand.") + wait() + } else if !valid { + fmt.Println("You can't enact anything here.") + wait() + } else if i <= 0 || i > len(opts) { + fmt.Println("That's not one of the options.") + wait() + } else if !opts[i-1].Enabled(p) { + fmt.Println("That option is not available to you right now.") + wait() + } else { + return i - 1, err + } + } + // Invalid selection made -- loop to prompt again. + } +} + +func displayCard[C StatsCollection](p *Player[C], card Card[C]) ([]CardOption[C], bool, error) { + cls() + t := card.Title(p).String() + urgent := card.Urgent(p) + if urgent { + t = "[URGENT!] " + t + } + fmt.Println(t) + fmt.Println(strings.Repeat("-", len(t))) + fmt.Println() + event, err := card.EventText(p) + if IsSeriousError(err) { + return nil, false, err + } + var errs ErrorCollector + errs.Add(err) + fmt.Println(event.String()) + fmt.Println() + fmt.Println(SectionBreak.String()) + fmt.Println() + lockout := false + if !urgent && p.HasUrgentCards() { + fmt.Println("") + fmt.Println() + lockout = true + } + opts, optErr := card.Options(p) + errs.Add(optErr) + if IsSeriousError(optErr) { + return nil, false, errs.Emit() + } + valid := false + for i, opt := range opts { + pfx := "[xx]:" + if !lockout && opt.Enabled(p) { + pfx = fmt.Sprintf("[%2d]:", i+1) + valid = true + } + t, err := opt.OptionText(p) + errs.Add(err) + if IsSeriousError(err) { + return nil, false, errs.Emit() + } + fmt.Println(pfx, t.String()) + } + return opts, valid, errs.Emit() +}