Display debug enactables.
Includes some refactoring work to pull out common code and express the idea of "wait, which panel, exactly?".
This commit is contained in:
		| @@ -12,6 +12,12 @@ type Message interface { | ||||
| 	fmt.Stringer | ||||
| } | ||||
|  | ||||
| // Titled desccribes any type that returns a Message as a title, given a Player | ||||
| // (which it may ignore). | ||||
| type Titled[C StatsCollection] interface { | ||||
| 	Title(*Player[C]) Message | ||||
| } | ||||
|  | ||||
| type stringMessage string | ||||
|  | ||||
| func (s stringMessage) String() string { | ||||
|   | ||||
| @@ -313,6 +313,38 @@ func (p *Player[C]) HasUrgentCards() bool { | ||||
| 	return false | ||||
| } | ||||
|  | ||||
| // EnactableType is an enumeration representing a category of enactable thing. | ||||
| // Debug actions, permanent actions, and cards behave equivalently in many ways, | ||||
| // so EnactableType allows parts of the program to work with any of these and | ||||
| // represent which one they apply to. | ||||
| type EnactableType int | ||||
|  | ||||
| const ( | ||||
| 	// InvalidEnactable is an uninitialized EnactableType value with no meaning. | ||||
| 	// Using it is generally an error. If you initialize EnactableType fields | ||||
| 	// with this value when your program has not yet calculated what type of | ||||
| 	// enactable will be used, CardSimEngine will be able to detect bugs where | ||||
| 	// such a calcualation, inadvertently, does not come to any conclusion. | ||||
| 	// Unlike NothingEnactable, there are no circumstances where this has a | ||||
| 	// specific valid meaning. | ||||
| 	InvalidEnactable = EnactableType(iota) | ||||
|  | ||||
| 	// NothingEnactable specifically represents not enacting anything. In some | ||||
| 	// contexts, it's an error to use it; in others, it is a sentinel value | ||||
| 	// for "do not enact anything". Unlike InvalidEnactable, this has a specific | ||||
| 	// valid meaning, it's just that the meaning is specifically "nothing". | ||||
| 	NothingEnactable | ||||
|  | ||||
| 	// CardEnactable refers to a card in the hand. | ||||
| 	CardEnactable | ||||
|  | ||||
| 	// PermanentActionEnactable refers to an item in the permanent actions list. | ||||
| 	PermanentActionEnactable | ||||
|  | ||||
| 	// DebugActionEnactable refers to an item in the debug actions list. | ||||
| 	DebugActionEnactable | ||||
| ) | ||||
|  | ||||
| // EnactCardUnchecked executes a card choice, removes it from the hand, and | ||||
| // decrements the ActionsRemaining. It does not check for conflicting Urgent | ||||
| // cards or already being out of actions. If no such card or card choice | ||||
|   | ||||
| @@ -17,7 +17,7 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { | ||||
|  | ||||
| 	for { | ||||
| 		for p.CanAct() { | ||||
| 			isCard, cardIdx, choiceIdx, err := pickNextAction(p) | ||||
| 			actionType, cardIdx, choiceIdx, err := pickNextAction(p) | ||||
| 			p.ReportError(err) | ||||
| 			if IsSeriousError(err) { | ||||
| 				if p.DebugLevel < 1 { | ||||
| @@ -26,10 +26,18 @@ func RunSimpleTerminalUI[C StatsCollection](p *Player[C]) error { | ||||
| 				continue | ||||
| 			} | ||||
| 			var msg Message | ||||
| 			if isCard { | ||||
| 			switch actionType { | ||||
| 			case CardEnactable: | ||||
| 				msg, err = p.EnactCard(cardIdx, choiceIdx) | ||||
| 			} else { | ||||
| 			case DebugActionEnactable: | ||||
| 				msg, err = p.EnactDebugActionUnchecked(cardIdx, choiceIdx) | ||||
| 			case PermanentActionEnactable: | ||||
| 				msg, err = p.EnactPermanentAction(cardIdx, choiceIdx) | ||||
| 			case NothingEnactable: | ||||
| 				continue | ||||
| 			default: | ||||
| 				msg = nil | ||||
| 				err = fmt.Errorf("invalid enaction type in action loop: %d", actionType) | ||||
| 			} | ||||
| 			p.ReportError(err) | ||||
| 			if IsSeriousError(err) { | ||||
| @@ -77,7 +85,7 @@ func wait() { | ||||
| 	fmt.Scanln() | ||||
| } | ||||
|  | ||||
| func displayMainMenu[C StatsCollection](p *Player[C]) (actionsOffset, handOffset, max int) { | ||||
| func displayMainMenu[C StatsCollection](p *Player[C]) (debugOffset, actionsOffset, handOffset, max int) { | ||||
| 	cls() | ||||
| 	needsDivider := displayMessageSection(p) | ||||
| 	if needsDivider { | ||||
| @@ -85,10 +93,14 @@ func displayMainMenu[C StatsCollection](p *Player[C]) (actionsOffset, handOffset | ||||
| 	} | ||||
| 	displayOnePanel(p, p.Prompt) | ||||
| 	divider() | ||||
| 	actionsOffset = displayStatsMenu(p) | ||||
| 	if actionsOffset > 0 { | ||||
| 	debugOffset = displayStatsMenu(p) | ||||
| 	if debugOffset > 0 { | ||||
| 		divider() | ||||
| 	} | ||||
| 	actionsOffset = displayDebugActionsMenu(p, debugOffset) | ||||
| 	if actionsOffset > debugOffset { | ||||
| 		fmt.Println() | ||||
| 	} | ||||
| 	handOffset = displayPermanentActionsMenu(p, actionsOffset) | ||||
| 	if handOffset > actionsOffset { | ||||
| 		fmt.Println() | ||||
| @@ -97,9 +109,9 @@ func displayMainMenu[C StatsCollection](p *Player[C]) (actionsOffset, handOffset | ||||
| 	return // uses named return values | ||||
| } | ||||
|  | ||||
| func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, choiceIdx int, err error) { | ||||
| func pickNextAction[C StatsCollection](p *Player[C]) (actionType EnactableType, cardIdx int, choiceIdx int, err error) { | ||||
| 	for { | ||||
| 		actionsOffset, handOffset, max := displayMainMenu(p) | ||||
| 		debugOffset, actionsOffset, handOffset, max := displayMainMenu(p) | ||||
|  | ||||
| 		divider() | ||||
| 		fmt.Printf("%d actions remaining.\n", p.ActionsRemaining) | ||||
| @@ -117,9 +129,8 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, | ||||
| 		case "s", "stat", "stats", "i", "info", "p", "panel", "panels", "infopanel", "infopanels": | ||||
| 			statsMode(p) | ||||
| 		case "a", "act", "actions": | ||||
| 			var committed bool | ||||
| 			isCard, cardIdx, choiceIdx, committed, err = actionsMode(p, true) | ||||
| 			if committed { | ||||
| 			actionType, cardIdx, choiceIdx, err = actionsMode(p, true) | ||||
| 			if actionType != NothingEnactable { | ||||
| 				return | ||||
| 			} | ||||
| 		case "q", "quit", "exit": | ||||
| @@ -132,21 +143,27 @@ func pickNextAction[C StatsCollection](p *Player[C]) (isCard bool, cardIdx int, | ||||
| 			} else if 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 { | ||||
| 			} else if i <= debugOffset { | ||||
| 				cls() | ||||
| 				displayOnePanel(p, p.InfoPanels[i-1]) | ||||
| 				wait() | ||||
| 			} else if i <= actionsOffset { | ||||
| 				i = i - debugOffset - 1 | ||||
| 				option, promptErr := promptCard(p, p.DebugActions[i]) | ||||
| 				if option >= 0 || IsSeriousError(promptErr) { | ||||
| 					return DebugActionEnactable, i, option, promptErr | ||||
| 				} | ||||
| 			} else if i <= handOffset { | ||||
| 				i = i - actionsOffset - 1 | ||||
| 				option, promptErr := promptCard(p, p.PermanentActions[i]) | ||||
| 				if option >= 0 || IsSeriousError(promptErr) { | ||||
| 					return false, i, option, promptErr | ||||
| 					return PermanentActionEnactable, i, option, promptErr | ||||
| 				} | ||||
| 			} else { | ||||
| 				i = i - handOffset - 1 | ||||
| 				option, promptErr := promptCard(p, p.Hand[i]) | ||||
| 				if option >= 0 || IsSeriousError(promptErr) { | ||||
| 					return true, i, option, nil | ||||
| 					return CardEnactable, i, option, nil | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| @@ -222,9 +239,7 @@ func displayStatsMenu[C StatsCollection](p *Player[C]) int { | ||||
| 	} | ||||
| 	fmt.Println("Info Panels") | ||||
| 	fmt.Println("-----------") | ||||
| 	for i, s := range p.InfoPanels { | ||||
| 		fmt.Printf("[%2d]: %s\n", i+1, s.Title(p).String()) | ||||
| 	} | ||||
| 	displayNumberedTitles(p, p.InfoPanels, 0) | ||||
| 	return len(p.InfoPanels) | ||||
| } | ||||
|  | ||||
| @@ -234,22 +249,34 @@ func displayPermanentActionsMenu[C StatsCollection](p *Player[C], offset int) in | ||||
| 	} | ||||
| 	fmt.Println("Always Available") | ||||
| 	fmt.Println("----------------") | ||||
| 	for i, s := range p.PermanentActions { | ||||
| 		fmt.Printf("[%2d]: %s\n", i+offset+1, s.Title(p)) | ||||
| 	} | ||||
| 	displayNumberedTitles(p, p.PermanentActions, offset) | ||||
| 	return offset + len(p.PermanentActions) | ||||
| } | ||||
|  | ||||
| func displayDebugActionsMenu[C StatsCollection](p *Player[C], offset int) int { | ||||
| 	if p.DebugLevel < 1 || len(p.DebugActions) == 0 { | ||||
| 		return offset | ||||
| 	} | ||||
| 	fmt.Println("Debug Mode") | ||||
| 	fmt.Println("----------") | ||||
| 	displayNumberedTitles(p, p.DebugActions, offset) | ||||
| 	return offset + len(p.DebugActions) | ||||
| } | ||||
|  | ||||
| func displayHandMenu[C StatsCollection](p *Player[C], offset int) int { | ||||
| 	if len(p.Hand) == 0 { | ||||
| 		return offset | ||||
| 	} | ||||
| 	fmt.Println("Hand") | ||||
| 	fmt.Println("----") | ||||
| 	for i, s := range p.Hand { | ||||
| 	displayNumberedTitles(p, p.Hand, offset) | ||||
| 	return offset + len(p.Hand) | ||||
| } | ||||
|  | ||||
| func displayNumberedTitles[C StatsCollection, T Titled[C]](p *Player[C], cards []T, offset int) { | ||||
| 	for i, s := range cards { | ||||
| 		fmt.Printf("[%2d]: %s\n", i+offset+1, s.Title(p)) | ||||
| 	} | ||||
| 	return offset + len(p.Hand) | ||||
| } | ||||
|  | ||||
| // promptCard asks the player to take an action on a card. Returns the option | ||||
| @@ -385,12 +412,16 @@ func statsMode[C StatsCollection](p *Player[C]) error { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, cardIdx, choiceIdx int, committed bool, err error) { | ||||
| func actionsMode[C StatsCollection](p *Player[C], canAct bool) (actionType EnactableType, cardIdx, choiceIdx int, err error) { | ||||
| 	var errs ErrorCollector | ||||
| 	for { | ||||
| 		cls() | ||||
| 		pOff := displayPermanentActionsMenu(p, 0) | ||||
| 		if pOff > 0 { | ||||
| 		dOff := displayDebugActionsMenu(p, 0) | ||||
| 		if dOff > 0 { | ||||
| 			fmt.Println() | ||||
| 		} | ||||
| 		pOff := displayPermanentActionsMenu(p, dOff) | ||||
| 		if pOff > dOff { | ||||
| 			fmt.Println() | ||||
| 		} | ||||
| 		max := displayHandMenu(p, pOff) | ||||
| @@ -400,7 +431,7 @@ func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, car | ||||
| 			fmt.Println("That's a problem. The game is stuck.") | ||||
| 			confirmQuit() | ||||
| 			errs.Add(WarningStalemate) | ||||
| 			return false, -1, -1, true, errs.Emit() | ||||
| 			return NothingEnactable, -1, -1, errs.Emit() | ||||
| 		} | ||||
|  | ||||
| 		fmt.Println() | ||||
| @@ -412,7 +443,7 @@ func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, car | ||||
| 		input := getResponse() | ||||
| 		switch input { | ||||
| 		case "b", "back": | ||||
| 			return false, -1, -1, false, errs.Emit() | ||||
| 			return NothingEnactable, -1, -1, errs.Emit() | ||||
| 		case "q", "quit": | ||||
| 			confirmQuit() | ||||
| 		default: | ||||
| @@ -423,19 +454,35 @@ func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, car | ||||
| 			} else if v < 1 || v > max { | ||||
| 				fmt.Println("That's not a card or action.") | ||||
| 				wait() | ||||
| 			} else if v <= pOff { | ||||
| 			} else if v <= dOff { | ||||
| 				v-- | ||||
| 				if canAct { | ||||
| 					optIdx, err := promptCard(p, p.DebugActions[v]) | ||||
| 					errs.Add(err) | ||||
| 					if optIdx >= 0 || IsSeriousError(err) { | ||||
| 						return DebugActionEnactable, v, optIdx, errs.Emit() | ||||
| 					} | ||||
| 				} else { | ||||
| 					_, _, err := displayCard(p, p.DebugActions[v], false) | ||||
| 					errs.Add(err) | ||||
| 					if IsSeriousError(err) { | ||||
| 						return DebugActionEnactable, -1, -1, errs.Emit() | ||||
| 					} | ||||
| 					wait() | ||||
| 				} | ||||
| 			} else if v <= pOff { | ||||
| 				v = v - dOff - 1 | ||||
| 				if canAct { | ||||
| 					optIdx, err := promptCard(p, p.PermanentActions[v]) | ||||
| 					errs.Add(err) | ||||
| 					if optIdx >= 0 || IsSeriousError(err) { | ||||
| 						return false, v, optIdx, true, errs.Emit() | ||||
| 						return PermanentActionEnactable, v, optIdx, errs.Emit() | ||||
| 					} | ||||
| 				} else { | ||||
| 					_, _, err := displayCard(p, p.PermanentActions[v], false) | ||||
| 					errs.Add(err) | ||||
| 					if IsSeriousError(err) { | ||||
| 						return false, -1, -1, true, errs.Emit() | ||||
| 						return PermanentActionEnactable, -1, -1, errs.Emit() | ||||
| 					} | ||||
| 					wait() | ||||
| 				} | ||||
| @@ -445,13 +492,13 @@ func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, car | ||||
| 					optIdx, err := promptCard(p, p.Hand[v]) | ||||
| 					errs.Add(err) | ||||
| 					if optIdx >= 0 || IsSeriousError(err) { | ||||
| 						return true, v, optIdx, false, errs.Emit() | ||||
| 						return CardEnactable, v, optIdx, errs.Emit() | ||||
| 					} | ||||
| 				} else { | ||||
| 					_, _, err := displayCard(p, p.Hand[v], false) | ||||
| 					errs.Add(err) | ||||
| 					if IsSeriousError(err) { | ||||
| 						return false, -1, -1, false, errs.Emit() | ||||
| 						return CardEnactable, -1, -1, errs.Emit() | ||||
| 					} | ||||
| 					wait() | ||||
| 				} | ||||
| @@ -464,7 +511,7 @@ func actionsMode[C StatsCollection](p *Player[C], canAct bool) (isCard bool, car | ||||
| func review[C StatsCollection](p *Player[C]) error { | ||||
| 	var errs ErrorCollector | ||||
| 	for { | ||||
| 		actionsOffset, handOffset, max := displayMainMenu(p) | ||||
| 		debugOffset, 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) | ||||
| @@ -485,7 +532,7 @@ func review[C StatsCollection](p *Player[C]) error { | ||||
| 				return errs.Emit() | ||||
| 			} | ||||
| 		case "a", "act", "actions": | ||||
| 			_, _, _, _, err := actionsMode(p, false) | ||||
| 			_, _, _, err := actionsMode(p, false) | ||||
| 			errs.Add(err) | ||||
| 			if IsSeriousError(err) { | ||||
| 				return errs.Emit() | ||||
| @@ -498,14 +545,18 @@ func review[C StatsCollection](p *Player[C]) error { | ||||
| 			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 { | ||||
| 			} else if i <= debugOffset { | ||||
| 				cls() | ||||
| 				displayOnePanel(p, p.InfoPanels[i-1]) | ||||
| 				wait() | ||||
| 			} else if i <= actionsOffset { | ||||
| 				i = i - debugOffset - 1 | ||||
| 				_, _, err := displayCard(p, p.DebugActions[i], false) | ||||
| 				errs.Add(err) | ||||
| 				if IsSeriousError(err) { | ||||
| 					return errs.Emit() | ||||
| 				} | ||||
| 			} else if i <= handOffset { | ||||
| 				i = i - actionsOffset - 1 | ||||
| 				_, _, err := displayCard(p, p.PermanentActions[i], false) | ||||
| @@ -513,7 +564,6 @@ func review[C StatsCollection](p *Player[C]) error { | ||||
| 				if IsSeriousError(err) { | ||||
| 					return errs.Emit() | ||||
| 				} | ||||
| 				wait() | ||||
| 			} else { | ||||
| 				i = i - handOffset - 1 | ||||
| 				_, _, err := displayCard(p, p.Hand[i], false) | ||||
| @@ -521,8 +571,8 @@ func review[C StatsCollection](p *Player[C]) error { | ||||
| 				if IsSeriousError(err) { | ||||
| 					return errs.Emit() | ||||
| 				} | ||||
| 				wait() | ||||
| 			} | ||||
| 			wait() | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user