Show a graph of the game on the Winner popup
Build / test (push) Successful in 38s
Build / build (push) Successful in 1m12s

The Winner popup now shows every player's score after each song as a
step line, with the winning score marked, and three facts under it: the
longest streak, the longest standstill and the song most players scored
on. The data comes from the points each song already records, through
the new ScoreHistory. The popup grows to 1600x920 with the graph across
the full width, so a 60-song game with five players stays readable.

Reset points now starts a new game: it clears the points on the songs
and the graph starts after the song that was playing. Songs already on
the server at startup count as an earlier game.

The screenshot tour has a long five-player game, and only the Winner
popup differs from before.
This commit is contained in:
Sansan committed 2026-10-07 10:40:46 +02:00
1 parent b40b160fde
commit e1e05f3904
21 files changed
+2461 -182

No files matched your search

+10
View File
@@ -11,6 +11,9 @@ var first_song_downloaded: bool = false
var first_song_played: bool = false
var number_of_tracks_to_preload: int = 1
var is_song_downloading: bool = false
## The first song of the current game. Songs before it belong to earlier games, which
## ended with Reset points.
var game_start_song: int = 0
func get_next_song() -> SongObject:
@@ -68,6 +71,7 @@ func get_playlist() -> Array[SongObject]:
func reset_playlist() -> void:
currently_playing_song = -1
latest_played_song = 0
game_start_song = 0
clear_song_list()
@@ -100,6 +104,12 @@ func remove_point(player_id: String) -> void:
song_object_array[currently_playing_song].remove_point(player_id)
## Removes every player's points from every song, for a new game.
func clear_points() -> void:
for song_object: SongObject in song_object_array:
song_object.players_given_point.clear()
## Removes a player's points from every song.
func remove_player_from_list(player_id: String) -> void:
for song_object: SongObject in song_object_array:
+121
View File
@@ -0,0 +1,121 @@
class_name ScoreHistory
extends RefCounted
## Who got a point on which song of the current game, for the graph and the summary on
## the Winner popup.
##
## Songs are counted from 1 at the start of the game, which is the start of the playlist
## or the first song after the last Reset points.
## The players, in the order of [code]Settings.player_array[/code].
var players: Array[PlayerObject] = []
## For each song of the game, in the order they were played, the ids of the players who
## got a point on it.
var songs: Array[Array] = []
## The current game, from [code]Playlist.game_start_song[/code] up to the last song that
## has played. Songs downloaded ahead but not played yet are left out, and so are points
## of players who are no longer in [param game_players].
static func from_playlist(game_players: Array[PlayerObject]) -> ScoreHistory:
var song_scorers: Array[Array] = []
# Not just the song playing now: an older song may have been picked in the music list
var last_song: int = mini(Playlist.currently_playing_song,
Playlist.song_object_array.size() - 1)
for song_no: int in Playlist.song_object_array.size():
if Playlist.song_object_array[song_no].has_played:
last_song = maxi(last_song, song_no)
for song_no: int in range(Playlist.game_start_song, last_song + 1):
var song: SongObject = Playlist.song_object_array[song_no]
if song.has_played || song_no == Playlist.currently_playing_song:
song_scorers.append(song.players_given_point)
return from_songs(game_players, song_scorers)
## A history of [param song_scorers], the ids of the players who scored on each song.
static func from_songs(game_players: Array[PlayerObject], song_scorers: Array) -> ScoreHistory:
var history: ScoreHistory = ScoreHistory.new()
history.players = game_players.duplicate()
var ids: Array[String] = []
for player: PlayerObject in game_players:
ids.append(player.id)
for scorers: Array in song_scorers:
history.songs.append(scorers.filter(func(id: String) -> bool: return ids.has(id)))
return history
## [param player_id]'s score before the first song and after each song.
func scores_after_songs(player_id: String) -> Array[int]:
var scores: Array[int] = [0]
for scorers: Array in songs:
scores.append(scores.back() + (1 if scorers.has(player_id) else 0))
return scores
## [param player_id]'s score after the last song.
func final_score(player_id: String) -> int:
return scores_after_songs(player_id).back()
## The player with the most songs in a row with a point, e.g.
## "Crono, 5 songs in a row (14–18)". The earliest streak wins a tie.
func longest_streak_text() -> String:
if songs.is_empty():
return "No songs played"
var best_length: int = 0
var best_start: int = 0
var best_player: PlayerObject = null
for player: PlayerObject in players:
var length: int = 0
for song: int in songs.size():
length = length + 1 if songs[song].has(player.id) else 0
var start: int = song - length + 1
if length > best_length || (length == best_length && length > 0 \
&& start < best_start):
best_length = length
best_start = start
best_player = player
if best_length < 2:
return "Nobody scored twice in a row"
return "%s, %d songs in a row (%d–%d)" % [best_player.player_name, best_length,
best_start + 1, best_start + best_length]
## The most songs in a row where nobody scored, e.g. "Songs 6–10, nobody scored".
func longest_standstill_text() -> String:
if songs.is_empty():
return "No songs played"
var best_length: int = 0
var best_start: int = 0
var length: int = 0
for song: int in songs.size():
length = length + 1 if songs[song].is_empty() else 0
if length > best_length:
best_length = length
best_start = song - length + 1
if best_length == 0:
return "Someone scored on every song"
if best_length == 1:
return "Song %d, nobody scored" % (best_start + 1)
return "Songs %d–%d, nobody scored" % [best_start + 1, best_start + best_length]
## The song most players scored on, e.g. "Song 11, Marle and Lucca", or
## "Song 11, 3 players" when more than two did. The earliest song wins a tie.
func most_shared_text() -> String:
if songs.is_empty():
return "No songs played"
var best_song: int = -1
for song: int in songs.size():
if songs[song].size() >= 2 \
&& (best_song < 0 || songs[song].size() > songs[best_song].size()):
best_song = song
if best_song < 0:
return "No song had two scorers"
var scorers: Array = songs[best_song]
if scorers.size() > 2:
return "Song %d, %d players" % [best_song + 1, scorers.size()]
var names: PackedStringArray = []
for player: PlayerObject in players:
if scorers.has(player.id):
names.append(player.player_name)
return "Song %d, %s" % [best_song + 1, " and ".join(names)]
+1
View File
@@ -0,0 +1 @@
uid://dcvwgwhlu80qh