Prompt for player choices on cards.
Also handles errors in the display/prompt logic somewhat better.
This commit is contained in:
		| @@ -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("<You have more urgent matters to attend to! You cannot act on this right now.>") | ||||
| 		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() | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user