Documentation
¶
Overview ¶
Package app is the interactive shell: the screens a player moves between and the state they share. The view layer in internal/ui draws; this package decides what is drawn and what the keys do.
Index ¶
- func Back() tea.Cmd
- func Done(msg DoneMsg) tea.Cmd
- func Fail(err error) tea.Cmd
- func Open(s Screen) tea.Cmd
- func Quit() tea.Cmd
- func Replace(s Screen) tea.Cmd
- type Departing
- type Deps
- type DoneMsg
- type GameConfig
- type Menu
- type OpenMsg
- type Picker
- type ReplayScreen
- type Screen
- type Seat
- type Shell
- type ThemeChangedMsg
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Done ¶
Done is a command that emits a DoneMsg, which is what a screen returns from Update when it is finished.
func Open ¶
Open shows another screen without giving up this one.
This is the difference between a menu that launches a game and a menu that is consumed by launching one. With Replace, leaving the game emptied the stack and ended the program, so there was no way back to the menu the player had started from.
Types ¶
type Departing ¶
type Departing interface {
// Depart is called once, on the way out, before the program ends. It must
// not block for long and must not expect to draw again.
Depart()
}
Departing is implemented by a screen that has something to finish before the program ends, such as saving a game that is still in progress.
The shell answers the global quit key itself so that a busy screen cannot trap the player, but that means the key never reaches the screen. Without this, a screen's own handling of it is dead code: quitting with the plain letter saved an unfinished game while quitting with the control key silently discarded it, which is the same act from the player's point of view.
type Deps ¶
type Deps struct {
// ConfigDir is where profiles, games and settings live.
ConfigDir string
Profiles *profile.Store
Board *leaderboard.Board
Games *gamestore.Store
Theme theme.Theme
Styles *ui.Styles
Keymap ui.Keymap
// Now is the clock, injected so that a test can pin the durations that end
// up on the leaderboard.
Now func() time.Time
// Note lets a screen leave a line for the player to read after the
// interface has closed. Anything printed while the alternate screen is up
// disappears with it, so a screen that saved a game on the way out has
// nowhere to say so: the player was left not knowing whether their game
// survived. Nil means nobody is listening.
Note func(string)
}
Deps are the collaborators every screen needs. It is passed by value; the stores inside it are shared and safe for concurrent use.
type DoneMsg ¶
DoneMsg asks the shell to leave the screen that sent it.
A screen that wants to be replaced sets Next. A screen that wants to go back to where it came from leaves Next nil. A screen that failed sets Err, which the shell shows before going back. Quit ends the program.
type GameConfig ¶
type GameConfig struct {
Kind gamestore.Kind
Rules game.Ruleset
// Seats says who plays each side. Both sides must be filled.
Seats map[game.Player]Seat
// Hints allows the player to ask for advice, which only makes sense when
// there is an engine to ask.
Hints bool
// HintFor is the engine consulted for hints, which is the opponent bot in a
// game against one and may be a separate engine otherwise.
HintFor bot.Bot
// Session is the network connection for a live remote game, nil otherwise.
Session netplay.Session
// Codes drives a remote seat by move codes the players exchange by hand,
// with no connection at all. It is mutually exclusive with Session: a
// correspondence game has a remote seat and no session, which is the one
// case where that combination is legitimate.
Codes bool
// Resume, when set, continues a stored game instead of starting a new one.
Resume *gamestore.Saved
// StoreID is the identifier the game is saved under, and for a
// correspondence game it is also the identifier its move codes are bound
// to, so a code from another game is refused. Empty means allocate one.
StoreID string
}
GameConfig describes a game to be played. The command line builds one of these and hands it to NewGameScreen.
type Menu ¶
type Menu struct {
// contains filtered or unexported fields
}
Menu is the main menu: everything a player can reach, each entry with a one-line explanation shown while it is highlighted, plus the small forms that collect the choices a game needs before it can start.
The forms live inside this model rather than being screens of their own because a screen cannot push another screen: the shell's DoneMsg replaces or pops, so a wizard built out of screens could not walk backwards.
type OpenMsg ¶
type OpenMsg struct {
Screen Screen
}
OpenMsg asks the shell to show another screen on top of the one that sent it, which stays underneath and is returned to when the new one is finished.
type Picker ¶
type Picker struct {
// contains filtered or unexported fields
}
Picker asks who is playing, which is the first thing the program does (R13). It satisfies R14 two ways at once: the list is browsable with an empty query, most recently played first, for someone who cannot recall how they spelled their name, and typing filters it fuzzily for someone who can nearly recall it. A name that is not in the list is offered for creation, which is also the whole of the first-run path (R12).
func NewPicker ¶
NewPicker returns the profile chooser. The prompt is the question at the top of the screen; an empty one falls back to the launch wording.
Once a name is chosen the picker replaces itself with the main menu, which is what launching the program does. Chosen overrides that for a caller that wants the name for something else.
func (*Picker) Cancelled ¶
Cancelled sets what escape does. The picker has no answer of its own: at launch it is the first screen, and a program that ends on one stray escape is worse than one that ignores the key. Setting this is therefore what puts "esc back" on the status line as well, so the offer and the key arrive together.
func (*Picker) Chosen ¶
Chosen replaces what happens once a name has been picked. It is how the picker is embedded in another screen: the command can emit a message that screen handles instead of leaving the picker.
type ReplayScreen ¶
type ReplayScreen struct {
// contains filtered or unexported fields
}
ReplayScreen walks through a finished game one move at a time.
Positions are materialised up front by replaying the record, rather than by undoing from the end. Replaying forwards is the same path a saved game takes when it is loaded, so a record that cannot be replayed is refused here too instead of appearing to work until the player steps back through it.
type Screen ¶
Screen is one place the player can be. Screens are Bubble Tea models; they hand control back by emitting a DoneMsg rather than by returning tea.Quit, so that the shell decides what happens next.
func NewGameScreen ¶
func NewGameScreen(d Deps, cfg GameConfig) (Screen, error)
NewGameScreen builds the screen for one game. Both sides must have a seat and at least one of them must be played at this keyboard. A remote seat is driven either by a live session or, in a correspondence game, by the move codes the players exchange by hand; it needs exactly one of the two.
func NewReplayScreen ¶
NewReplayScreen prepares a stored game for review.
func NewTutorialScreen ¶
NewTutorialScreen opens the interactive tutorial. An empty lessonID starts at the lesson chooser; a lesson id starts that lesson, and leaving it then leaves the screen rather than dropping into a chooser the player never saw.
type Seat ¶
type Seat struct {
// Profile is the local player's name, empty for a bot or a remote opponent.
Profile string
// Bot is the opponent engine, nil unless this seat is a bot.
Bot bot.Bot
// Remote marks a seat driven by a networked opponent.
Remote bool
// Label is what the interface calls this seat.
Label string
}
Seat says who is playing one side of a game.
type Shell ¶
type Shell struct {
// contains filtered or unexported fields
}
Shell is the root Bubble Tea model: it owns the stack of screens, the terminal size, the alternate screen and the error banner, and it is the only thing in the program that calls tea.Quit.
Screens hand control back with a DoneMsg instead of quitting or pushing themselves, so the rule for what happens when a screen finishes lives in one place and a screen can be reached from more than one place without knowing where it will return to.
func (*Shell) Push ¶
Push shows sc on top of the current screen, which stays on the stack and is revealed again when sc finishes.