Live Leaderboard & the Finish Line
The race is moving, every horse's position in the skip list stays correct frame to frame. Now use that to show the standings live, and to actually end the race.
Rendering the leaderboard
void drawLeaderboard(sf::RenderWindow& window, SkipList& track, sf::Font& font) {
std::vector<int> order = track.leaderboard(); // horse IDs, 1st place first
for (std::size_t rank = 0; rank < order.size(); rank++) {
sf::Text entry;
entry.setFont(font);
entry.setCharacterSize(16);
entry.setFillColor(sf::Color::White);
entry.setPosition(970.f, 20.f + rank * 22.f);
entry.setString(std::to_string(rank + 1) + ". Horse " + std::to_string(order[rank] + 1));
window.draw(entry);
}
}
track.leaderboard() does all the real work, it's the level-0 traversal from two lessons ago, called once per frame. Nothing here re-sorts anything, the list was already kept sorted the entire time updateHorsePosition was running, this function's only job is turning that order into text on screen.
Detecting the finish line
const float FINISH_LINE = 800.f;
int checkForWinner(std::vector<Horse>& horses) {
for (auto& horse : horses) {
if (horse.distance >= FINISH_LINE) {
return horse.id;
}
}
return -1; // nobody has finished yet
}
This loops horses directly rather than the skip list, since the question here is "has any horse crossed FINISH_LINE", not "who's currently ahead", a plain scan is simpler and just as fast for a handful of horses. Once a winner is found, stop calling updateHorsePosition for every horse (freeze the race) and display the result.
bool raceOver = false;
int winnerId = -1;
// inside the game loop, once per frame:
if (!raceOver) {
for (auto& horse : horses) {
updateHorsePosition(track, horse);
updateHorseSprite(horse);
}
winnerId = checkForWinner(horses);
if (winnerId != -1) {
raceOver = true;
}
}
drawLeaderboard(window, track, font);
if (raceOver) {
sf::Text winnerText;
winnerText.setFont(font);
winnerText.setCharacterSize(28);
winnerText.setFillColor(sf::Color::Yellow);
winnerText.setPosition(300.f, 10.f);
winnerText.setString("Horse " + std::to_string(winnerId + 1) + " wins!");
window.draw(winnerText);
}
Guarding the movement update with if (!raceOver) freezes every horse exactly where it was the instant someone crossed the line, while drawLeaderboard and the window's own clear/display calls keep running either way, so the final standings and the winner banner both stay visible instead of the screen going blank.
TIP
Because checkForWinner scans in id order, not skip list order, two horses crossing on the exact same frame would always report the lower id as the winner. Detecting a genuine photo finish (comparing exact distances, not just who's checked first) is a good stretch goal for the final project.