nsnake
Classic snake game for the terminal
Loading...
Searching...
No Matches
ScoreFile.cpp
1#include <Entities/ScoreFile.hpp>
2#include <Entities/BoardParser.hpp>
3#include <Engine/Helpers/File.hpp>
4#include <Engine/Helpers/String.hpp>
5#include <Engine/Helpers/INI.hpp>
6#include <Engine/Helpers/Base64.hpp>
7
8#include <stdlib.h> // getenv()
9#include <fstream> // ofstream
10
11// HACK This will be initialized at `Globals::init()`
12std::string ScoreFile::directory = "";
13
14std::string ScoreFile::extension = "nsnakescores";
15
16
18 points(0),
19 speed(0),
20 level(""),
21 fruits(0),
22 random_walls(false),
23 teleport(false),
24 board_size(Globals::Game::LARGE),
25 board_scroll_delay(0),
26 board_scroll_left(false),
27 board_scroll_right(false),
28 board_scroll_up(false),
29 board_scroll_down(false)
30{ }
31
33{
34 if (this->level != other.level)
35 return false;
36
37 // First thing we gotta check is if this score
38 // was made on Arcade Mode (level is empty)
39 //
40 // If that's the case, then we care for the
41 // board size
42 if (this->level.empty())
43 {
44 return (this->fruits == other.fruits &&
45 this->random_walls == other.random_walls &&
46 this->teleport == other.teleport &&
47 this->speed == other.speed &&
48 this->board_scroll_delay == other.board_scroll_delay &&
49 this->board_scroll_left == other.board_scroll_left &&
50 this->board_scroll_right == other.board_scroll_right &&
51 this->board_scroll_up == other.board_scroll_up &&
52 this->board_scroll_down == other.board_scroll_down &&
53 this->board_size == other.board_size);
54 }
55
56 // If not, the board size is not important, since levels
57 // can have any size.
58 return (this->fruits == other.fruits &&
59 this->random_walls == other.random_walls &&
60 this->teleport == other.teleport &&
61 this->speed == other.speed &&
62 this->board_scroll_delay == other.board_scroll_delay &&
63 this->board_scroll_left == other.board_scroll_left &&
64 this->board_scroll_right == other.board_scroll_right &&
65 this->board_scroll_up == other.board_scroll_up &&
66 this->board_scroll_down == other.board_scroll_down);
67}
68
69
70
71
72
74{
75 // 1. Delete the arcade score fileerase this one
76 // 2. Lists all files under the score dir and erase
77 // the ones ending with a score extension
78
79 Utils::File::rm_f(Globals::Config::scoresFile);
80
81 std::vector<std::string> files = Utils::File::ls(ScoreFile::directory);
82
83 for (size_t i = 0; i < files.size(); i++)
84
85 if (Utils::File::extension(files[i]) == ScoreFile::extension)
86 Utils::File::rm_f(files[i]);
87}
88
89
90
91
92
93ScoreFile::ScoreFile(std::string levelName):
94 highScore(NULL),
95 level_name(levelName)
96{ }
97
99{
100 // Make it point nowhere, since we're refreshing
101 // the score entries.
102 this->highScore = NULL;
103
104 // Score files are dependent of the level name.
105 std::string score_file = (ScoreFile::directory +
106 this->level_name +
107 "." +
109
110 // Will fall back to default high score file
111 // (Arcade Mode) if no level was specified
112 if (this->level_name.empty())
113 score_file = Globals::Config::scoresFile;
114
115 if (! Utils::File::exists(score_file))
116 throw ScoreFileException("File '" + score_file + "' doesn't exist");
117
118 // Reading whole file's contents into a buffer
119 std::ifstream file;
120 file.open(score_file.c_str());
121
122 std::stringstream buffer;
123 buffer << file.rdbuf();
124 file.close();
125
126 std::stringstream contents;
127 contents << Utils::Base64::decode(buffer.str());
128
129 // Parsing file's contents as INI
130 INI::Parser ini(contents);
131
132 // If it's a score file from a different major version,
133 // how should we react?
134 // No need to worry about minor versions.
135 std::string version = ini["version"];
136
137 if (version[0] != Globals::version[MAJOR])
138 {
139 // Compare versions, lower, higher, whatever...
140 Globals::Error::old_version_score_file = true;
141
142 throw ScoreFileException("File '" + score_file + "' has an old version format");
143 }
144
145 // Going through each group on the INI file
146 // (each score the user had)
147 for (INI::Level::Sections::const_iterator it = ini.top().ordered_sections.begin();
148 it != ini.top().ordered_sections.end();
149 ++it)
150 {
151 // This is SOO ugly!
152 // We should NOT have to worry about INI parser's internals!
153 INI::Level ini_score = (*it)->second;
154
155 ScoreEntry entry;
156 entry.level = ini_score["level"];
157 entry.points = Utils::String::to<unsigned int>(ini_score["points"]);
158 entry.speed = Utils::String::to<unsigned int>(ini_score["speed"]);
159 entry.fruits = Utils::String::to<int>(ini_score["fruits"]);
160 entry.random_walls = Utils::String::to<bool>(ini_score["random_walls"]);
161 entry.teleport = Utils::String::to<bool>(ini_score["teleport"]);
162
163 entry.board_scroll_delay = Utils::String::to<int>(ini_score["board_scroll_delay"]);
164 entry.board_scroll_left = Utils::String::to<bool>(ini_score["board_scroll_left"]);
165 entry.board_scroll_right = Utils::String::to<bool>(ini_score["board_scroll_right"]);
166 entry.board_scroll_up = Utils::String::to<bool>(ini_score["board_scroll_up"]);
167 entry.board_scroll_down = Utils::String::to<bool>(ini_score["board_scroll_down"]);
168
169 int board_size = Utils::String::to<int>(ini_score["board_size"]);
170 entry.board_size = Globals::Game::intToBoardSize(board_size);
171
172 this->entries.push_back(entry);
173 }
174
175 // Finally, we have to pick the highest score
176 // according to these game settings.
177 ScoreEntry tmp_score;
178 tmp_score.level = this->level_name;
179 tmp_score.speed = Globals::Game::starting_speed;
180 tmp_score.fruits = Globals::Game::fruits_at_once;
181 tmp_score.random_walls = Globals::Game::random_walls;
182 tmp_score.teleport = Globals::Game::teleport;
183 tmp_score.board_size = Globals::Game::board_size;
184 tmp_score.board_scroll_delay = Globals::Game::board_scroll_delay;
185 tmp_score.board_scroll_left = Globals::Game::board_scroll_left;
186 tmp_score.board_scroll_right = Globals::Game::board_scroll_right;
187 tmp_score.board_scroll_up = Globals::Game::board_scroll_up;
188 tmp_score.board_scroll_down = Globals::Game::board_scroll_down;
189
190 for (size_t i = 0; i < (this->entries.size()); i++)
191 {
192 if (tmp_score.isLike(this->entries[i]))
193 {
194 this->highScore = &(this->entries[i]);
195 break;
196 }
197 }
198 if (this->highScore == NULL)
199 {
200 this->entries.push_back(tmp_score);
201 this->highScore = &(this->entries[this->entries.size() - 1]);
202 }
203}
205{
206 // Score files are dependent of the level name.
207 std::string score_file = (ScoreFile::directory +
208 this->level_name +
209 "." +
211
212 // Will fall back to default high score file
213 // if no level was specified
214 if (this->level_name.empty())
215 score_file = Globals::Config::scoresFile;
216
217 // Tries to create file if it doesn't exist.
218 // If we can't create it at all let's just give up.
219 if (! Utils::File::exists(score_file))
220 {
221 Utils::File::create(score_file);
222
223 if (! Utils::File::exists(score_file))
224 throw ScoreFileException("Could not create file '" + score_file + "'");
225 }
226
227 // We'll recreate the whole score file from scratch
228 INI::Parser ini;
229 ini.create();
230 ini.top().addKey("version", std::string(VERSION));
231
232 // Adding each score entry on the file
233 for (size_t i = 0; i < (this->entries.size()); i++)
234 {
235 std::string score_name = "score" + Utils::String::toString(i);
236
237 ini.top().addGroup(score_name);
238
239 ini(score_name).addKey("level", this->entries[i].level);
240 ini(score_name).addKey("points", Utils::String::toString(this->entries[i].points));
241 ini(score_name).addKey("speed", Utils::String::toString(this->entries[i].speed));
242 ini(score_name).addKey("fruits", Utils::String::toString(this->entries[i].fruits));
243
244 ini(score_name).addKey("random_walls", Utils::String::toString(this->entries[i].random_walls));
245 ini(score_name).addKey("teleport", Utils::String::toString(this->entries[i].teleport));
246
247 int board_size = Globals::Game::boardSizeToInt(this->entries[i].board_size);
248 ini(score_name).addKey("board_size", Utils::String::toString(board_size));
249
250 ini(score_name).addKey("board_scroll_delay", Utils::String::toString(this->entries[i].board_scroll_delay));
251 ini(score_name).addKey("board_scroll_left", Utils::String::toString(this->entries[i].board_scroll_left));
252 ini(score_name).addKey("board_scroll_right", Utils::String::toString(this->entries[i].board_scroll_right));
253 ini(score_name).addKey("board_scroll_up", Utils::String::toString(this->entries[i].board_scroll_up));
254 ini(score_name).addKey("board_scroll_down", Utils::String::toString(this->entries[i].board_scroll_down));
255 }
256
257 std::stringstream contents;
258 ini.dump(contents);
259
260 std::ofstream file;
261 file.open(score_file.c_str());
262 file << Utils::Base64::encode(contents.str());
263 file.close();
264}
266{
267 // No high score until now, we've made it!
268 if (! this->highScore)
269 {
270 this->entries.push_back(*score);
271 this->highScore = &(this->entries[this->entries.size() - 1]);
272 return true;
273 }
274
275 // Wrong game settings?
276 if (! score->isLike(*this->highScore))
277 return false;
278
279 if ((score->points) > (this->highScore->points))
280 {
281 this->highScore->points = score->points;
282
283 return true;
284 }
285 return false;
286}
287
Definition Game.hpp:17
Custom exception class to specify an error that occurred during a level loading.
Definition ScoreFile.hpp:14
static std::string directory
Default directory where we store the game score files.
Definition ScoreFile.hpp:97
bool handle(ScoreEntry *score)
Checks if #score is the highest score and make it so.
ScoreFile(std::string levelName)
Creates a new score handler for the level #levelName.
Definition ScoreFile.cpp:93
static void eraseAll()
Erases all high score files.
Definition ScoreFile.cpp:73
void load()
Loads all high score entries based on a level name.
Definition ScoreFile.cpp:98
ScoreEntry * highScore
Maximum high score obtained for the current game.
static std::string extension
Default extension to save the score files.
void save()
Saves all the current scores on the file.
Container for global settings on the game.
Definition Globals.hpp:31
char version[3]
Game version (format MMP - Major Minor Patch).
Definition Globals.cpp:15
A single entry on the high-score file.
Definition ScoreFile.hpp:28
bool random_walls
If random walls were spawned on this level.
Definition ScoreFile.hpp:43
bool isLike(ScoreEntry &other)
Tells if both scores were made on exact same game settings.
Definition ScoreFile.cpp:32
std::string level
On which level the user made this score.
Definition ScoreFile.hpp:37
int fruits
How many fruits at once were allowed on this level.
Definition ScoreFile.hpp:40
bool teleport
If teleport was enabled on this level.
Definition ScoreFile.hpp:46
unsigned int speed
Under which game speed the score was made.
Definition ScoreFile.hpp:33
unsigned int points
How many points the user got.
Definition ScoreFile.hpp:30
ScoreEntry()
Creates an empty score entry.
Definition ScoreFile.cpp:17
Globals::Game::BoardSize board_size
How large was the game board on this score.
Definition ScoreFile.hpp:52