Maelstrom: Now we can actually base the high score list off the replays, which is nice because we'll be able to let the player replay...

https://github.com/libsdl-org/Maelstrom/commit/6436f1a536cc6871e392e58ee21aa63cbf3ebee9

From 6436f1a536cc6871e392e58ee21aa63cbf3ebee9 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 21 Nov 2011 00:53:13 -0500
Subject: [PATCH] Now we can actually base the high score list off the replays,
 which is nice because we'll be able to let the player replay any of the games
 on the high score list. It also means that we can let people pass each other
 their files and merge their scores into each other's high score lists.

---
 game/Maelstrom_Globals.h |   2 +-
 game/game.cpp            |  51 +++++++-------
 game/gameinfo.cpp        |   8 +++
 game/gameinfo.h          |   1 +
 game/init.cpp            |   1 +
 game/main.cpp            |   2 +-
 game/replay.cpp          |  14 ++--
 game/replay.h            |   9 ++-
 game/scores.cpp          | 139 +++++++++++++++++++++++----------------
 game/scores.h            |  15 +++--
 10 files changed, 145 insertions(+), 97 deletions(-)

diff --git a/game/Maelstrom_Globals.h b/game/Maelstrom_Globals.h
index 5a466331..940524e3 100644
--- a/game/Maelstrom_Globals.h
+++ b/game/Maelstrom_Globals.h
@@ -143,7 +143,7 @@ extern Controls	controls;
 extern PrefsVariable<int> gSoundLevel;
 extern PrefsVariable<int> gGammaCorrect;
 // int scores.cpp :
-extern Scores	hScores[];
+extern Scores	hScores[NUM_SCORES];
 
 // -- Variables specific to each game 
 // in game.cpp : 
diff --git a/game/game.cpp b/game/game.cpp
index 7bc3bc51..5cd25162 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -1055,20 +1055,19 @@ static void DoGameOver(void)
 		Delay(SOUND_DELAY);
 
 	/* -- See if they got a high score */
-	LoadScores();
-	for ( i = 0; i<10; ++i ) {
-		if ( TheShip->GetScore() >= (int)hScores[i].score ) {
-			which = i;
-			break;
+	if (gReplay.IsRecording() && !gGameInfo.IsMultiplayer() &&
+	    (gGameInfo.wave == 1) && (gGameInfo.lives == 3)) {
+		for ( i = 0; i<NUM_SCORES; ++i ) {
+			if ( TheShip->GetScore() >= (int)hScores[i].score ) {
+				which = i;
+				break;
+			}
 		}
 	}
 
-	/* -- They got a high score! */
 	gLastHigh = which;
 
-	if ((which != -1) &&
-			!gGameInfo.IsMultiplayer() && !gReplay.IsPlaying() &&
-			(gGameInfo.wave == 1) && (gGameInfo.lives == 3)) {
+	if (which != -1) {
 		sound->PlaySound(gBonusShot, 5);
 
 		/* -- Let them enter their name */
@@ -1133,34 +1132,34 @@ static void DoGameOver(void)
 		screen->DisableTextInput();
 
 		if (*handle) {
-			for ( i = 8; i >= which ; --i ) {
-				hScores[i + 1].score = hScores[i].score;
-				hScores[i + 1].wave = hScores[i].wave;
-				strcpy(hScores[i+1].name, hScores[i].name);
-			}
-			hScores[which].wave = gWave;
-			hScores[which].score = TheShip->GetScore();
-			strcpy(hScores[which].name, handle);
+			GameInfo &info = gReplay.GetGameInfo();
+			info.SetPlayerName(gDisplayed, handle);
+			gReplay.Save();
+			LoadScores();
 		}
 
 		sound->HaltSound();
 		sound->PlaySound(gGotPrize, 6);
-		SaveScores();
-	} else
-	if ( gGameInfo.IsMultiplayer() )	/* Let them watch their ranking */
-		SDL_Delay(3000);
 
-	while ( sound->Playing() )
-		Delay(SOUND_DELAY);
-	HandleEvents(0);
+	} else if ( gGameInfo.IsMultiplayer() )	/* Let them watch their ranking */
+		SDL_Delay(3000);
 
-	ui->HidePanel(PANEL_GAMEOVER);
+	if (gReplay.IsRecording()) {
+		// Save this as the last game
+		gReplay.Save(LAST_REPLAY);
+	}
+	gReplay.SetMode(REPLAY_IDLE);
 
 	/* Make sure we clear the game info so we don't crash trying to
 	   update UI in a future replay
 	*/
 	gGameInfo.Reset();
-	gReplay.SetMode(REPLAY_IDLE);
+
+	while ( sound->Playing() )
+		Delay(SOUND_DELAY);
+	HandleEvents(0);
+
+	ui->HidePanel(PANEL_GAMEOVER);
 
 }	/* -- DoGameOver */
 
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 15d78785..4075111a 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -85,6 +85,14 @@ GameInfo::SetPlayerSlot(int slot, const char *name, Uint8 controlMask)
 	UpdateUI(player);
 }
 
