Compare commits

5 Commits

9 changed files with 66 additions and 19 deletions

View File

@@ -5,6 +5,9 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -Wall")
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
include_directories( include_directories(
include include
) )

25
doc/maze.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -3,14 +3,15 @@
## Протокол пользовательского уровня ## Протокол пользовательского уровня
UML-диаграмма протокола пользовательского уровня представлена на рисунке ниже. UML-диаграмма протокола пользовательского уровня представлена на рисунке ниже.
![UML-диаграмма протокола пользовательского уровня](echo-protocol.svg)
![UML-диаграмма протокола пользовательского уровня](maze.svg)
## Описание работы приложения ## Описание работы приложения
Сервер `maze-server` ожидает подключения клиентов по протоколу TCP на заданный порт (по умолчанию 1024). После установления соединения сервер запрашивает имя игрока, а затем начинает игру в лабиринте. Лабиринт генерируется случайным образом при старте каждой новой игровой сессии. Гарантируется наличие хотя бы одного пути из начальной точки `(0, 0)` в конечную точку `(2, 2)`. Добавление дополнительных стен происходит с соблюдением условия достижимости цели. Сервер `MazeServer` ожидает подключения клиентов по протоколу TCP на заданный порт (по умолчанию 2000). После установления соединения сервер запрашивает имя игрока, а затем начинает игру в лабиринте. Лабиринт генерируется случайным образом при старте каждой новой игровой сессии. Гарантируется наличие хотя бы одного пути из начальной точки `(0, 0)` в конечную точку `(2, 2)`. Добавление дополнительных стен происходит с соблюдением условия достижимости цели.
Cхема и нумерация клеток лабиринта представлена на рисунке ниже. Cхема и нумерация клеток лабиринта представлена на рисунке ниже.
![Схема лабиринта](maze_numeration.svg) ![Схема лабиринта](numeration_maze.svg)
Клиент отправляет команды движения ("вперёд", "направо", "налево", "назад") или команду "сдаюсь" для завершения игры. Сервер обрабатывает команды, проверяет возможность хода, обновляет состояние лабиринта и отправляет клиенту текстовый ответ с результатом хода, количеством оставшихся ходов и текущей позицией (в формате координат). Игра завершается, если игрок достигает конечной точки (позиция 8), исчерпывает ходы или сдаётся. Клиент отправляет команды движения ("вперёд", "направо", "налево", "назад") или команду "сдаюсь" для завершения игры. Сервер обрабатывает команды, проверяет возможность хода, обновляет состояние лабиринта и отправляет клиенту текстовый ответ с результатом хода, количеством оставшихся ходов и текущей позицией (в формате координат). Игра завершается, если игрок достигает конечной точки (позиция 8), исчерпывает ходы или сдаётся.
@@ -24,9 +25,9 @@ Cхема и нумерация клеток лабиринта представ
- `-s` — включает сервисный режим (без ограничений по количеству шагов). - `-s` — включает сервисный режим (без ограничений по количеству шагов).
```bash ```bash
./maze-server -h localhost -p 1024 -n 10 -s ./maze-server -h localhost -p 2000 -n 10 -s
``` ```
По умолчанию сокет сервера связывается с адресом `localhost:1024`, игрокам разрешено 10 шагов, сервисный режим выключен. По умолчанию сокет сервера связывается с адресом `localhost:2000`, игрокам разрешено 10 шагов, сервисный режим выключен.
Завершение работы сервера — `Ctrl+C`. Завершение работы сервера — `Ctrl+C`.
@@ -47,7 +48,7 @@ Cхема и нумерация клеток лабиринта представ
- `-p` — задаёт номер порта сервера. - `-p` — задаёт номер порта сервера.
```bash ```bash
./maze_client -h localhost -p 1024 ./maze_client -h localhost -p 2000
``` ```
По умолчанию клиент пытается подключиться к адресу `localhost:1024`. По умолчанию клиент пытается подключиться к адресу `localhost:2000`.

View File

