nsnake
Classic snake game for the terminal
BoardParser.cpp
1 #include <Entities/BoardParser.hpp>
2 #include <Config/Globals.hpp>
3 #include <Engine/Helpers/INI.hpp>
4 #include <Engine/Helpers/File.hpp>
5 #include <Engine/Helpers/String.hpp>
6 
7 #include <fstream>
8 #include <vector>
9 #include <string>
10 
11 // HACK This will be initialized at `Globals::init()`
12 std::string BoardParser::directory = "";
13 
14 std::string BoardParser::extension = "nsnake";
15 
16 
17 
18 
19 Board* BoardParser::load(std::string name)
20 {
21  std::string filename = (BoardParser::directory +
22  name +
23  "." +
25 
26  return BoardParser::loadFile(filename);
27 }
28 
29 Board* BoardParser::loadFile(std::string filename)
30 {
31  std::ifstream file(filename.c_str());
32 
33  if (!(file.is_open()))
34  throw BoardParserException("Can't open file '" + filename + "'");
35 
36  // Tells what's the current line on the file
37  // (independent of comments and empty lines)
38  int line_count = 0;
39 
40  // The whole file has two parts: metadata and level definition.
41  //
42  // Let's read the whole file, line by line, adding the respective
43  // parts to each of these buffers.
44  //
45  // We'll handle them separately later.
46  std::string metadata_buffer;
47  std::string level_buffer;
48 
49  // This will get used to the end of the
50  // function.
51  std::string current_line = "";
52 
53  while (std::getline(file, current_line))
54  {
55  ++line_count;
56 
57  current_line = Utils::String::trim(current_line);
58 
59  // We only care for the line that tells a level
60  // definition will start.
61  if (current_line != "start")
62  metadata_buffer += (current_line + '\n');
63 
64  else
65  {
66  // Yay, start of the level definition!
67  bool parsed_level = false;
68 
69  while (std::getline(file, current_line))
70  {
71  ++line_count;
72 
73  current_line = Utils::String::trim(current_line);
74 
75  if (current_line == "end")
76  {
77  parsed_level = true;
78  break;
79  }
80 
81  level_buffer += (current_line + '\n');
82  }
83 
84  if (! parsed_level)
85  {
86  // End-of-file...
87  // Something wrong happened
89  "Abrupt ending of file while parsing level at line " +
90  Utils::String::toString(line_count)
91  );
92  }
93  // Finished parsing the level!
94  // Back to the metadata.
95  }
96  }
97 
98  // Now we'll analyze the level definition we just got from the file
99 
100  int player_start_x = 1; // It's (1, 1) because if it somehow starts
101  int player_start_y = 1; // at (0, 0) it will always end up in a wall
102  // and die right at the beginning
103 
104  // Finally, when we read the level we have
105  // two states for each tile - "wall" or "not wall"
106  std::vector<std::vector<bool> > rawBoard;
107 
108 
109  std::vector<std::string> level_lines = Utils::String::split(level_buffer, '\n');
110 
111  for (size_t i = 0; i < level_lines.size(); ++i)
112  level_lines[i] = Utils::String::trim(level_lines[i]);
113 
114  for (size_t j = 0; j < (level_lines.size()); j++)
115  {
116  current_line = level_lines[j];
117 
118  if (current_line.empty())
119  continue;
120 
121  std::vector<bool> rawBoardLine;
122 
123  // And now we go through each char on the line
124  // checking if it's a wall, blank space or the
125  // player's starting point.
126  //
127  for (size_t i = 0; i < current_line.size(); i++)
128  {
129  if (current_line[i] == SNAKE_CHAR)
130  {
131  player_start_x = i;
132  player_start_y = rawBoard.size();
133 
134  // It IS an empty space, after all...
135  rawBoardLine.push_back(false);
136  }
137  else
138  rawBoardLine.push_back(current_line[i] == WALL_CHAR);
139  }
140 
141  // Commit this line to the level
142  rawBoard.push_back(rawBoardLine);
143  }
144 
145  // I know it's counter-intuitive, but the width
146  // and height is just like this
147  int board_width = rawBoard[0].size();
148  int board_height = rawBoard.size();
149 
150  Board* board = new Board(board_width,
151  board_height,
152  ((Globals::Game::teleport) ?
153  Board::TELEPORT :
154  Board::SOLID));
155 
156  board->setBoard(rawBoard);
157 
158  board->setStartX(player_start_x);
159  board->setStartY(player_start_y);
160 
161  // Remember that metadata up there?
162  // Let's get every present metadata through an INI parser
163  std::stringstream stream;
164  stream << metadata_buffer;
165  INI::Parser parser(stream);
166 
167  board->setMetadata("name", parser["name"]);
168  board->setMetadata("author", parser["author"]);
169  board->setMetadata("date", parser["date"]);
170  board->setMetadata("comment", parser["comment"]);
171 
172  return board;
173 }
174 std::vector<std::string> BoardParser::listLevels()
175 {
176  std::vector<std::string> levels = Utils::File::ls(BoardParser::directory);
177 
178  // Remove files that doesn't end with the default file extension
179  //
180  // Also, don't store the full path, only it's basename
181  // (like "file" and not "/path/to/file")
182  //
183  for (std::vector<std::string>::iterator it = levels.begin();
184  it != levels.end();
185  ++it)
186  {
187  if (Utils::File::extension(*it) == BoardParser::extension)
188  (*it) = (Utils::File::dropExtension(Utils::File::basename((*it))));
189 
190  else
191  {
192  // When we remove an element of a vector
193  // it points to the next element.
194  it = levels.erase(it);
195 
196  // We need to decrement it because the `for`
197  // will increment at the end
198  --it;
199  }
200  }
201 
202  return levels;
203 }
204 
static std::vector< std::string > listLevels()
Lists all levels found by the game.
static Board * loadFile(std::string filename)
Loads and parses the level at filename.
Definition: BoardParser.cpp:29
static std::string directory
Default directory where the level files are.
Definition: BoardParser.hpp:38
A level where the snake runs and eats fruits.
Definition: Board.hpp:32
void setMetadata(std::string name, std::string value)
Sets a meta information from this level.
Definition: Board.cpp:162
static Board * load(std::string filename)
Loads and parses level with name.
Definition: BoardParser.cpp:19
void setBoard(std::vector< std::vector< bool > > &newBoard)
Sets the whole level content.
Definition: Board.cpp:136
static std::string extension
Default extension for nSnake level files.
Definition: BoardParser.hpp:47
Custom exception class to specify an error that occurred during a level loading.
Definition: BoardParser.hpp:12