nsnake
Classic snake game for the terminal
Loading...
Searching...
No Matches
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()`
12std::string BoardParser::directory = "";
13
14std::string BoardParser::extension = "nsnake";
15
16
17
18
19Board* BoardParser::load(std::string name)
20{
21 std::string filename = (BoardParser::directory +
22 name +
23 "." +
25
26 return BoardParser::loadFile(filename);
27}
28
29Board* 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}
174std::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
Custom exception class to specify an error that occurred during a level loading.
static Board * loadFile(std::string filename)
Loads and parses the level at filename.
static std::string directory
Default directory where the level files are.
static std::vector< std::string > listLevels()
Lists all levels found by the game.
static Board * load(std::string filename)
Loads and parses level with name.
static std::string extension
Default extension for nSnake level files.
A level where the snake runs and eats fruits.
Definition Board.hpp:33
void setMetadata(std::string name, std::string value)
Sets a meta information from this level.
Definition Board.cpp:162
void setBoard(std::vector< std::vector< bool > > &newBoard)
Sets the whole level content.
Definition Board.cpp:136