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