@@ -13,6 +13,9 @@
#include <unistd.h> #include <unistd.h>
#include <netdb.h> #include <netdb.h>
/*! Размер буфера для обмена данными между сервером и клиентом. */
const int BUFFER_SIZE = 1024;
/*! Класс клиента для взаимодействия с сервером игры в лабиринт. */ /*! Класс клиента для взаимодействия с сервером игры в лабиринт. */
class Client{ class Client{
private: private:

View File

@@ -49,6 +49,10 @@ void Client::run(const std::string& h, const unsigned short p) {
std::string player_name; std::string player_name;
std::getline(std::cin, player_name); std::getline(std::cin, player_name);
if (player_name.empty() || player_name.size() > BUFFER_SIZE) {
throw std::runtime_error("Incorrect name!");
}
if (!ping(serv_addr)) { if (!ping(serv_addr)) {
throw std::runtime_error("Connection lost!"); throw std::runtime_error("Connection lost!");
} }
@@ -61,25 +65,30 @@ void Client::game(){
std::cout << "Игра началась!" << std::endl; std::cout << "Игра началась!" << std::endl;
//print_instructions(); //print_instructions();
char buffer[1024] = {0}; char buffer[BUFFER_SIZE] = {0};
while (true) { while (true) {
std::cout << "Введите команду: "; std::cout << "Введите команду: ";
std::string command; std::string command;
std::getline(std::cin, command); std::getline(std::cin, command);
if (command.empty() || command.size() > BUFFER_SIZE) {
std::cout << "Некорректная команда" << std::endl;
continue;
}
// Отправка команды на сервер // Отправка команды на сервер
send(sock, command.c_str(), command.size(), 0); send(sock, command.c_str(), command.size(), 0);
// Получение ответа от сервера // Получение ответа от сервера
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
int bytes_received = recv(sock, buffer, sizeof(buffer), 0); int bytes_received = recv(sock, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) { if (bytes_received == 0 || bytes_received > BUFFER_SIZE) {
throw std::runtime_error("Connection lost!"); throw std::runtime_error("Connection lost!");
//break; //break;
} }
std::string response(buffer); std::string response(buffer);
std::cout << "Ответ сервера: " << response; std::cout << "Ответ сервера: " << response << std::endl;
// Проверка завершения игры // Проверка завершения игры
if (response.find("вы выиграли") != std::string::npos || if (response.find("вы выиграли") != std::string::npos ||

View File

@@ -28,7 +28,7 @@
int main(int argc, char** argv){ int main(int argc, char** argv){
int opt; int opt;
std::string host = "localhost"; std::string host = "localhost";
short unsigned port = 1024u; short unsigned port = 2000u;
while ((opt = getopt(argc, argv, "h:p:")) != -1) { while ((opt = getopt(argc, argv, "h:p:")) != -1) {
switch (opt) { switch (opt) {

View File

@@ -6,7 +6,9 @@
#include "maze.hpp" #include "maze.hpp"
Maze::Maze(bool _test_mode, int _steps){ Maze::Maze(bool _test_mode, int _steps){
std::vector<Edge> edges = { // Инициализация графа лабиринта и его ребер
// Индексы направлений: 0 - север, 1 - восток, 2 - юг, 3 - запад
std::vector<Edge> edges = {
{0, 1, 1, 3}, {0, 0, 3, 2}, {1, 1, 2, 3}, {1, 0, 4, 2}, {0, 1, 1, 3}, {0, 0, 3, 2}, {1, 1, 2, 3}, {1, 0, 4, 2},
{2, 0, 5, 2}, {3, 1, 4, 3}, {3, 0, 6, 2}, {4, 1, 5, 3}, {2, 0, 5, 2}, {3, 1, 4, 3}, {3, 0, 6, 2}, {4, 1, 5, 3},
{4, 0, 7, 2}, {5, 0, 8, 2}, {6, 1, 7, 3}, {7, 1, 8, 3} {4, 0, 7, 2}, {5, 0, 8, 2}, {6, 1, 7, 3}, {7, 1, 8, 3}
@@ -18,11 +20,14 @@ Maze::Maze(bool _test_mode, int _steps){
graph[i] = std::vector<bool>(DIRECTIONS, true); graph[i] = std::vector<bool>(DIRECTIONS, true);
} }
// Выставление внешних стен
graph[6][0] = graph[7][0] = graph[8][0] = false; graph[6][0] = graph[7][0] = graph[8][0] = false;
graph[0][2] = graph[1][2] = graph[2][2] = false; graph[0][2] = graph[1][2] = graph[2][2] = false;
graph[0][3] = graph[3][3] = graph[6][3] = false; graph[0][3] = graph[3][3] = graph[6][3] = false;
graph[2][1] = graph[5][1] = graph[8][1] = false; graph[2][1] = graph[5][1] = graph[8][1] = false;
// Добавление внутренних стен
std::mt19937 rng(time(nullptr)); std::mt19937 rng(time(nullptr));
std::shuffle(edges.begin(), edges.end(), rng); std::shuffle(edges.begin(), edges.end(), rng);
int walls_to_add = std::uniform_int_distribution<int>(MIN_WALLS, MAX_WALLS)(rng); int walls_to_add = std::uniform_int_distribution<int>(MIN_WALLS, MAX_WALLS)(rng);
@@ -31,6 +36,7 @@ Maze::Maze(bool _test_mode, int _steps){
const auto& edge = edges[i]; const auto& edge = edges[i];
graph[edge.node1][edge.dir1] = false; graph[edge.node1][edge.dir1] = false;
graph[edge.node2][edge.dir2] = false; graph[edge.node2][edge.dir2] = false;
// Проверка на существование пути
if (!is_path_exists(0, 8)) { if (!is_path_exists(0, 8)) {
graph[edge.node1][edge.dir1] = true; graph[edge.node1][edge.dir1] = true;
graph[edge.node2][edge.dir2] = true; graph[edge.node2][edge.dir2] = true;

View File

@@ -41,7 +41,7 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
std::string response; std::string response;
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) { if (bytes_received <= 0 || bytes_received > BUFFER_SIZE) {
std::cout << "Error in getting player name" << std::endl;; std::cout << "Error in getting player name" << std::endl;;
close(client_socket); close(client_socket);
return; return;
@@ -55,7 +55,7 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
memset(buffer, 0, sizeof(buffer)); memset(buffer, 0, sizeof(buffer));
bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) { if (bytes_received <= 0 || bytes_received > BUFFER_SIZE) {
break; break;
} }
@@ -65,7 +65,7 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
int direction = -1; int direction = -1;
int new_position = current_position; int new_position = current_position;
if (command == "вперёд") { if (command == "вперед") {
direction = 0; direction = 0;
new_position = current_position + 3; new_position = current_position + 3;
} else if (command == "направо") { } else if (command == "направо") {
@@ -94,14 +94,14 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
maze.set_moves_left(moves_left - 1); maze.set_moves_left(moves_left - 1);
if (!check_status(maze)) if (!check_status(maze))
break; break;
response = "там стена, осталось " + std::to_string(maze.get_moves_left()) + " ходов\n"; response = "там стена, осталось " + std::to_string(maze.get_moves_left()) + " ходов";
send(client_socket, response.c_str(), response.size(), 0); send(client_socket, response.c_str(), response.size(), 0);
continue; continue;
} else if (maze.is_wall(current_position, direction)) { } else if (maze.is_wall(current_position, direction)) {
maze.set_moves_left(moves_left - 1); maze.set_moves_left(moves_left - 1);
if (!check_status(maze)) if (!check_status(maze))
break; break;
response = "там стена, осталось " + std::to_string(maze.get_moves_left()) + " ходов\n"; response = "там стена, осталось " + std::to_string(maze.get_moves_left()) + " ходов";
send(client_socket, response.c_str(), response.size(), 0); send(client_socket, response.c_str(), response.size(), 0);
continue; continue;
} else { } else {
@@ -119,7 +119,7 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
int x = current_position % 3; int x = current_position % 3;
int y = current_position / 3; int y = current_position / 3;
std::string text("(" + std::to_string(x) + ", " + std::to_string(y) + ")\n"); std::string text("(" + std::to_string(x) + ", " + std::to_string(y) + ")");
response = "успешно, осталось " + std::to_string(maze.get_moves_left()) + " ходов. Вы находитесь в " + text; response = "успешно, осталось " + std::to_string(maze.get_moves_left()) + " ходов. Вы находитесь в " + text;
send(client_socket, response.c_str(), response.size(), 0); send(client_socket, response.c_str(), response.size(), 0);
} }

View File

@@ -30,7 +30,7 @@ int main(int argc, char **argv) {
std::string host = "localhost"; std::string host = "localhost";
int steps = 10; int steps = 10;
bool service_mode = false; bool service_mode = false;
short unsigned port = 1024u; short unsigned port = 2000u;
while ((opt = getopt(argc, argv, "h:p:sn:")) != -1) { while ((opt = getopt(argc, argv, "h:p:sn:")) != -1) {
switch (opt) { switch (opt) {