+void
+GameInfo::SetPlayerName(int slot, const char *name)
+{
+	GameInfoPlayer *player = &players[slot];
+	SDL_strlcpy(player->name, name ? name : "", sizeof(player->name));
+	UpdateUI(player);
+}
+
 bool
 GameInfo::AddNetworkPlayer(Uint32 nodeID, const IPaddress &address, const char *name)
 {
diff --git a/game/gameinfo.h b/game/gameinfo.h
index b5b320ca..f99e5118 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -117,6 +117,7 @@ class GameInfo
 	void SetHost(const char *name, Uint8 wave, Uint8 lives, Uint8 turbo, Uint8 deathMatch);
 
 	void SetPlayerSlot(int slot, const char *name, Uint8 controlMask);
+	void SetPlayerName(int slot, const char *name);
 	bool AddNetworkPlayer(Uint32 nodeID, const IPaddress &address, const char *name);
 
 	void CopyFrom(const GameInfo &rhs);
diff --git a/game/init.cpp b/game/init.cpp
index cd18d5e3..5db8eb82 100644
--- a/game/init.cpp
+++ b/game/init.cpp
@@ -641,6 +641,7 @@ static void BuildVelocityTable(void)
 */
 void CleanUp(void)
 {
+	FreeScores();
 	SaveControls();
 	if ( spriteres ) {
 		delete spriteres;
diff --git a/game/main.cpp b/game/main.cpp
index 4f41936d..0c1da86c 100644
--- a/game/main.cpp
+++ b/game/main.cpp
@@ -476,7 +476,7 @@ MainPanelDelegate::OnTick()
 	}
 	gUpdateBuffer = false;
 
-	for (int index = 0; index < 10; index++) {
+	for (int index = 0; index < NUM_SCORES; index++) {
 		Uint8 R, G, B;
 
 		if ( gLastHigh == index ) {
diff --git a/game/replay.cpp b/game/replay.cpp
index 51c2c2a0..797096a6 100644
--- a/game/replay.cpp
+++ b/game/replay.cpp
@@ -131,7 +131,7 @@ Replay::Load(const char *file, bool headerOnly)
 
 	// Open the file
 	if (SDL_strchr(file, '/') == NULL) {
-		SDL_snprintf(path, sizeof(path), "%s/%s.%s", REPLAY_DIRECTORY, file, REPLAY_FILETYPE);
+		SDL_snprintf(path, sizeof(path), "%s/%s", REPLAY_DIRECTORY, file);
 		file = path;
 	}
 	fp = PHYSFS_openRead(file);
@@ -230,9 +230,12 @@ Replay::Save(const char *file)
 	// Create the directory if needed
 	PHYSFS_mkdir(REPLAY_DIRECTORY);
 
-	// Open the file
-	if (SDL_strchr(file, '/') == NULL) {
-		SDL_snprintf(path, sizeof(path), "%s/%s.%s", REPLAY_DIRECTORY, file, REPLAY_FILETYPE);
+	// If we aren't given a file, construct one from the game data
+	if (!file) {
+		SDL_snprintf(path, sizeof(path), "%s/%s_%d_%d_%8.8x.%s", REPLAY_DIRECTORY, GetDisplayName(), GetFinalScore(), GetFinalWave(), m_seed, REPLAY_FILETYPE);
+		file = path;
+	} else if (SDL_strchr(file, '/') == NULL) {
+		SDL_snprintf(path, sizeof(path), "%s/%s", REPLAY_DIRECTORY, file);
 		file = path;
 	}
 	fp = PHYSFS_openWrite(file);
@@ -443,7 +446,4 @@ Replay::HandleGameOver()
 		m_finalScore[i].Score = gPlayers[i]->GetScore();
 		m_finalScore[i].Frags = gPlayers[i]->GetFrags();
 	}
-
-	// Save this as the last score
-	Save(LAST_REPLAY);
 }
diff --git a/game/replay.h b/game/replay.h
index 4e514470..f7649439 100644
--- a/game/replay.h
+++ b/game/replay.h
@@ -33,9 +33,9 @@
 // game logic, etc.
 //
 #define REPLAY_VERSION	1
-#define REPLAY_DIRECTORY "Scores"
+#define REPLAY_DIRECTORY "Games"
 #define REPLAY_FILETYPE "mreplay"
-#define LAST_REPLAY	"LastScore"
+#define LAST_REPLAY	"LastGame."REPLAY_FILETYPE
 
 enum REPLAY_MODE {
 	REPLAY_IDLE,
@@ -77,9 +77,12 @@ class Replay
 		// Return the approximage length of the replay, in seconds
 		return (float)m_frameCount / 30.0f;
 	}
+	GameInfo &GetGameInfo() {
+		return m_game;
+	}
 
 	bool Load(const char *file, bool headerOnly = false);
-	bool Save(const char *file);
+	bool Save(const char *file = NULL);
 
 	void HandleNewGame();
 	bool HandlePlayback();
diff --git a/game/scores.cpp b/game/scores.cpp
index 15da5ad3..9c8da28a 100644
--- a/game/scores.cpp
+++ b/game/scores.cpp
@@ -24,85 +24,114 @@
    This file handles the cheat dialogs and the high score file
 */
 
-#ifdef unix
-#include <sys/types.h>
-#include <sys/stat.h>
-#endif
-#include <stdio.h>
+#include <stdlib.h>	// for qsort()
 
-#include "SDL_endian.h"
+#include "physfs.h"
 
 #include "Maelstrom_Globals.h"
-#include "load.h"
+#include "scores.h"
+#include "../utils/array.h"
 
-#define MAELSTROM_SCORES	"Maelstrom-Scores"
-#define NUM_SCORES		10		// Do not change this!
-
-/* Everyone can write to scores file if defined to 0 */
-#define SCORES_PERMMASK		0
 
 Scores hScores[NUM_SCORES];
 
+int SortScores(const void *_a, const void *_b)
+{
+	const Scores *a = (const Scores *)_a;
+	const Scores *b = (const Scores *)_b;
+
+	if (a->score == b->score) {
+		if (a->wave == b->wave) {
+			return SDL_strcmp(a->name, b->name);
+		}
+		return b->wave - a->wave;
+	}
+	return b->score - a->score;
+}
+
 void LoadScores(void)
 {
-	SDL_RWops *scores_src;
+	char path[1024];
+	Replay replay;
+	Scores score;
+	array<Scores> scores;
 	int i;
 
-	memset(&hScores, 0, sizeof(hScores));
+	FreeScores();
 
-	scores_src = PHYSFSRWOPS_openRead(MAELSTROM_SCORES);
-	if ( scores_src != NULL ) {
-		for ( i=0; i<NUM_SCORES; ++i ) {
-			SDL_RWread(scores_src, hScores[i].name,
-			           sizeof(hScores[i].name), 1);
-			hScores[i].wave = SDL_ReadBE32(scores_src);
-			hScores[i].score = SDL_ReadBE32(scores_src);
+	// Load all the games
+	char **rc = PHYSFS_enumerateFiles(REPLAY_DIRECTORY);
+	char **f;
+	for (f = rc; *f; ++f) {
+		if (SDL_strcmp(*f, LAST_REPLAY) == 0) {
+			continue;
+		}
+		if (!replay.Load(*f, true)) {
+			continue;
 		}
-		SDL_RWclose(scores_src);
+
+		SDL_strlcpy(score.name, replay.GetDisplayName(), sizeof(score.name));
+		score.wave = replay.GetFinalWave();
+		score.score = replay.GetFinalScore();
+		score.file = *f;
+		scores.add(score);
+	}
+
+	// Take the top 10
+	if (scores.length() > 0) {
+		qsort(&scores[0], scores.length(), sizeof(scores[0]), SortScores);
 	}
+	for (i = 0; i < scores.length() && i < NUM_SCORES; ++i) {
+		hScores[i] = scores[i];
+		hScores[i].file = SDL_strdup(scores[i].file);
+	}
+
+	// Trim the rest
+	for ( ; i < scores.length(); ++i) {
+		SDL_snprintf(path, sizeof(path), "%s/%s", REPLAY_DIRECTORY, scores[i].file);
+		PHYSFS_delete(path);
+	}
+
+	PHYSFS_freeList(rc);
 }
 
-void SaveScores(void)
+void FreeScores(void)
 {
-	SDL_RWops *scores_src;
+	// Free the existing scores
 	int i;
-#ifdef unix
-	int omask;
-#endif
-
-#ifdef unix
-	omask=umask(SCORES_PERMMASK);
-#endif
-	scores_src = PHYSFSRWOPS_openWrite(MAELSTROM_SCORES);
-	if ( scores_src != NULL ) {
-		for ( i=0; i<NUM_SCORES; ++i ) {
-			SDL_RWwrite(scores_src, hScores[i].name,
-			            sizeof(hScores[i].name), 1);
-			SDL_WriteBE32(scores_src, hScores[i].wave);
-			SDL_WriteBE32(scores_src, hScores[i].score);
+	for (i = 0; i < NUM_SCORES; ++i) {
+		if (hScores[i].file) {
+			SDL_free(hScores[i].file);
 		}
-		SDL_RWclose(scores_src);
-	} else {
-		error("Warning: Couldn't save scores to %s\n",
-						MAELSTROM_SCORES);
 	}
-#ifdef unix
-	umask(omask);
-#endif
+	SDL_zero(hScores);
+
 }
 
 void ZapHighScores(UIDialog *dialog, int status)
 {
-	if (status) {
-		memset(hScores, 0, sizeof(hScores));
-		SaveScores();
-		gLastHigh = -1;
-
-		/* Fade the screen and redisplay scores */
-		screen->FadeOut();
-		Delay(SOUND_DELAY);
-		sound->PlaySound(gExplosionSound, 5);
-		gUpdateBuffer = true;
+	char path[1024];
+
+	if (!status) {
+		return;
+	}
+
+	// Delete all the games
+	char **rc = PHYSFS_enumerateFiles(REPLAY_DIRECTORY);
+	char **f;
+	for (f = rc; *f; ++f) {
+		SDL_snprintf(path, sizeof(path), "%s/%s", REPLAY_DIRECTORY, *f);
+		PHYSFS_delete(path);
 	}
+	PHYSFS_freeList(rc);
+
+	FreeScores();
+	gLastHigh = -1;
+
+	/* Fade the screen and redisplay scores */
+	screen->FadeOut();
+	Delay(SOUND_DELAY);
+	sound->PlaySound(gExplosionSound, 5);
+	gUpdateBuffer = true;
 }
 
diff --git a/game/scores.h b/game/scores.h
index 4b103b73..361d8146 100644
--- a/game/scores.h
+++ b/game/scores.h
@@ -20,17 +20,24 @@
     slouken@libsdl.org
 */
 
+#ifndef _scores_h
+#define _scores_h
+
 class UIDialog;
 
-// Functions from scores.cc
-extern void	LoadScores(void);
-extern void	SaveScores(void);
-extern void	ZapHighScores(UIDialog *dialog, int status);
+// Functions from scores.cpp
+extern void LoadScores(void);
+extern void FreeScores(void);
+extern void ZapHighScores(UIDialog *dialog, int status);
 
 /* The high scores structure */
 typedef	struct {
 	char name[20];
 	Uint32 wave;
 	Uint32 score;	
+	char *file;
 } Scores;
 
+#define NUM_SCORES	10
+
+#endif // _scores_h