Implement review mode.
This finishes the UI.
This commit is contained in:
		| @@ -227,7 +227,7 @@ func (p *Player[C]) Simulate() error { | ||||
|  | ||||
| 	if p.DebugLevel > 0 && !errs.IsEmpty() { | ||||
| 		p.ChapterBreak() | ||||
| 		p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d ERRORS AND WARNINGS:", len(errs.Errs))) | ||||
| 		p.TemporaryMessages = append(p.TemporaryMessages, Msgf("%d ERRORS AND WARNINGS while simulating turn:", len(errs.Errs))) | ||||
| 		for i, e := range errs.Errs { | ||||
| 			yikes := " " | ||||
| 			if IsSeriousError(e) { | ||||
|   | ||||
| @@ -41,17 +41,26 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { | ||||
| 			wait() | ||||
| 		} | ||||
|  | ||||
| 		review(p) | ||||
| 		// Allow player to review state before continuing simulation. | ||||
| 		// Errors from review mode are reported *after* the simulation | ||||
| 		// step because the first thing Simulate does is throw out old | ||||
| 		// messages -- like these errors. | ||||
| 		reviewErr := review(p) | ||||
| 		if p.DebugLevel < 1 && IsSeriousError(err) { | ||||
| 			return reviewErr | ||||
| 		} | ||||
| 		err = p.Simulate() | ||||
| 		if p.DebugLevel < 1 && IsSeriousError(err) { | ||||
| 			return err | ||||
| 		} | ||||
| 		// Simulation errors are already in messages; now add the review error. | ||||
| 		p.ReportError(reviewErr) | ||||
|  | ||||
| 		if p.DebugLevel < 1 && p.State.Over() { | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| 	// loop forever until the game ends or the player quits | ||||
| } | ||||
|  | ||||
| func display(m Message) { | ||||
| @@ -67,27 +76,33 @@ func wait() { | ||||
| 	fmt.Scanln() | ||||
| } | ||||
|  | ||||
| func displayMainMenu[C StatsCollection](p *Player[C]) (actionsOffset, handOffset, max int) { | ||||
| 	cls() | ||||
| 	needsDivider := displayMessageSection(p) | ||||
| 	if needsDivider { | ||||
| 		divider() | ||||
| 	} | ||||
| 	displayOnePanel(p, p.Prompt) | ||||
| 	divider() | ||||
| 	actionsOffset = displayStatsMenu(p) | ||||
| 	if actionsOffset > 0 { | ||||
| 		divider() | ||||
| 	} | ||||
| 	handOffset = displayPermanentActionsMenu(p, actionsOffset) | ||||
| 	if handOffset > actionsOffset { | ||||
| 		fmt.Println() | ||||
| 	} | ||||
| 	max = displayHandMenu(p, handOffset) | ||||
| 	return // uses named return values | ||||
| } | ||||
|  | ||||
| func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, choiceIdx int, err error) { | ||||
| 	for { | ||||
| 		cls() | ||||
| 		needsDivider := displayMessageSection(p) | ||||
| 		if needsDivider { | ||||
| 			divider() | ||||
| 		} | ||||
| 		displayOnePanel(p, p.Prompt) | ||||
| 		divider() | ||||
| 		actionsOffset := displayStatsMenu(p) | ||||
| 		if actionsOffset > 0 { | ||||
| 			divider() | ||||
| 		} | ||||
| 		handOffset := displayPermanentActionsMenu(p, actionsOffset) | ||||
| 		if handOffset > actionsOffset { | ||||
| 			fmt.Println() | ||||
| 		} | ||||
| 		max := displayHandMenu(p, handOffset) | ||||
| 		actionsOffset, handOffset, max := displayMainMenu(p) | ||||
|  | ||||
| 		divider() | ||||
| 		fmt.Printf("Show just (M)essages, (I)nfo Panels, (A)ctions, or consider an action (1-%d), or (Q)uit? > ", max) | ||||
| 		fmt.Printf("%d actions remaining.\n", p.ActionsRemaining) | ||||
| 		fmt.Printf("Show just (M)essages, (I)nfo Panels, (A)ctions, or consider an item (1-%d), or (Q)uit? > ", max) | ||||
| 		input := getResponse() | ||||
| 		switch input { | ||||
| 		// Special cases | ||||
| @@ -102,7 +117,7 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, | ||||
| 			statsMode(p) | ||||
| 		case "a", "act", "actions": | ||||
| 			var committed bool | ||||
| 			isCard, cardIdx, choiceIdx, committed, err = actionsMode(p) | ||||
| 			isCard, cardIdx, choiceIdx, committed, err = actionsMode(p, true) | ||||
| 			if committed { | ||||
| 				return | ||||
| 			} | ||||
| @@ -150,6 +165,12 @@ func getResponse() string { | ||||
| } | ||||
|  | ||||
| func divider() { | ||||
| 	fmt.Println() | ||||
| 	fmt.Println(ChapterBreak.String()) | ||||
| 	fmt.Println() | ||||
| } | ||||
|  | ||||
| func lightDivider() { | ||||
| 	fmt.Println() | ||||
| 	fmt.Println(SectionBreak.String()) | ||||
| 	fmt.Println() | ||||
| @@ -184,7 +205,16 @@ func displayMessageSection[C StatsCollection](p *Player[C]) bool { | ||||
| 	if len(p.TemporaryMessages) == 0 { | ||||
| 		return false | ||||
| 	} | ||||
| 	display(MultiMessage(p.TemporaryMessages)) | ||||
| 	hasPrevious := false | ||||
| 	for _, m := range p.TemporaryMessages { | ||||
| 		if m != nil { | ||||
| 			if hasPrevious { | ||||
| 				lightDivider() | ||||
| 			} | ||||
| 			display(m) | ||||
| 			hasPrevious = true | ||||
| 		} | ||||
| 	} | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| @@ -229,7 +259,7 @@ func displayHandMenu[C StatsCollection](p *Player[C], offset int) int { | ||||
| 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) | ||||
| 		opts, valid, err := displayCard(p, card, true) | ||||
| 		if IsSeriousError(err) { | ||||
| 			return -1, err | ||||
| 		} | ||||
| @@ -267,7 +297,7 @@ func promptCard[C StatsCollection](p *Player[C], card Card[C]) (optionIdx int, e | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func displayCard[C StatsCollection](p *Player[C], card Card[C]) ([]CardOption[C], bool, error) { | ||||
| func displayCard[C StatsCollection](p *Player[C], card Card[C], canAct bool) ([]CardOption[C], bool, error) { | ||||
| 	cls() | ||||
| 	t := card.Title(p).String() | ||||
| 	urgent := card.Urgent(p) | ||||
| @@ -287,11 +317,10 @@ func displayCard[C StatsCollection](p *Player[C], card Card[C]) ([]CardOption[C] | ||||
| 	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 | ||||
| 		canAct = false | ||||
| 	} | ||||
| 	opts, optErr := card.Options(p) | ||||
| 	errs.Add(optErr) | ||||
| @@ -301,9 +330,13 @@ func displayCard[C StatsCollection](p *Player[C], card Card[C]) ([]CardOption[C] | ||||
| 	valid := false | ||||
| 	for i, opt := range opts { | ||||
| 		pfx := "[xx]:" | ||||
| 		if !lockout && opt.Enabled(p) { | ||||
| 			pfx = fmt.Sprintf("[%2d]:", i+1) | ||||
| 			valid = true | ||||
| 		if opt.Enabled(p) { | ||||
| 			if canAct { | ||||
| 				pfx = fmt.Sprintf("[%2d]:", i+1) | ||||
| 				valid = true | ||||
| 			} else { | ||||
| 				pfx = "[--]:" | ||||
| 			} | ||||
| 		} | ||||
| 		t, err := opt.OptionText(p) | ||||
| 		errs.Add(err) | ||||
| @@ -326,7 +359,7 @@ func statsMode[C StatsCollection](p *Player[C]) error { | ||||
| 			return errs.Emit() | ||||
| 		} | ||||
| 		fmt.Println() | ||||
| 		fmt.Printf("Go (B)ack, (Q)uit, or view an info panel (1-%d)? > ") | ||||
| 		fmt.Printf("Go (B)ack, (Q)uit, or view an info panel (1-%d)? > ", n) | ||||
| 		s := getResponse() | ||||
| 		switch s { | ||||
| 		case "b", "back": | ||||
| @@ -353,7 +386,7 @@ func statsMode[C StatsCollection](p *Player[C]) error { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func actionsMode[C StatsCollection](p *Player[C]) (isCard bool, cardIdx, choiceIdx int, committed bool, err error) { | ||||
| func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, cardIdx, choiceIdx int, committed bool, err error) { | ||||
| 	var errs ErrorCollector | ||||
| 	for { | ||||
| 		cls() | ||||
| @@ -372,7 +405,11 @@ func actionsMode[C StatsCollection](p *Player[C]) (isCard bool, cardIdx, choiceI | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println() | ||||
| 		fmt.Printf("Go (B)ack, (Q)uit, or consider an action (1-%d)? > ", max) | ||||
| 		if canAct { | ||||
| 			fmt.Printf("Go (B)ack, (Q)uit, or consider an action (1-%d)? > ", max) | ||||
| 		} else { | ||||
| 			fmt.Printf("Go (B)ack, (Q)uit, or view an action (1-%d)? > ", max) | ||||
| 		} | ||||
| 		input := getResponse() | ||||
| 		switch input { | ||||
| 		case "b", "back": | ||||
| @@ -389,20 +426,104 @@ func actionsMode[C StatsCollection](p *Player[C]) (isCard bool, cardIdx, choiceI | ||||
| 				wait() | ||||
| 			} else if v <= pOff { | ||||
| 				v-- | ||||
| 				optIdx, err := promptCard(p, p.PermanentActions[v]) | ||||
| 				errs.Add(err) | ||||
| 				if optIdx >= 0 || IsSeriousError(err) { | ||||
| 					return false, v, optIdx, true, errs.Emit() | ||||
| 				if canAct { | ||||
| 					optIdx, err := promptCard(p, p.PermanentActions[v]) | ||||
| 					errs.Add(err) | ||||
| 					if optIdx >= 0 || IsSeriousError(err) { | ||||
| 						return false, v, optIdx, true, errs.Emit() | ||||
| 					} | ||||
| 				} else { | ||||
| 					_, _, err := displayCard(p, p.PermanentActions[v], false) | ||||
| 					errs.Add(err) | ||||
| 					if IsSeriousError(err) { | ||||
| 						return false, -1, -1, true, errs.Emit() | ||||
| 					} | ||||
| 					wait() | ||||
| 				} | ||||
| 			} else { | ||||
| 				v = v - pOff - 1 | ||||
| 				optIdx, err := promptCard(p, p.Hand[v]) | ||||
| 				errs.Add(err) | ||||
| 				if optIdx >= 0 || IsSeriousError(err) { | ||||
| 					return true, v, optIdx, true, errs.Emit() | ||||
| 				if canAct { | ||||
| 					optIdx, err := promptCard(p, p.Hand[v]) | ||||
| 					errs.Add(err) | ||||
| 					if optIdx >= 0 || IsSeriousError(err) { | ||||
| 						return true, v, optIdx, false, errs.Emit() | ||||
| 					} | ||||
| 				} else { | ||||
| 					_, _, err := displayCard(p, p.Hand[v], false) | ||||
| 					errs.Add(err) | ||||
| 					if IsSeriousError(err) { | ||||
| 						return false, -1, -1, false, errs.Emit() | ||||
| 					} | ||||
| 					wait() | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		// Re-prompt to get a valid choice. | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func review[C StatsCollection](p *Player[C]) error { | ||||
| 	var errs ErrorCollector | ||||
| 	for { | ||||
| 		actionsOffset, handOffset, max := displayMainMenu(p) | ||||
| 		divider() | ||||
| 		fmt.Println("No actions remaining.") | ||||
| 		fmt.Printf("(C)ontinue, review just (M)essages, (I)nfo Panels, (A)ctions, or an item (1-%d), or (Q)uit? > ", max) | ||||
| 		input := getResponse() | ||||
| 		switch input { | ||||
| 		// Special cases | ||||
| 		case "m", "msg", "message", "messages": | ||||
| 			cls() | ||||
| 			if displayMessageSection(p) { | ||||
| 				divider() | ||||
| 			} | ||||
| 			displayOnePanel(p, p.Prompt) | ||||
| 			wait() | ||||
| 		case "s", "stat", "stats", "i", "info", "p", "panel", "panels", "infopanel", "infopanels": | ||||
| 			err := statsMode(p) | ||||
| 			errs.Add(err) | ||||
| 			if IsSeriousError(err) { | ||||
| 				return errs.Emit() | ||||
| 			} | ||||
| 		case "a", "act", "actions": | ||||
| 			_, _, _, _, err := actionsMode(p, false) | ||||
| 			errs.Add(err) | ||||
| 			if IsSeriousError(err) { | ||||
| 				return errs.Emit() | ||||
| 			} | ||||
| 		case "q", "quit", "exit": | ||||
| 			confirmQuit() | ||||
| 		case "c", "continue", "ok": | ||||
| 			return errs.Emit() | ||||
| 		default: | ||||
| 			i, err := strconv.Atoi(input) | ||||
| 			if err != nil { | ||||
| 				fmt.Println("Sorry, I don't understand.") | ||||
| 				wait() | ||||
| 			} else if i <= 0 || i > max { | ||||
| 				fmt.Println("That's not on this menu. If the menu is too big to read, choose a detail view.") | ||||
| 				wait() | ||||
| 			} else if i <= actionsOffset { | ||||
| 				cls() | ||||
| 				displayOnePanel(p, p.InfoPanels[i-1]) | ||||
| 				wait() | ||||
| 			} else if i < handOffset { | ||||
| 				i = i - actionsOffset - 1 | ||||
| 				_, _, err := displayCard(p, p.PermanentActions[i], false) | ||||
| 				errs.Add(err) | ||||
| 				if IsSeriousError(err) { | ||||
| 					return errs.Emit() | ||||
| 				} | ||||
| 				wait() | ||||
| 			} else { | ||||
| 				i = i - handOffset - 1 | ||||
| 				_, _, err := displayCard(p, p.Hand[i], false) | ||||
| 				errs.Add(err) | ||||
| 				if IsSeriousError(err) { | ||||
| 					return errs.Emit() | ||||
| 				} | ||||
| 				wait() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user