nsnake
Classic snake game for the terminal
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()`
12 std::string ScoreFile::directory = "";
13 
14 std::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 
93 ScoreFile::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 
ScoreEntry * highScore
Maximum high score obtained for the current game.
Definition: ScoreFile.hpp:148
static std::string extension
Default extension to save the score files.
Definition: ScoreFile.hpp:105
int fruits
How many fruits at once were allowed on this level.
Definition: ScoreFile.hpp:40
std::string level
On which level the user made this score.
Definition: ScoreFile.hpp:37
Custom exception class to specify an error that occurred during a level loading.
Definition: ScoreFile.hpp:13
ScoreEntry()
Creates an empty score entry.
Definition: ScoreFile.cpp:17
bool random_walls
If random walls were spawned on this level.
Definition: ScoreFile.hpp:43
Globals::Game::BoardSize board_size
How large was the game board on this score.
Definition: ScoreFile.hpp:52
char version[3]
Game version (format MMP - Major Minor Patch).
Definition: Globals.cpp:15
void save()
Saves all the current scores on the file.
Definition: ScoreFile.cpp:204
bool teleport
If teleport was enabled on this level.
Definition: ScoreFile.hpp:46
Definition: Game.hpp:16
static std::string directory
Default directory where we store the game score files.
Definition: ScoreFile.hpp:97
void load()
Loads all high score entries based on a level name.
Definition: ScoreFile.cpp:98
A single entry on the high-score file.
Definition: ScoreFile.hpp:27
static void eraseAll()
Erases all high score files.
Definition: ScoreFile.cpp:73
Container for global settings on the game.
Definition: Globals.hpp:30
bool handle(ScoreEntry *score)
Checks if #score is the highest score and make it so.
Definition: ScoreFile.cpp:265
bool isLike(ScoreEntry &other)
Tells if both scores were made on exact same game settings.
Definition: ScoreFile.cpp:32
unsigned int points
How many points the user got.
Definition: ScoreFile.hpp:30
unsigned int speed
Under which game speed the score was made.
Definition: ScoreFile.hpp:33
ScoreFile(std::string levelName)
Creates a new score handler for the level #levelName.
Definition: ScoreFile.cpp:93