diff --git a/CMakeLists.txt b/CMakeLists.txt index f06c99c..f298b19 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,12 @@ project(robots VERSION 0.1.0) include(CTest) enable_testing() -add_executable(robots src/main.cpp src/utils.cpp src/entities.cpp) +add_executable(robots src/main.cpp + src/game.cpp src/files.cpp src/info.cpp + src/keyboard.cpp src/logic.cpp src/mazes.cpp + src/screen.cpp src/timer.cpp src/utf8.cpp +) + include_directories(robots include/) set(CPACK_PROJECT_NAME ${PROJECT_NAME}) diff --git a/MAZE_01.TXT b/MAZE_01.TXT deleted file mode 100644 index 3f9c721..0000000 --- a/MAZE_01.TXT +++ /dev/null @@ -1,10 +0,0 @@ -******************** -* R R * -* R * * * * -* H * * -* ** * * * -* * * * * -* * * R * * -* * ** * -* R * * -******************** diff --git a/include/entities.h b/include/entities.h deleted file mode 100644 index 0819eb6..0000000 --- a/include/entities.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ENTITIES_H -#define ENTITIES_H - -#include -#include - -using namespace std; - -typedef vector> Map; - -struct Robot { - unsigned int id; - int x; - int y; - bool alive; -}; - -struct Player { - int x; - int y; - bool alive; -}; - -bool update_dead(Robot robot, Map &map); -int get_robot_dx(Robot robot, Player player); -int get_robot_dy(Robot robot, Player player); -unsigned move(Robot &robot, Map &map, int dx, int dy); - -bool should_be_dead(Player player, Map &map); -unsigned move(Player &player, Map &map, int dx, int dy); -bool is_move_valid(Player player, Map &map, int dx, int dy); - -#endif diff --git a/include/files.h b/include/files.h new file mode 100644 index 0000000..f7c9c05 --- /dev/null +++ b/include/files.h @@ -0,0 +1,64 @@ +#ifndef FILES_H +#define FILES_H + +#include +#include "mazes.h" + +namespace files { + + /** + * @brief Returns the name of the file containing the given maze. + * + * @param number The number of the maze. + * @return The name of the file containing the given maze. + */ + string get_maze_file_name(unsigned int number); + + /** + * @brief Returns the name of the file containing the given maze's winners. + * + * @param number The number of the maze. + * @return The name of the file containing the given maze's winners. + */ + string get_maze_winners_file_name(unsigned int number); + + /** + * @brief Opens the given file for reading. + * If the file doesn't exist, an error is thrown. + * + * @param file_name The name of the file. + * @return An open stream to the given file. + */ + ifstream open_file_reader(string file_name); + + /** + * @brief Opens the given file for writing. + * If the file doesn't exist and it couldn't be created, an error is thrown. + * + * @param file_name The name of the file. + * @return An open stream to the given file. + */ + ofstream open_file_writer(string file_name); + + /** + * @brief Returns the requested maze's data. + * If the given maze file doesn't exist, can't be read or doesn't fulfill the expected format + * exceptions are thrown. + * + * @param maze The number of the maze. + * @return The requested maze's data, if it exists. + */ + mazes::Maze read_maze(unsigned int maze); + + /** + * @brief Writes the player's name and score in the winner's file. The entries are sorted in ascending score order. + * If the file doesn't exist or doesn't fulfill the expected format, exceptions are thrown. + * + * @param maze The number of the maze. + * @param player_name The given player's name. + * @param player_score The player's score. + */ + void save_maze_score(unsigned int maze, string player_name, time_t player_score); +} // namespace files + +#endif diff --git a/include/game.h b/include/game.h new file mode 100644 index 0000000..1e7207c --- /dev/null +++ b/include/game.h @@ -0,0 +1,31 @@ +#ifndef GAME_H +#define GAME_H + +/** + * @brief Asks the user for the map he/she wants to play on. + * + * @return The chosen maze's number. + */ +mazes::Maze ask_maze(); + +/** + * @brief Asks the user for a move to play. + * + * @param maze The maze the player is playing on. + * @return A letter corresponding to the move. + */ +char ask_move(const mazes::Maze &maze); + +/** + * @brief Asks the user for his/her name. + * + * @return The name provided by the user. + */ +string ask_name(); + +/** + * @brief Plays the game. + */ +void play_game(); + +#endif diff --git a/include/info.h b/include/info.h new file mode 100644 index 0000000..a33210f --- /dev/null +++ b/include/info.h @@ -0,0 +1,22 @@ +#ifndef INFO_H +#define INFO_H + +namespace info { + + /** + * @brief Displays the game's main menu. + */ + void menu(); + + /** + * @brief Displays the game's rules. + */ + void rules(); + + /** + * @brief Displays the game's exit message. + */ + void bye(); +} // namespace info + +#endif diff --git a/include/keyboard.h b/include/keyboard.h new file mode 100644 index 0000000..4980377 --- /dev/null +++ b/include/keyboard.h @@ -0,0 +1,70 @@ +#ifndef KEYBOARD_H +#define KEYBOARD_H + +#include +#include + +namespace keyboard { + + struct Name { + std::string data; + }; + + /** + * @brief Blocks execution until the user presses the RETURN key. + */ + void wait_for_enter(); + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const std::string prompt, const std::string warning, unsigned int &result, const std::function validator = [](unsigned int res) { return true; }); + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const std::string prompt, const std::string warning, char &result, const std::function validator = [](char res) { return true; }); + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const std::string prompt, const std::string warning, Name &result, const std::function validator = [](Name res) { return true; }); +} // namespace keyboard + +#endif diff --git a/include/logic.h b/include/logic.h new file mode 100644 index 0000000..cd8cd35 --- /dev/null +++ b/include/logic.h @@ -0,0 +1,72 @@ +#include "mazes.h" + +namespace logic { + + /** + * @brief Translates the given direction to horizontal and a vertical variations. + * + * @param direction The player's move. + * @param dx The variable to store the variation on the horizontal direction associated with the provided direction on. + * @param dy The variable to store the variation on the vertcal direction associated with the provided direction on. + */ + void get_deltas_from_direction(const char direction, int &dx, int &dy); + + namespace player { + /** + * @brief Checks whether the given move is valid. + * + * @param maze The maze the player is playing on. + * @param dx The change in the player's horizontal position. + * @param dy The change in the player's vertical position. + * @return true, if the given move is valid, or false, otherwise. + */ + bool is_move_valid(const mazes::Maze &maze, const int dx, const int dy); + + /** + * @brief Checks whether the given move is valid. + * + * @param maze The maze the player is playing on. + * @param direction The player's move. + * @return true, if the move is valid, or false, otherwise. + */ + bool is_move_valid(const mazes::Maze &maze, char direction); + + /** + * @brief Moves the player. + * + * @param maze The maze the player is playing on. + * @param dx The change in the player's horizontal position. + * @param dy The change in the player's vertical position. + */ + void move(mazes::Maze &maze, int dx, int dy); + + /** + * @brief Moves the player. + * + * @param maze The maze the player is playing on. + * @param direction The player's move. + */ + void move(mazes::Maze &maze, char direction); + } // namespace player + + namespace robot { + + /** + * @brief Provides the horizontal and vertical variations that get the robot the closest to the player. + * + * @param maze The maze the player is playing on. + * @param robot A robot. + * @param dx The change in the robot's horizontal position. + * @param dy The change in the robot's vertical position. + */ + void get_suggested_deltas(const mazes::Maze &maze, const mazes::Robot &robot, int &dx, int &dy); + + /** + * @brief Moves the robot. + * + * @param maze The maze the player is playing on. + * @param robot A robot. + */ + void move(mazes::Maze &maze, mazes::Robot &robot); + } // namespace robot +} // namespace logic diff --git a/include/mazes.h b/include/mazes.h new file mode 100644 index 0000000..e54906b --- /dev/null +++ b/include/mazes.h @@ -0,0 +1,174 @@ +#ifndef MAZES_H +#define MAZES_H + +#include + +using namespace std; + +namespace mazes { + namespace masks { + + /** + * @brief Bitmask that indicates whether a cell has a barrier in it. + */ + const unsigned int BARRIER = 1; + + /** + * @brief Bitmask that indicates whether a cell has a human in it. + */ + const unsigned int HUMAN = 2; + + /** + * @brief Bitmask that indicates whether a cell has a robot in it. + */ + const unsigned int ROBOT = 4; + + /** + * @brief Bitmask that indicates whether a cell has something dead in it. + */ + const unsigned int DEAD = 8; + } // namespace masks + + /** + * @brief Default value for a cell. + */ + const unsigned int EMPTY = 0; + + /** + * @brief Represents a player. + * + */ + struct Player { + size_t x; + size_t y; + }; + + /** + * @brief Represents a robot. + */ + struct Robot { + size_t id; + size_t x; + size_t y; + }; + + /** + * @brief Represents a game maze. + */ + struct Maze { + unsigned int id; + + size_t width; + size_t height; + unsigned int* cells; + + Player player; + vector robots; + }; + + /** + * @brief Returns whether or not a cell has something dead in it. + * + * @param value The value of a cell. + * @return true, if the cell has something dead in it, or false, otherwise. + */ + bool is_cell_dead(unsigned int value); + + /** + * @brief Returns whether or not a cell has a human in it. + * + * @param value The value of a cell. + * @return true, if the cell has a human in it, or false, otherwise. + */ + bool is_cell_human(unsigned int value); + + /** + * @brief Returns whether or not a cell has a robot in it. + * + * @param value The value of a cell. + * @return true, if the cell has a robot in it, or false, otherwise. + */ + bool is_cell_robot(unsigned int value); + + /** + * @brief Returns whether or not a cell has a barrier in it. + * + * @param value The value of a cell. + * @return true, if the cell has a barrier in it, or false, otherwise. + */ + bool is_cell_barrier(unsigned int value); + + /** + * @brief Returns a character representing the value present in a given cell. + * If a value is not recognized, the character '?' is returned. + * + * @param value The value of a cell. + * @return The character representing the value present in a given cell. + */ + char translate_from_cell_value(unsigned int value); + + /** + * @brief Returns the value that is represented by the given character. + * If the character is not recognized, an error is thrown. + * + * @param value The value of a cell. + * @return The character representing the value present in a given cell. + */ + unsigned int translate_to_cell_value(char value); + + /** + * @brief Gets the index of the cell at (x, y). + * + * @param width The maze's width. + * @param height The maze's height. + * @param x The horizontal position. + * @param y The vertical position. + * @return The cell's index. + */ + size_t get_cell_index(size_t width, size_t height, size_t x, size_t y); + + /** + * @brief Gets the index of the cell at (x, y). + * + * @param maze The maze the player is playing on. + * @param x The horizontal position. + * @param y The vertical position. + * @return The cell's index. + */ + size_t get_cell_index(const mazes::Maze &maze, size_t x, size_t y); + + /** + * @brief Returns the value of the cell at (x, y), by reference. + * + * @param maze The maze the player is playing on. + * @param x The horizontal position. + * @param y The vertical position. + * @return A reference to the cell's value. + */ + unsigned int& get_cell_value_at(const mazes::Maze &maze, size_t x, size_t y); + + /** + * @brief Returns the value of the cell at the player's position, by reference. + * + * @param maze The maze the player is playing on. + * @return A reference to the cell's value. + */ + unsigned int& get_cell_value_at_player_position(const mazes::Maze &maze); + + /** + * @brief Returns whether or not a maze number is valid. + * + * @param maze A maze number. + * @return true, if the maze number is valid, or false, otherwise. + */ + bool is_maze_number_valid(unsigned int maze); + + /** + * @brief Displays the maze the player is playing on. + * + * @param maze The maze the player is playing on. + */ + void show_maze(const mazes::Maze &maze); +} // namespace mazes + +#endif diff --git a/include/screen.h b/include/screen.h new file mode 100644 index 0000000..97007cc --- /dev/null +++ b/include/screen.h @@ -0,0 +1,12 @@ +#ifndef SCREEN_H +#define SCREEN_H + +namespace screen { + + /** + * @brief Clears the terminal window. + */ + void clear(); +} // namespace screen + +#endif diff --git a/include/timer.h b/include/timer.h new file mode 100644 index 0000000..d46f22c --- /dev/null +++ b/include/timer.h @@ -0,0 +1,21 @@ +#ifndef TIMER_H +#define TIMER_H + +#include "time.h" + +namespace timer { + + /** + * @brief Starts the game timer. + */ + void start(); + + /** + * @brief Returns the number of seconds since the game timer started. + * + * @return The number of seconds since the game timer started. + */ + time_t stop(); +} // namespace timer + +#endif diff --git a/include/utf8.h b/include/utf8.h new file mode 100644 index 0000000..eb13ae8 --- /dev/null +++ b/include/utf8.h @@ -0,0 +1,61 @@ +#ifndef UTF8_H +#define UTF8_H + +#include + +namespace utf8 { + + /** + * @brief Returns the number of code points present in the given string. + * + * @param s the string whose UTF8 length is to be returned. + * @return The number of UTF8 code points present in the given string. + */ + std::size_t length(const char* str); + + /** + * @brief Returns the number of code points present in the given string. + * + * @param s the string whose UTF8 length is to be returned. + * @return The number of UTF8 code points present in the given string. + */ + std::size_t length(const std::string str); + + /** + * @brief Adds filler characters until the given string has the specified length. + * + * @param s The string that will be filled. + * @param filler The filler character. + * @param length The desired length. + * @param left true to add the filler characters to the start of the string, false otherwise. + * + * @return The filled string. + */ + std::string zfill(std::string str, const char filler, const std::size_t length, const bool left); + + /** + * @brief Removes whitespace characters at the start of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + std::string ltrim(std::string str); + + /** + * @brief Removes whitespace characters at the end of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + std::string rtrim(std::string str); + + /** + * @brief Removes whitespace characters at both the start and the end of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + std::string trim(std::string str); +} // namespace utf8 + +#endif diff --git a/include/utils.h b/include/utils.h deleted file mode 100644 index a10c66e..0000000 --- a/include/utils.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H - -#include -#include -#include - -namespace input { - - struct Name { - std::string name; - }; - std::basic_istream& operator>>(std::basic_istream&, Name&); - std::basic_ostream& operator<<(std::basic_ostream&, Name&); - - bool read_value(std::string prompt, std::string default_warning, unsigned int &result, const std::function validator = [](unsigned int res) { return std::string(); }); - bool read_value(std::string prompt, std::string default_warning, char &result, const std::function validator = [](char res) { return std::string(); }); - bool read_value(std::string prompt, std::string default_warning, Name &result, const std::function validator = [](Name res) { return std::string(); }); - - void clear_screen(); - void wait_for_enter(); - - int utf8Len(const char* s); - -} // namespace input - -namespace files { - -} // namespace files - -#endif diff --git a/mazes/MAZE_01.TXT b/mazes/MAZE_01.TXT index 3f9c721..d9d218d 100644 --- a/mazes/MAZE_01.TXT +++ b/mazes/MAZE_01.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * R R * * R * * * * diff --git a/mazes/MAZE_04.TXT b/mazes/MAZE_04.TXT index 8f6fab4..b948105 100644 --- a/mazes/MAZE_04.TXT +++ b/mazes/MAZE_04.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** *** * R * * * R * * diff --git a/mazes/MAZE_09.TXT b/mazes/MAZE_09.TXT index f1b825c..4b79b2d 100644 --- a/mazes/MAZE_09.TXT +++ b/mazes/MAZE_09.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * R * * * * * * * * * * diff --git a/mazes/MAZE_10.TXT b/mazes/MAZE_10.TXT index ea469da..c53d16c 100644 --- a/mazes/MAZE_10.TXT +++ b/mazes/MAZE_10.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * H * * * * * * diff --git a/mazes/MAZE_11.TXT b/mazes/MAZE_11.TXT index 6a2d894..d4fb48f 100644 --- a/mazes/MAZE_11.TXT +++ b/mazes/MAZE_11.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * * * * R * * diff --git a/mazes/MAZE_12.TXT b/mazes/MAZE_12.TXT index cbeff98..bb8dfb1 100644 --- a/mazes/MAZE_12.TXT +++ b/mazes/MAZE_12.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * *** R * * * * * diff --git a/mazes/MAZE_13.TXT b/mazes/MAZE_13.TXT index d7d3170..e72b8a1 100644 --- a/mazes/MAZE_13.TXT +++ b/mazes/MAZE_13.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * *** * * * * * * H * diff --git a/mazes/MAZE_14.TXT b/mazes/MAZE_14.TXT index 5915dcb..b233978 100644 --- a/mazes/MAZE_14.TXT +++ b/mazes/MAZE_14.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * R *** *** * * * H* * diff --git a/mazes/MAZE_15.TXT b/mazes/MAZE_15.TXT index 6e3b42c..2d9c170 100644 --- a/mazes/MAZE_15.TXT +++ b/mazes/MAZE_15.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * *** R * * * *** * diff --git a/mazes/MAZE_16.TXT b/mazes/MAZE_16.TXT index 8a134fb..7fdee66 100644 --- a/mazes/MAZE_16.TXT +++ b/mazes/MAZE_16.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** ******************** *** * R ** diff --git a/mazes/MAZE_17.TXT b/mazes/MAZE_17.TXT index a196b27..c0d1915 100644 --- a/mazes/MAZE_17.TXT +++ b/mazes/MAZE_17.TXT @@ -1,7 +1,8 @@ +20 x 10 ******************** ******* *** H * * * * * -* R * * +* R * * * * * * * * * * * * R **** * diff --git a/mazes/MAZE_18.TXT b/mazes/MAZE_18.TXT deleted file mode 100644 index e69de29..0000000 diff --git a/mazes/MAZE_19.TXT b/mazes/MAZE_19.TXT index 74fd69d..b9a583e 100644 --- a/mazes/MAZE_19.TXT +++ b/mazes/MAZE_19.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * ***H * * R * * * diff --git a/mazes/MAZE_20.TXT b/mazes/MAZE_20.TXT index bb024a9..0cbbbdb 100644 --- a/mazes/MAZE_20.TXT +++ b/mazes/MAZE_20.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * R * * * * diff --git a/mazes/MAZE_21.TXT b/mazes/MAZE_21.TXT index a274511..1e66c54 100644 --- a/mazes/MAZE_21.TXT +++ b/mazes/MAZE_21.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * H * * * * * * diff --git a/mazes/MAZE_22.TXT b/mazes/MAZE_22.TXT index 33845a3..84e8fdd 100644 --- a/mazes/MAZE_22.TXT +++ b/mazes/MAZE_22.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * H * * * * * diff --git a/mazes/MAZE_23.TXT b/mazes/MAZE_23.TXT index 7dd3388..8aebb82 100644 --- a/mazes/MAZE_23.TXT +++ b/mazes/MAZE_23.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * R * * * ***** * diff --git a/mazes/MAZE_24.TXT b/mazes/MAZE_24.TXT index 11b37c4..0088156 100644 --- a/mazes/MAZE_24.TXT +++ b/mazes/MAZE_24.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * *** R *** * * R * * * diff --git a/mazes/MAZE_25.TXT b/mazes/MAZE_25.TXT deleted file mode 100644 index e69de29..0000000 diff --git a/mazes/MAZE_26.TXT b/mazes/MAZE_26.TXT index ed2ca91..5197746 100644 --- a/mazes/MAZE_26.TXT +++ b/mazes/MAZE_26.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * R ** R * * * * diff --git a/mazes/MAZE_66.TXT b/mazes/MAZE_66.TXT index 2e10c5d..ae8960d 100644 --- a/mazes/MAZE_66.TXT +++ b/mazes/MAZE_66.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * * R * * @@ -7,4 +8,4 @@ * * * * R * * * * R * -******************** \ No newline at end of file +******************** \ No newline at end of file diff --git a/mazes/MAZE_67.TXT b/mazes/MAZE_67.TXT index c2267b3..cd6ed91 100644 --- a/mazes/MAZE_67.TXT +++ b/mazes/MAZE_67.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * * R * * * * * diff --git a/mazes/MAZE_69.TXT b/mazes/MAZE_69.TXT index 7ecf673..7d42460 100644 --- a/mazes/MAZE_69.TXT +++ b/mazes/MAZE_69.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** *R R* * * diff --git a/mazes/MAZE_89.TXT b/mazes/MAZE_89.TXT index c23bc00..6a09c25 100644 --- a/mazes/MAZE_89.TXT +++ b/mazes/MAZE_89.TXT @@ -1,3 +1,4 @@ +20 x 10 ******************** * * H* * *R * * * * diff --git a/src/entities.cpp b/src/entities.cpp deleted file mode 100644 index bb25861..0000000 --- a/src/entities.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "entities.h" -#include "math.h" - -bool update_dead(Robot robot, Map &map) { - return map.at(robot.y).at(robot.x) != 'R'; -} - -unsigned move(Robot &robot, Map &map, int dx, int dy) { - if (!robot.alive || dx < -1 || dx > 1 || dy < -1 || dy > 1) - return 0; - - int new_x = robot.x + dx; - int new_y = robot.y + dy; - if (new_y >= 0 && new_y < map.size() && new_x >= 0 && new_x < map.at(new_y).size()) { - map.at(robot.y)[robot.x] = ' '; - int status = 1; - switch (map.at(new_y).at(new_x)) { - case '*': - case 'r': - robot.alive = false; - break; - - case 'R': - robot.x = new_x; - robot.y = new_y; - robot.alive = false; - status = 2; - break; - - case 'H': - status = 3; - break; - - default: - robot.x = new_x; - robot.y = new_y; - break; - } - - - map.at(robot.y)[robot.x] = (robot.alive ? 'R' : 'r'); - return status; - } else { - robot.alive = false; - } - - return 1; -} - -bool should_be_dead(Player player, Map &map) { - return map.at(player.y).at(player.x) != 'H'; -} - -int get_robot_dx(Robot robot, Player player) { - int diff = player.x - robot.x; - - if (diff > 0) - return 1; - else if (diff < 0) - return -1; - else - return 0; -} - -int get_robot_dy(Robot robot, Player player) { - int diff = player.y - robot.y; - - if (diff > 0) - return 1; - else if (diff < 0) - return -1; - else - return 0; -} - -unsigned move(Player &player, Map &map, int dx, int dy) { - if (!player.alive || dx < -1 || dx > 1 || dy < -1 || dy > 1) - return 0; - - int new_x = player.x + dx; - int new_y = player.y + dy; - if (new_y >= 0 && new_y < map.size() && new_x >= 0 && new_x < map.at(new_y).size()) { - map.at(player.y)[player.x] = ' '; - - switch (map.at(new_y).at(new_x)) { - case '*': - case 'r': - case 'R': - return 0; - - default: - player.x = new_x; - player.y = new_y; - break; - } - - map.at(player.y)[player.x] = 'H'; - } else { - return 0; - } - - return 1; -} - -bool is_move_valid(Player player, Map &map, int dx, int dy) { - if (!player.alive || dx < -1 || dx > 1 || dy < -1 || dy > 1) - return false; - - int new_x = player.x + dx; - int new_y = player.y + dy; - if (new_y >= 0 && new_y < map.size() && new_x >= 0 && new_x < map.at(new_y).size()) { - switch (map.at(new_y).at(new_x)) { - case '*': - case 'r': - case 'R': - return false; - - default: - return true; - } - } - - return false; -} diff --git a/src/files.cpp b/src/files.cpp new file mode 100644 index 0000000..1c346d0 --- /dev/null +++ b/src/files.cpp @@ -0,0 +1,222 @@ +#include +#include "utf8.h" + +#include "files.h" + +namespace files { + + /** + * @brief Returns the name of the file containing the given maze. + * + * @param number The number of the maze. + * @return The name of the file containing the given maze. + */ + string get_maze_file_name(unsigned int number) { + if (!mazes::is_maze_number_valid(number)) + throw "The provided maze number is invalid"; + + string file_name = utf8::zfill(to_string(number), '0', 2, true); + return "MAZE_" + file_name + ".TXT"; + } + + /** + * @brief Returns the name of the file containing the given maze's winners. + * + * @param number The number of the maze. + * @return The name of the file containing the given maze's winners. + */ + string get_maze_winners_file_name(unsigned int number) { + if (!mazes::is_maze_number_valid(number)) + throw "The provided maze number is invalid."; + + string file_name = utf8::zfill(to_string(number), '0', 2, true); + return "MAZE_" + file_name + "_WINNERS.TXT"; + } + + /** + * @brief Opens the given file for reading. + * If the file doesn't exist, an error is thrown. + * + * @param file_name The name of the file. + * @return An open stream to the given file. + */ + ifstream open_file_reader(string file_name) { + ifstream file; + file.open(file_name, ios::in); + if (!file.is_open()) + throw "The given file doesn't exist"; + + return file; + } + + /** + * @brief Opens the given file for writing. + * If the file doesn't exist and it couldn't be created, an error is thrown. + * + * @param file_name The name of the file. + * @return An open stream to the given file. + */ + ofstream open_file_writer(string file_name) { + ofstream file; + file.open(file_name, ios::trunc); + if (!file.is_open()) + throw "Couldn't create the given file"; + + return file; + } + + /** + * @brief Returns the requested maze's data. + * If the given maze file doesn't exist, can't be read or doesn't fulfill the expected format + * exceptions are thrown. + * + * @param maze The number of the maze. + * @return The requested maze's data, if it exists. + */ + mazes::Maze read_maze(unsigned int maze) { + ifstream file = files::open_file_reader(files::get_maze_file_name(maze)); + + char sep; + size_t width, height; + file >> width >> sep >> height; + if (sep != 'x' || width == 0 || height == 0 || file.bad()) + throw "The given file does not fulfill the expected format"; + + unsigned int* cells = (unsigned int*) malloc(width * height * sizeof(unsigned int));//TODO: free(cells); !!!!!!!!!!!!!!!!! + + mazes::Player player; + bool has_found_player = false; + + vector robots; + + char ch; + size_t index = 0; + while (file.get(ch)) { + if (ch == '\n') { + if (index % width != 0) { + free(cells); + throw "The given file does not fulfill the expected format"; + } + + continue; + } + + if (index >= width * height) { + free(cells); + throw "The given file does not fulfill the expected format"; + } + + unsigned int value = mazes::translate_to_cell_value(ch); + + size_t x = index % width; + size_t y = index / width; + + if (mazes::is_cell_human(value)) { + if (!has_found_player) { + player.x = x; + player.y = y; + + has_found_player = true; + } else { + free(cells); + throw "The given file does not fulfill the expected format"; + } + } else if (mazes::is_cell_robot(value)) { + robots.push_back({ + robots.size() + 1, // Robot IDs start at 1 + x, y + }); + } + + cells[index++] = value; + } + + if (!has_found_player || index < width * height) { + free(cells); + throw "The given file does not fulfill the expected format"; + } + + file.close(); + return { maze, width, height, cells, player, robots }; + } + + /** + * @brief Writes the player's name and score in the winner's file. The entries are sorted in ascending score order. + * If the file doesn't exist or doesn't fulfill the expected format, exceptions are thrown. + * + * @param maze The number of the maze. + * @param player_name The given player's name. + * @param player_score The player's score. + */ + void save_maze_score(unsigned int maze, string player_name, time_t player_score) { + static const string HEADER1 = "Player - Time"; + static const string HEADER2 = "----------------------"; + + struct Score { + string name; + time_t score; + }; + + vector scores; + string winners_file_name = files::get_maze_winners_file_name(maze); + + // Read scores from file + try { + + ifstream file_reader = files::open_file_reader(winners_file_name); + + string line; + getline(file_reader, line); + if (line != HEADER1) + throw "The given file does not fulfill the expected format"; + + getline(file_reader, line); + if (line != HEADER2) + throw "The given file does not fulfill the expected format"; + + while (getline(file_reader, line)) { + size_t length = utf8::length(line); + if (length != utf8::length(HEADER1)) + throw "The given file does not fulfill the expected format"; + + size_t start_of_score = line.length() - 4; // Score has 4 characters + size_t start_of_separator = start_of_score - 3; + + string sep = line.substr(start_of_separator, 3); + if (sep != " - ") + throw "The given file does not fulfill the expected format"; + + string name = utf8::rtrim(line.substr(0, start_of_separator)); + time_t score = stoi(line.substr(start_of_score, 4)); + + scores.push_back({ name, score }); + } + + file_reader.close(); + } catch (...) { + scores.clear(); + } + + // Sort scores + scores.push_back({ player_name, player_score }); + sort(scores.begin(), scores.end(), [] (auto first, auto second) { + return first.score < second.score; + }); + + // Write ordered scores to file + ofstream file_writer = files::open_file_writer(winners_file_name); + + file_writer << HEADER1 << '\n' + << HEADER2 << '\n'; + + for (auto score : scores) { + file_writer << utf8::zfill(score.name, ' ', 15, false) + << " - " + << utf8::zfill(to_string(score.score), ' ', 4, true) + << '\n'; + } + + file_writer << flush; + file_writer.close(); + } +} // namespace files diff --git a/src/game.cpp b/src/game.cpp new file mode 100644 index 0000000..10ce0b8 --- /dev/null +++ b/src/game.cpp @@ -0,0 +1,183 @@ +#include +#include "keyboard.h" +#include "screen.h" +#include "mazes.h" +#include "files.h" +#include "logic.h" +#include "timer.h" +#include "utf8.h" + +#include "game.h" + +using namespace std; + +/** + * @brief Asks the user for the map he/she wants to play on. + * + * @return The chosen maze's number. + */ +mazes::Maze ask_maze() { + mazes::Maze maze; + + unsigned int number; + bool is_successful = keyboard::read_value( + "Please select a maze to play on (0 to return to menu): ", + "Please specify a number between 0 and 99", + number, + [&maze] (auto res) { + if (res >= 100) + return false; + + if (res == 0) + return true; + + maze = files::read_maze(res); + return true; + } + ); + + cout << endl; + + if (number == 0) + throw true; + + if (!is_successful) + throw false; + + return maze; +} + +/** + * @brief Asks the user for a move to play. + * + * @param maze The maze the player is playing on. + * @return A letter corresponding to the move. + */ +char ask_move(const mazes::Maze &maze) { + + char letter; + bool is_successful = keyboard::read_value( + "Your move: ", + "Your move must be one of Q, A, Z, W, S, X, E, D, or C", + letter, + [&maze] (auto res) { + bool is_move_valid; + try { + is_move_valid = logic::player::is_move_valid(maze, res); + } catch (const char *exception) { + return false; + } + + if (!is_move_valid) + throw "Uh oh, that move seems invalid"; + + return true; + } + ); + + cout << endl; + + if (!is_successful) + throw false; + + return letter; +} + +/** + * @brief Asks the user for his/her name. + * + * @return The name provided by the user. + */ +string ask_name() { + keyboard::Name name = { string() }; + + bool is_successful = keyboard::read_value( + "Well done, champion! What's your name? ", + "The provided name must have less than 15 characters", + name + ); + + cout << endl; + + if (!is_successful) + throw false; + + return name.data; +} + +/** + * @brief Plays the game. + */ +void play_game() { + mazes::Maze maze = ask_maze(); + timer::start(); + + try { + bool has_player_won; + while (true) { + screen::clear(); + mazes::show_maze(maze); + cout << endl; + + char move = ask_move(maze); + logic::player::move(maze, move); + + for (auto &robot : maze.robots) { + logic::robot::move(maze, robot); + } + + if (mazes::is_cell_dead(mazes::get_cell_value_at_player_position(maze))) { + has_player_won = false; + break; + } + + bool are_robots_alive = false; + for (auto robot : maze.robots) { + if (!mazes::is_cell_dead(mazes::get_cell_value_at(maze, robot.x, robot.y))) { + are_robots_alive = true; + break; + } + } + + if (!are_robots_alive) { + has_player_won = true; + break; + } + } + + time_t score = timer::stop(); + + screen::clear(); + mazes::show_maze(maze); + cout << endl; + + if (has_player_won) { + cout << "You won :)\n" + << "Congratulations! You finished the game in " << score << " seconds!" << endl; + } else { + cout << "You lost :(" << endl; + } + + cout << endl; + keyboard::wait_for_enter(); + + if (!has_player_won) + return; + + string name = ask_name(); + try { + files::save_maze_score(maze.id, name, score); + cout << "Your score was saved!" << endl; + } catch (const char *exception) { + cout << ">> Warning: " << exception << endl; + } + + cout << endl; + keyboard::wait_for_enter(); + + throw true; // Make sure to free memory and restart game + } catch (bool ex) { + free(maze.cells); + throw ex; + } +} diff --git a/src/info.cpp b/src/info.cpp new file mode 100644 index 0000000..bc4f531 --- /dev/null +++ b/src/info.cpp @@ -0,0 +1,88 @@ +#include +#include "screen.h" + +#include "info.h" + +using namespace std; + +namespace info { + + /** + * @brief Displays the game's main menu. + */ + void menu() { + screen::clear(); + + cout << ".______________________________________________________.\n" + << "| |\n" + << "| |\n" + << "| |\n" + << "| .___ ___. _______ .__ __. __ __ |\n" + << "| | \\/ | | ____|| \\ | | | | | | |\n" + << "| | \\ / | | |__ | \\| | | | | | |\n" + << "| | |\\/| | | __| | . ` | | | | | |\n" + << "| | | | | | |____ | |\\ | | `--' | |\n" + << "| |__| |__| |_______||__| \\__| \\______/ |\n" + << "| |\n" + << "| |\n" + << "| |\n" + << "| |\n" + << "| 1) Rules |\n" + << "| 2) Play |\n" + << "| 0) Exit |\n" + << "| |\n" + << "| |\n" + << "| |\n" + << "| |\n" + << "|______________________________________________________|\n"; + + cout << endl; + } + + /** + * @brief Displays the game's rules. + */ + void rules() { + screen::clear(); + + cout << " _ \n" + << " _ __ _ _| | ___ ___ \n" + << "| '__| | | | |/ _ \\/ __| \n" + << "| | | |_| | | __/\\__ \\ \n" + << "|_| \\__,_|_|\\___||___/ \n\n\n"; + + + cout << "The player is placed in a maze made up of high-voltage fences and posts.\n" + << "There are also some interceptor robots that will try to destroy the player.\n" + << "If the player touches the maze or any of these robots, that is the end of the game (and the player!).\n" + << "The robots are also destroyed when they touch the fences/posts or when they collide with each other.\n\n" + + << "Ever time the player moves in any direction (horizontally, vertically, or diagonally) to a contiguous cell,\n" + << "each robot moves one cell closer to the new player's location, in whichever direction is the shortest path.\n" + << "The robots have no vision sensors but they have an accurate odour sensor that allows them to follow the player!\n\n" + + << "There is one hope: make the robots hit the maze or each other. If all of them are destroyed, the player wins.\n\n" + + << "The controls are as follows: \n" + << "-------------\n" + << "| Q | W | E |\n" + << "| A | S | D |\n" + << "| Z | X | C |\n" + << "-------------\n"; + + cout << endl; + } + + /** + * @brief Displays the game's exit message. + */ + void bye() { + cout << " ___ \n" + << " | _ )_ _ ___ \n" + << " | _ \\ || / -_) \n" + << " |___/\\_, \\___|\n" + << " |__/ \n"; + + cout << endl; + } +} // namespace info diff --git a/src/keyboard.cpp b/src/keyboard.cpp new file mode 100644 index 0000000..c644116 --- /dev/null +++ b/src/keyboard.cpp @@ -0,0 +1,177 @@ +#include +#include +#include "unistd.h" +#include "utf8.h" + +#include "keyboard.h" + +using namespace std; + +namespace keyboard { + + std::basic_istream& operator>>(std::basic_istream& istream, keyboard::Name &name) { + name.data.clear(); + + char ch; + while ((istream.peek() != '\n' && istream.get(ch)) && utf8::length(name.data) < 15) + name.data.push_back(ch); + + return istream; + } + + std::basic_ostream& operator<<(std::basic_ostream& ostream, keyboard::Name &name) { + return ostream << name.data; + } + + /** + * @brief Blocks execution until the user presses the RETURN key. + */ + void wait_for_enter() { + cout << "Press ENTER to continue..." << endl; + cin.ignore(numeric_limits::max(), '\n'); + } + + /** + * @brief Auxiliary function. + */ + template + bool read_value_interactive(const string prompt, const string warning, T &result, const function validator) { + cout << "? " << prompt << flush; + while (true) { + // Is true if, and only if, all the content present on the line is of the type T + bool is_input_valid = false; + const char *error = warning.c_str(); + + try { + is_input_valid = cin.peek() != '\n' + && cin >> result && cin.peek() == '\n' + && validator(result); + } catch (const char *exception) { + error = exception; + } + + // Clear the buffer + // We do this to avoid having "123a[EOF]" trigger the loop more than once. + cin.clear(); + cin.ignore(numeric_limits::max(), '\n'); + + if (is_input_valid) { + cout << "\x1B[F\x1B[G✓\x1B[" << prompt.length() + 3 << "G\x1B[K" << result << "\n\x1B[K" << flush; + return true; + } else { + if (!cin.eof()) + cout << ">> " << error << "\x1B[F"; + + cout << "\x1B[" << prompt.length() + 3 << "G\x1B[K"; + + if (cin.eof()) { + cout << "EOF\n\x1B[F\x1B[G×\x1B[E\x1B[K" << flush; + return false; + } + } + } + } + + /** + * @brief Auxiliary function. + */ + template + bool read_value_dinossaur(const string prompt, const string warning, T &result, const function validator) { + while (true) { + cout << "? " << prompt << flush; + + bool is_input_valid = false; + const char *error = warning.c_str(); + + try { + is_input_valid = cin.peek() != '\n' + && cin >> result && cin.peek() == '\n' + && validator(result); + } catch (const char *exception) { + error = exception; + } + + cin.clear(); + cin.ignore(numeric_limits::max(), '\n'); + + if (is_input_valid) { + return true; + } else { + if (cin.eof()) { + cout << "EOF" << endl; + return false; + } else { + cout << "\u26A0 " << error << endl; + } + } + } + } + + /** + * @brief Auxiliary function. + */ + template + inline bool read_value_adaptive(const string prompt, const string warning, T &result, const function validator = [] (T res) { return true; }) { + if (isatty(fileno(stdin)) && isatty(fileno(stdout))) + return keyboard::read_value_interactive(prompt, warning, result, validator); + + return keyboard::read_value_dinossaur(prompt, warning, result, validator); + } + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const string prompt, const string warning, unsigned int &result, const function validator) { + return keyboard::read_value_adaptive(prompt, warning, result, validator); + } + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const string prompt, const string warning, char &result, const function validator) { + return keyboard::read_value_adaptive(prompt, warning, result, validator); + } + + /** + * @brief Prompts the user to provide a value through console input. + * On interactive terminals, it will be done in a "fancy" fashion and in a single line. + * Otherwise, it will be done in a more "traditional" manner. + * + * @param prompt The text that will be used to prompt the user for a value. + * @param warning The error message that will be presented to the user if the value could not be parsed or if the validator function returns false. + * @param result The variable where the result will be stored. + * @param validator An optional function that determines if the value is valid. + * This function is only executed after the parsing of the value was successful. + * If this function throws a string, it will be presented to the user as an error message. + * If this function returns false, the warning message will be presented to the user. + * + * @return true, if the input is valid, or false, if cin has reached EOF. + */ + bool read_value(const string prompt, const string warning, Name &result, const function validator) { + return keyboard::read_value_adaptive(prompt, warning, result, validator); + } +} // namespace keyboard diff --git a/src/logic.cpp b/src/logic.cpp new file mode 100644 index 0000000..53b65b3 --- /dev/null +++ b/src/logic.cpp @@ -0,0 +1,223 @@ +#include + +#include "logic.h" + +namespace logic { + + /** + * @brief Translates the given direction to horizontal and a vertical variations. + * + * @param direction The player's move. + * @param dx The variable to store the variation on the horizontal direction associated with the provided direction on. + * @param dy The variable to store the variation on the vertcal direction associated with the provided direction on. + */ + void get_deltas_from_direction(const char direction, int &dx, int &dy) { // Gets direction through vector coordinates + switch (toupper(direction)) { + case 'Q': + dx = -1; + dy = -1; + break; + + case 'W': + dx = 0; + dy = -1; + break; + + case 'E': + dx = 1; + dy = -1; + break; + + case 'A': + dx = -1; + dy = 0; + break; + + case 'S': + dx = 0; + dy = 0; + break; + + case 'D': + dx = 1; + dy = 0; + break; + + case 'Z': + dx = -1; + dy = 1; + break; + + case 'X': + dx = 0; + dy = 1; + break; + + case 'C': + dx = 1; + dy = 1; + break; + + default: + throw "The given direction is invalid"; + } + } + + namespace player { + /** + * @brief Checks whether the given move is valid. + * + * @param maze The maze the player is playing on. + * @param dx The change in the player's horizontal position. + * @param dy The change in the player's vertical position. + * @return true, if the given move is valid, or false, otherwise. + */ + bool is_move_valid(const mazes::Maze &maze, const int dx, const int dy) { + if (dx < -1 || dx > 1 || dy < -1 || dy > 1) + return false; + + if (mazes::is_cell_dead(mazes::get_cell_value_at_player_position(maze))) + return false; + + int new_x = maze.player.x + dx; + int new_y = maze.player.y + dy; + if (new_x < 0 || new_y < 0 || new_x >= maze.width || new_y >= maze.height) + return false; + + unsigned int value = mazes::get_cell_value_at(maze, new_x, new_y); + if (mazes::is_cell_robot(value) || mazes::is_cell_barrier(value)) + return false; + + return true; + } + + /** + * @brief Checks whether the given move is valid. + * + * @param maze The maze the player is playing on. + * @param direction The player's move. + * @return true, if the move is valid, or false, otherwise. + */ + bool is_move_valid(const mazes::Maze &maze, char direction) { + int dx, dy; + logic::get_deltas_from_direction(direction, dx, dy); + + return logic::player::is_move_valid(maze, dx, dy); + } + + /** + * @brief Moves the player. + * + * @param maze The maze the player is playing on. + * @param dx The change in the player's horizontal position. + * @param dy The change in the player's vertical position. + */ + void move(mazes::Maze &maze, int dx, int dy) { + if (!logic::player::is_move_valid(maze, dx, dy)) + throw "The provided move is not valid"; + + int new_x = maze.player.x + dx; + int new_y = maze.player.y + dy; + + mazes::get_cell_value_at_player_position(maze) &= ~mazes::masks::HUMAN; + + maze.player.x = new_x; + maze.player.y = new_y; + + mazes::get_cell_value_at_player_position(maze) |= mazes::masks::HUMAN; + } + + /** + * @brief Moves the player. + * + * @param maze The maze the player is playing on. + * @param direction The player's move. + */ + void move(mazes::Maze &maze, char direction) { + int dx, dy; + logic::get_deltas_from_direction(direction, dx, dy); + + logic::player::move(maze, dx, dy); + } + } // namespace player + + namespace robot { + + /** + * @brief Provides the horizontal and vertical variations that get the robot the closest to the player. + * + * @param maze The maze the player is playing on. + * @param robot A robot. + * @param dx The change in the robot's horizontal position. + * @param dy The change in the robot's vertical position. + */ + void get_suggested_deltas(const mazes::Maze &maze, const mazes::Robot &robot, int &dx, int &dy) { + int delta_x = maze.player.x - robot.x; + int delta_y = maze.player.y - robot.y; + + if (delta_x > 0) + dx = 1; + else if (delta_x < 0) + dx = -1; + else + dx = 0; + + if (delta_y > 0) + dy = 1; + else if (delta_y < 0) + dy = -1; + else + dy = 0; + } + + /** + * @brief Moves the robot. + * + * @param maze The maze the player is playing on. + * @param robot A robot. + */ + void move(mazes::Maze &maze, mazes::Robot &robot) { + int dx, dy; + logic::robot::get_suggested_deltas(maze, robot, dx, dy); + + int new_x = robot.x + dx; + int new_y = robot.y + dy; + if (new_x < 0 || new_x >= maze.width) + new_x = robot.x; + + if (new_y < 0 || new_y >= maze.height) + new_y = robot.y; + + unsigned int& current_value = mazes::get_cell_value_at(maze, robot.x, robot.y); + unsigned int& new_value = mazes::get_cell_value_at(maze, new_x, new_y); + + if (mazes::is_cell_dead(current_value)) + return; + + if (mazes::is_cell_barrier(new_value)) { + if (!mazes::is_cell_dead(new_value)) { // If the barrier is powered + current_value |= mazes::masks::DEAD; // Kill robot + new_value |= mazes::masks::DEAD; // Make barrier unpowered + } + } else if (mazes::is_cell_robot(new_value)) { + if (mazes::is_cell_dead(new_value)) { // If the robot in the new position is dead + current_value |= mazes::masks::DEAD; + } else { // If the robot in the new position is alive + current_value &= ~mazes::masks::ROBOT; // Move the robot + new_value |= mazes::masks::DEAD; // Kill both robots; + + robot.x = new_x; + robot.y = new_y; + } + } else if (mazes::is_cell_human(new_value)) { + new_value |= mazes::masks::DEAD; + } else { + current_value &= ~mazes::masks::ROBOT; // Move the robot + new_value |= mazes::masks::ROBOT; + + robot.x = new_x; + robot.y = new_y; + } + } + } // namespace robot +} // namespace logic diff --git a/src/main.cpp b/src/main.cpp index 494289d..908b152 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,502 +1,54 @@ -#include -#include -#include -#include -#include "utils.h" -#include "entities.h" +#include +#include "keyboard.h" +#include "info.h" +#include "game.h" using namespace std; -time_t game_timer(bool overwrite) { // Funtion to get playing time - static time_t start_time; - - if (overwrite) - return start_time = time(NULL); - - return time(NULL) - start_time; -} - -void displayMenu() { //Displays inicial menu - input::clear_screen(); - - cout << ".______________________________________________________.\n" - << "| |\n" - << "| |\n" - << "| |\n" - << "| .___ ___. _______ .__ __. __ __ |\n" - << "| | \\/ | | ____|| \\ | | | | | | |\n" - << "| | \\ / | | |__ | \\| | | | | | |\n" - << "| | |\\/| | | __| | . ` | | | | | |\n" - << "| | | | | | |____ | |\\ | | `--' | |\n" - << "| |__| |__| |_______||__| \\__| \\______/ |\n" - << "| |\n" - << "| |\n" - << "| |\n" - << "| |\n" - << "| 1) Rules |\n" - << "| 2) Play |\n" - << "| 0) Exit |\n" - << "| |\n" - << "| |\n" - << "| |\n" - << "| |\n" - << "|______________________________________________________|\n"; - - - - cout << endl; -} - -void displayRules() { //Displays rules of the game - input::clear_screen(); - - cout << " _ \n" - << " _ __ _ _| | ___ ___ \n" - << "| '__| | | | |/ _ \\/ __| \n" - << "| | | |_| | | __/\\__ \\ \n" - << "|_| \\__,_|_|\\___||___/ \n\n\n"; - - - cout << "The player is placed in a maze made up of high-voltage fences and posts.\n" - << "There are also some interceptor robots that will try to destroy the player.\n" - << "If the player touches the maze or any of these robots, that is the end of the game (and the player!).\n" - << "The robots are also destroyed when they touch the fences/posts or when they collide with each other.\n\n" - - << "Ever time the player moves in any direction (horizontally, vertically, or diagonally) to a contiguous cell,\n" - << "each robot moves one cell closer to the new player's location, in whichever direction is the shortest path.\n" - << "The robots have no vision sensors but they have an accurate odour sensor that allows them to follow the player!\n\n" - - << "There is one hope: make the robots hit the maze or each other. If all of them are destroyed, the player wins.\n\n" - - << "The controls are as follows: \n" - << "-------------\n" - << "| Q | W | E |\n" - << "| A | S | D |\n" - << "| Z | X | C |\n" - << "-------------" << endl; - - cout << endl; -} - -bool readMap(unsigned int number, vector> &result) { // Stores chosen map in 2D vector - const static string maze_str = "MAZE_"; - const static string txt_str = ".TXT"; - - string file_name = to_string(number); - if (number < 10) - file_name.insert(file_name.begin(), '0'); // Transforms single digit numbers into XX format - - file_name.insert(0, maze_str); - file_name.append(txt_str); // Gets file name to correct format - - ifstream file; - file.open(file_name, ios::in); - if (!file.is_open()) { - return false; - } - - char ch; - bool found_eof = false; // EOF will break the program at any time - unsigned players = 0; - while (!found_eof) { - vector line; - while(true) { - file.get(ch); - if (file.eof()) { - found_eof = true; - break; - } - - if (ch == '\n') - break; - else if (ch == 'H') // Makes sure there's at least a player - players++; - - line.push_back(ch); - } - - if (line.size() > 0) - result.push_back(line); - } - - file.close(); - return players == 1; // Makes sure there's only one player in the map -} - -bool loadMap(unsigned int &number, vector> &result) { // Validates map number choice - result.clear(); - - function fun = [&result] (unsigned int res) { - if (res == 0) - return string(); - - if (res >= 100) - return string("Please specify a number between 1 and 99"); - - if (!readMap(res, result)) - return string("A map with that number does not exist"); - - return string(); - }; - - bool success = input::read_value( - "Please select a map to play on (0 to return to menu): ", - "Please specify a number", - number, - fun - ); - - cout << endl; - return success; -} - -bool get_deltas_from_direction(char direction, int &dx, int &dy) { // Gets direction through vector coordinates - switch (direction) { - case 'Q': - dx = -1; - dy = -1; - break; - - case 'W': - dx = 0; - dy = -1; - break; - - case 'E': - dx = 1; - dy = -1; - break; - - case 'A': - dx = -1; - dy = 0; - break; - - case 'S': - dx = 0; - dy = 0; - break; - - case 'D': - dx = 1; - dy = 0; - break; - - case 'Z': - dx = -1; - dy = 1; - break; - - case 'X': - dx = 0; - dy = 1; - break; - - case 'C': - dx = 1; - dy = 1; - break; - - default: - return false; - } - - return true; -} - -bool saveScore(string &player_name, int player_score, unsigned int map_number) { - const static string maze_str = "MAZE_"; - const static string txt_str = "_WINNERS.TXT"; - - string file_name = to_string(map_number); - if (map_number < 10) - file_name.insert(file_name.begin(), '0'); // Transforms single digit numbers into XX format - - file_name.insert(0, maze_str); - file_name.append(txt_str); // Gets file name to correct format - - struct Score { - string name; - int score; - }; - - vector scores; - - ifstream read_file; // Opening file for read and write - read_file.open(file_name, ios::in); // Reading file - - if (read_file.is_open()) { - read_file.ignore(numeric_limits::max(), '\n'); // Ignoring 2 first lines - read_file.ignore(numeric_limits::max(), '\n'); - - while (read_file.good() && !read_file.eof()) { - input::Name name_struct; - read_file >> name_struct; - - char sep; - read_file >> sep; - - int read_score; - read_file >> read_score; - - read_file.ignore(numeric_limits::max(), '\n'); - - if (read_file.peek() != '\n') { - Score score = { name_struct.name, read_score }; - scores.push_back(score); - } - } - - read_file.close(); - } - - ofstream write_file; - write_file.open(file_name, ios::trunc); // Writing to file - if (!write_file.is_open()) { - return false; - } - - scores.push_back({ player_name, player_score }); - - sort(scores.begin(), scores.end(), [] (auto first, auto second) { - return first.score < second.score; - }); - - write_file << "Player - Time\n"; - write_file << "----------------------\n"; - - for (auto score : scores) { - write_file << score.name; - for (int i = 0; i < 15 - input::utf8Len(score.name.c_str()); i++) - write_file << ' '; - - write_file << " - "; - - for (int i = 0; i < 4 - to_string(score.score).length(); i++) - write_file << ' '; - - write_file << score.score << '\n'; - } - - write_file << flush; - - write_file.close(); - return true; -} - -bool playGame() { - Map map; // Defined in entities.h as the vector cointaining the game's map - unsigned int map_number; - if (!loadMap(map_number, map)) - return false; - - if (map_number == 0) - return true; - - Player player; // Struct defining the player's status - vector robots; // Vector that will store all robots present in the map - - for (size_t i = 0; i < map.size(); i++) { - vector line = map.at(i); // Defining a vector for each line in map - for (size_t j = 0; j < line.size(); j++) { - char character = line.at(j); - - switch (character) { - case 'R': - robots.push_back({ - static_cast (robots.size() + 1), // Increasing vector size if robot is found - static_cast (j), - static_cast (i), - true - }); - break; - - case 'H': - player = { static_cast (j), static_cast (i), true }; // Assigning position and status to player struct - break; - - default: - break; - } - } - } - - game_timer(true); // Starts timer - while (true) { - input::clear_screen(); - for (size_t i = 0; i < map.size(); i++) { // Prints map to the console - vector line = map.at(i); - for (size_t j = 0; j < line.size(); j++) { - char ch = line.at(j); - cout << ch; - } - cout << endl; - } - - cout << endl; - - char letter; - int dx, dy; - bool is_not_eof = input::read_value( // Validates player's move - "Your move: ", - "Your move must be one of Q, A, Z, W, S, X, E, D, or C", - letter, - [player, &map, &dx, &dy] (auto res) { - if(get_deltas_from_direction(toupper(res), dx, dy) && is_move_valid(player, map, dx, dy)) - return string(); - else - return string("Your move must be one of Q, A, Z, W, S, X, E, D, or C AND must be a valid move"); - } - ); - - cout << endl; - - if (!is_not_eof) - return false; - - map.at(player.y)[player.x] = ' '; // Replaces previous place with empty space - move(player, map, dx, dy); - - for (size_t i = 0; i < robots.size(); i++) { - Robot &robot = robots.at(i); - - switch (move(robot, map, get_robot_dx(robot, player), get_robot_dy(robot, player))) { // Moves every robot independently in order - //Returns 1, 2 or 3 depending on the outcome - case 2: // Robot colided with another robot, creating a stump - for (size_t j = 0; j < robots.size(); j++) { - if (i == j) // Checking robot's id is not his own - continue; - - Robot &another_robot = robots.at(j); - if (robot.x == another_robot.x && robot.y == another_robot.y) - another_robot.alive = false; - } - - break; - - case 3: // Robot colided with player - player.alive = false; // Player's status becomes dead - break; - - default: - break; - } - } - - map.at(player.y)[player.x] = (player.alive ? 'H' : 'h'); // Replacing player with "h" if dead - - if (!player.alive) // Ending game when player dies - break; - - bool has_game_ended = true; - for (auto robot : robots) { - if (robot.alive) // Checking if there are robots alive; if so (and player's not dead), game will not end - has_game_ended = false; - } - - if (has_game_ended) - break; - } - - time_t score = game_timer(false); // Timestamping game's end - - input::clear_screen(); - for (size_t i = 0; i < map.size(); i++) { // Displays last game frame - vector line = map.at(i); - for (size_t j = 0; j < line.size(); j++) { - char ch = line.at(j); - cout << ch; - } - cout << endl; - } - - cout << endl; - - if (player.alive) { - cout << "You won :)" << endl; - cout << "You finished the game in " << score << " seconds" << endl; - } else { - cout << "You lost :(" << endl; - } - - cout << endl; - cout << "Press ENTER to continue..." << endl; - input::wait_for_enter(); - - if (!player.alive) - return true; - - input::Name name_struct; - if (!input::read_value("Well done, champion! What's your name? ", "Please specify your name (must have 15 characters or less)", name_struct)) - return false; - - string name = name_struct.name; - - saveScore(name, score, map_number); - return true; -} - -/* void writePoints(double start, string name) { // Writes winners file - string ln; - fstream points; - points.open ("points.txt", ios::in); - - points << name << setw(10) << " - " << endl; //indentar direito << - points.close(); -} */ - -void displayWinners() { // Displays winners - ifstream file; - file.open("points.txt", ios::in); - if (!file) { - cout << "No such file"; - } - - //display lines - file.close(); -} - int main(int, char**) { while (true) { - displayMenu(); + info::menu(); unsigned int option; - if (!input::read_value("Your option: ", "Your option must be one of 0, 1 or 2", option, [] (auto res) { - return (res > 2 ? string("Your option must be one of 0, 1 or 2") : string()); - })) + bool success = keyboard::read_value( + "Your option: ", + "Your option must be one of 0, 1 or 2", + option, + [] (auto res) { return res <= 2; } + ); + + if (!success) option = 0; // Exits the program cout << endl; - + + + string line; + switch (option) { case 0: //Exits program - cout << " ___ \n" - << " | _ )_ _ ___ \n" - << " | _ \\ || / -_) \n" - << " |___/\\_, \\___|\n" - << " |__/ \n" << endl; + info::bye(); return 0; case 1: // Displays Rules - displayRules(); - cout << endl << "Press ENTER to continue..." << endl; - input::wait_for_enter(); + info::rules(); + keyboard::wait_for_enter(); break; case 2: // Starts game sequence - if (!playGame()) { - cout << " ___ \n" - << " | _ )_ _ ___ \n" - << " | _ \\ || / -_) \n" - << " |___/\\_, \\___|\n" - << " |__/ \n" << endl; - return 0; + try { + play_game(); + } catch (bool ex) { + if (!ex) { + info::bye(); + return 1; + } } break; default: cout << "The power of Christ compels you!" << endl; - return 1; + return 2; } } } diff --git a/src/mazes.cpp b/src/mazes.cpp new file mode 100644 index 0000000..ff9a8fb --- /dev/null +++ b/src/mazes.cpp @@ -0,0 +1,177 @@ +#include + +#include "mazes.h" + +namespace mazes { + + /** + * @brief Returns whether or not a cell has something dead in it. + * + * @param value The value of a cell. + * @return true, if the cell has something dead in it, or false, otherwise. + */ + bool is_cell_dead(unsigned int value) { + return value & mazes::masks::DEAD; + } + + /** + * @brief Returns whether or not a cell has a human in it. + * + * @param value The value of a cell. + * @return true, if the cell has a human in it, or false, otherwise. + */ + bool is_cell_human(unsigned int value) { + return value & mazes::masks::HUMAN; + } + + /** + * @brief Returns whether or not a cell has a robot in it. + * + * @param value The value of a cell. + * @return true, if the cell has a robot in it, or false, otherwise. + */ + bool is_cell_robot(unsigned int value) { + return value & mazes::masks::ROBOT; + } + + /** + * @brief Returns whether or not a cell has a barrier in it. + * + * @param value The value of a cell. + * @return true, if the cell has a barrier in it, or false, otherwise. + */ + bool is_cell_barrier(unsigned int value) { + return value & mazes::masks::BARRIER; + } + + /** + * @brief Returns a character representing the value present in a given cell. + * If a value is not recognized, the character '?' is returned. + * + * @param value The value of a cell. + * @return The character representing the value present in a given cell. + */ + char translate_from_cell_value(unsigned int value) { + if (mazes::is_cell_barrier(value)) + return '*'; + + if (mazes::is_cell_human(value)) + if (mazes::is_cell_dead(value)) + return 'h'; + else + return 'H'; + + if (mazes::is_cell_robot(value)) + if (mazes::is_cell_dead(value)) + return 'r'; + else + return 'R'; + + if (value == mazes::EMPTY) + return ' '; + + return '?'; + } + + /** + * @brief Returns the value that is represented by the given character. + * If the character is not recognized, an error is thrown. + * + * @param value The value of a cell. + * @return The character representing the value present in a given cell. + */ + unsigned int translate_to_cell_value(char value) { + switch (value) { + case '*': + return mazes::masks::BARRIER; + + case 'H': + return mazes::masks::HUMAN; + + case 'R': + return mazes::masks::ROBOT; + + case ' ': + return mazes::EMPTY; + + default: + throw "The provided character is invalid"; + } + } + + /** + * @brief Gets the index of the cell at (x, y). + * + * @param width The maze's width. + * @param height The maze's height. + * @param x The horizontal position. + * @param y The vertical position. + * @return The cell's index. + */ + size_t get_cell_index(size_t width, size_t height, size_t x, size_t y) { + if (x >= width || y >= height) + throw "The provided coordinates are invalid"; + + return x + y * width; + } + + /** + * @brief Gets the index of the cell at (x, y). + * + * @param maze The maze the player is playing on. + * @param x The horizontal position. + * @param y The vertical position. + * @return The cell's index. + */ + size_t get_cell_index(const mazes::Maze &maze, size_t x, size_t y) { + return get_cell_index(maze.width, maze.height, x, y); + } + + /** + * @brief Returns the value of the cell at (x, y), by reference. + * + * @param maze The maze the player is playing on. + * @param x The horizontal position. + * @param y The vertical position. + * @return A reference to the cell's value. + */ + unsigned int& get_cell_value_at(const mazes::Maze &maze, size_t x, size_t y) { + return maze.cells[get_cell_index(maze, x, y)]; + } + + /** + * @brief Returns the value of the cell at the player's position, by reference. + * + * @param maze The maze the player is playing on. + * @return A reference to the cell's value. + */ + unsigned int& get_cell_value_at_player_position(const mazes::Maze &maze) { + return get_cell_value_at(maze, maze.player.x, maze.player.y); + } + + /** + * @brief Returns whether or not a maze number is valid. + * + * @param maze A maze number. + * @return true, if the maze number is valid, or false, otherwise. + */ + bool is_maze_number_valid(unsigned int maze) { + return maze < 100; + } + + /** + * @brief Displays the maze the player is playing on. + * + * @param maze The maze the player is playing on. + */ + void show_maze(const mazes::Maze &maze) { + for (int i = 0; i < maze.width * maze.height; i++) { + if (i % maze.width == 0) + cout << '\n'; + + cout << mazes::translate_from_cell_value(maze.cells[i]); + } + + cout << endl; + } +} // namespace mazes diff --git a/src/screen.cpp b/src/screen.cpp new file mode 100644 index 0000000..c8777b2 --- /dev/null +++ b/src/screen.cpp @@ -0,0 +1,19 @@ +#include + +#include "screen.h" + +using namespace std; + +namespace screen { + + /** + * @brief Clears the terminal window. + */ + void clear() { + for (int i = 0; i < 100; i++) { + cout << '\n'; + } + + cout << flush; + } +} // namespace screen diff --git a/src/timer.cpp b/src/timer.cpp new file mode 100644 index 0000000..11ed2f6 --- /dev/null +++ b/src/timer.cpp @@ -0,0 +1,24 @@ +#include "time.h" + +#include "timer.h" + +namespace timer { + + time_t start_time; + + /** + * @brief Starts the game timer. + */ + void start() { + start_time = time(NULL); + } + + /** + * @brief Returns the number of seconds since the game timer started. + * + * @return The number of seconds since the game timer started. + */ + time_t stop() { + return time(NULL) - start_time; + } +} // namespace timer diff --git a/src/utf8.cpp b/src/utf8.cpp new file mode 100644 index 0000000..ca18614 --- /dev/null +++ b/src/utf8.cpp @@ -0,0 +1,87 @@ +#include + +#include "utf8.h" + +using namespace std; + +namespace utf8 { + + /** + * @brief Returns the number of code points present in the given string. + * + * @param s the string whose UTF8 length is to be returned. + * @return the number of UTF8 code points present in the given string. + */ + size_t length(const char* str) { + size_t len = 0; + while (*str) + len += (*str++ & 0xc0) != 0x80; + + return len; + } + + /** + * @brief Returns the number of code points present in the given string. + * + * @param s the string whose UTF8 length is to be returned. + * @return the number of UTF8 code points present in the given string. + */ + size_t length(const string str) { + return length(str.c_str()); + } + + /** + * @brief Adds filler characters until the given string has the specified length. + * + * @param s The string that will be filled. + * @param filler The filler character. + * @param length The desired length. + * @param left true to add the filler characters to the start of the string, false otherwise. + * + * @return The filled string. + */ + string zfill(string str, const char filler, const size_t length, const bool left) { + if (utf8::length(str) >= length) + return str; + + size_t missing_characters = length - utf8::length(str); + for (size_t i = 0; i < missing_characters; i++) { + if (left) + str.insert(str.begin(), filler); + else + str.insert(str.end(), filler); + } + + return str; + } + + /** + * @brief Removes whitespace characters at the start of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + string ltrim(string str) { + return str.erase(0, str.find_first_not_of("\t\n\v\f\r ")); + } + + /** + * @brief Removes whitespace characters at the end of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + string rtrim(string str) { + return str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1); + } + + /** + * @brief Removes whitespace characters at both the start and the end of the string. + * + * @param str The string to trim. + * @return The trimmed string. + */ + string trim(string str) { + return ltrim(rtrim(str)); + } +} // namespace utf8 diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index ef08931..0000000 --- a/src/utils.cpp +++ /dev/null @@ -1,173 +0,0 @@ -#include -#include -#include "utils.h" - -using namespace std; - -std::string& ltrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") -{ - str.erase(0, str.find_first_not_of(chars)); - return str; -} - -std::string& rtrim(std::string& str, const std::string& chars = "\t\n\v\f\r ") -{ - str.erase(str.find_last_not_of(chars) + 1); - return str; -} - -std::string& trim(std::string& str, const std::string& chars = "\t\n\v\f\r ") -{ - return ltrim(rtrim(str, chars), chars); -} - -namespace input { - std::basic_istream& operator>>(std::basic_istream& istream, Name &name) { - char ch; - string result; - - while (result.length() < 15 && istream.good() && istream.peek() != '\n' && istream.get(ch)) - result.push_back(ch); - - trim(result); - name.name = result; - return istream; - } - - std::basic_ostream& operator<<(std::basic_ostream& ostream, Name &name) { - ostream << name.name; - return ostream; - } - - template - bool read_value_interactive(string prompt, string default_warning, T &result, const function validator) { - static string csi = "\x1B["; - - string warning; - cout << "? " << prompt << flush; - while (true) { - // Is true if, and only if, all the content present on the line is a number - bool is_input_valid = cin.peek() != '\n' - && cin >> result && cin.peek() == '\n'; - - warning = is_input_valid ? validator(result) : default_warning; - - if (is_input_valid) - is_input_valid = warning.length() == 0; - - // Clear the buffer - // We do this to avoid having "123a[EOF]" trigger the loop more than once. - cin.clear(); - cin.ignore(numeric_limits::max(), '\n'); - - if (is_input_valid) { - // Change status to valid - cout << csi << 'F' << csi << 'G'; - cout << "✓"; - - cout << csi << prompt.length() + 3 << 'G'; // Place cursor at the start of the input area - cout << csi << 'K'; // Clear the input area - - cout << result << endl; - - cout << csi << 'K' << flush; // Clear the line in case there is something there - return true; - } else { - if (!cin.eof()) { - // Show warning - cout << ">> " << warning; - cout << csi << 'F'; // Place cursor on the previous line - } - - cout << csi << prompt.length() + 3 << 'G'; // Place cursor at the start of the input area - cout << csi << 'K' << flush; // Clear the input area - - if (cin.eof()) { - cout << "EOF" << endl; - - // Change status to invalid - cout << csi << 'F' << csi << 'G'; - cout << "×"; - cout << csi << 'E' << flush; - cout << csi << 'K' << flush; // Clear the line in case there is something there - return false; - } - } - } - } - - template - bool read_value_dinossaur(string prompt, string default_warning, T &result, const function validator) { - string warning; - while (true) { - cout << "? " << prompt << flush; - bool is_input_valid = cin.peek() != '\n' - && cin >> result && cin.peek() == '\n'; - - if (is_input_valid) - is_input_valid = (warning = validator(result)).length() == 0; - - cin.clear(); - cin.ignore(numeric_limits::max(), '\n'); - - if (is_input_valid) { - return true; - } else { - if (cin.eof()) { - cout << "EOF" << endl; - return false; - } else { - cout << "\u26A0 " << warning << endl; - } - } - } - } - - bool read_value(std::string prompt, string default_warning, unsigned int &result, const std::function validator) { - if (isatty(fileno(stdout))) { - return read_value_interactive(prompt, default_warning, result, validator); - } else { - return read_value_dinossaur(prompt, default_warning, result, validator); - } - } - - bool read_value(std::string prompt, string default_warning, char &result, const std::function validator) { - if (isatty(fileno(stdin)) && isatty(fileno(stdout))) { - return read_value_interactive(prompt, default_warning, result, validator); - } else { - return read_value_dinossaur(prompt, default_warning, result, validator); - } - } - - bool read_value(std::string prompt, string default_warning, Name &result, const std::function validator) { - if (isatty(fileno(stdin)) && isatty(fileno(stdout))) { - return read_value_interactive(prompt, default_warning, result, validator); - } else { - return read_value_dinossaur(prompt, default_warning, result, validator); - } - } - - void clear_screen() { - for (int i = 0; i < 5; i++) { - cout << '\n'; - } - - cout << flush; - } - - void wait_for_enter() { - cin.ignore(numeric_limits::max(), '\n'); - } - - int utf8Len(const char* s) { - int len = 0; - while (*s) - len += (*s++ & 0xc0) != 0x80; - return len; - } - -} // namespace input - -namespace files { - -} // namespace files