package scoreboard import ( "fmt" "slices" "strings" ) // Scoreboard represents a scoreboard that may be sent to a player. The scoreboard is shown on the right side // of the player's screen. // Scoreboard implements the io.Writer and io.StringWriter interfaces. fmt.Fprintf and fmt.Fprint may be used // to write formatted text to the scoreboard. type Scoreboard struct { name string lines []string padding bool descending bool } // New returns a new scoreboard with the display name passed. Once returned, lines may be added to the // scoreboard to add text to it. The name is formatted according to the rules of fmt.Sprintln. // Changing the scoreboard after sending it to a player will not update the scoreboard of the player // automatically: Player.SendScoreboard() must be called again to update it. func New(name ...any) *Scoreboard { return &Scoreboard{name: strings.TrimSuffix(fmt.Sprintln(name...), "\n"), padding: true} } // Name returns the display name of the scoreboard, as passed during the construction of the scoreboard. func (board *Scoreboard) Name() string { return board.name } // Write writes a slice of data as text to the scoreboard. Newlines may be written to create a new line on // the scoreboard. func (board *Scoreboard) Write(p []byte) (n int, err error) { return board.WriteString(string(p)) } // WriteString writes a string of text to the scoreboard. Newlines may be written to create a new line on // the scoreboard. func (board *Scoreboard) WriteString(s string) (n int, err error) { lines := strings.Split(s, "\n") board.lines = append(board.lines, lines...) // Scoreboards can have up to 15 lines. (16 including the title.) if len(board.lines) >= 15 { return len(lines), fmt.Errorf("write scoreboard: maximum of 15 lines of text exceeded") } return len(lines), nil } // Set changes a specific line in the scoreboard and adds empty lines until this index is reached. Set panics if the // index passed is negative or 15+. func (board *Scoreboard) Set(index int, s string) { if index < 0 || index >= 15 { panic(fmt.Sprintf("index out of range %v", index)) } if diff := index - (len(board.lines) - 1); diff > 0 { board.lines = append(board.lines, make([]string, diff)...) } // Remove new lines from the string board.lines[index] = strings.TrimSuffix(strings.TrimSuffix(s, "\n"), "\n") } // Remove removes a specific line from the scoreboard. Remove panics if the index passed is negative or 15+. func (board *Scoreboard) Remove(index int) { if index < 0 || index >= 15 { panic(fmt.Sprintf("index out of range %v", index)) } board.lines = append(board.lines[:index], board.lines[index+1:]...) } // RemovePadding removes the padding of one space that is added to the start of every line. func (board *Scoreboard) RemovePadding() { board.padding = false } // Lines returns the data of the Scoreboard as a slice of strings. func (board *Scoreboard) Lines() []string { lines := slices.Clone(board.lines) if board.padding { for i, line := range lines { if len(board.name)-len(line)-2 <= 0 { lines[i] = " " + line + " " continue } lines[i] = " " + line + strings.Repeat(" ", len(board.name)-len(line)-2) } } if board.descending { slices.Reverse(lines) } return lines } // Descending returns whether the scoreboard is sorted in descending order. func (board *Scoreboard) Descending() bool { return board.descending } // SetDescending sets the scoreboard sort order to descending. // When sending the scoreboard to the player, it will reverse the order of the lines to match when it's ascending. func (board *Scoreboard) SetDescending() { board.descending = true }