Compare commits

...

21 Commits

Author SHA1 Message Date
2d0bd162d6 Breaking update 2025-05-05 15:31:08 +03:00
896f1bb951 Little fix 2025-05-04 22:37:11 +03:00
b0fb83e030 Make vector diagram + comments for DFS method 2025-05-04 22:25:57 +03:00
651e8ec5aa Add diagrams and comments 2025-05-04 14:17:17 +03:00
725777d4fa Merge pull request 'Almost ready project' (#2) from Refactor into main
Reviewed-on: https://git.tjoyspotifylastfm.tech/ParkSuMin/Maze/pulls/2
2025-05-01 00:15:02 +02:00
12d5ca5d41 Gracefull down of c++ standart version
Add stress-test shell script
2025-05-01 02:36:10 +03:00
787164f086 Little cosmetic fix 2025-05-01 02:06:51 +03:00
bec5293659 Move iostream to hpp part
Fix visibility of maze's flag
2025-05-01 01:36:10 +03:00
d6966f69db Doxygen config 2025-05-01 01:27:21 +03:00
8823945938 Multithread maze server description (without UML)
Fix link in README file
2025-04-30 21:38:30 +03:00
4402bbfe3f Service mode flag as argument in start function of Server 2025-04-30 21:36:32 +03:00
f0c7bb3f18 Initial markdown description of server and client parts of maze game 2025-04-30 20:08:27 +03:00
78f03cbfb5 Readme file + update in maze.hpp 2025-04-30 19:58:11 +03:00
726bda2a80 Merge pull request 'experimental' (#1) from experimental into main
Reviewed-on: https://git.tjoyspotifylastfm.tech/ParkSuMin/Maze/pulls/1
2025-04-30 18:39:14 +02:00
87eff11337 Update CMakeLists.txt 2025-04-30 19:36:26 +03:00
d905c1a3f9 Refactoring and updating
Fix bug, when steps = 0, but player didn't lose
2025-04-30 19:36:14 +03:00
7f0e023e0c Edit throw text part in server 2025-04-30 12:32:02 +02:00
be1b36e7ce Edit text in server cerr part 2025-04-30 12:24:55 +02:00
062f95492a Edit throw text 2025-04-30 12:23:07 +02:00
79fcae617e Change structure of code base 2025-04-30 13:06:23 +03:00
3b84aa7740 Choose hostname and port (for server and client part) 2025-04-30 15:14:10 +03:00
16 changed files with 2989 additions and 120 deletions

3
.gitignore vendored
View File

@@ -3,4 +3,5 @@ CMakeFiles/
cmake*
CMakeC*
Makefile
maze_*
maze_*
documentation/

View File

@@ -1,9 +1,12 @@
cmake_minimum_required(VERSION 3.5)
project(maze LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++23")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -Wall")
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
include_directories(
include

2496
Doxyfile Normal file

File diff suppressed because it is too large Load Diff

37
README.md Normal file
View File

@@ -0,0 +1,37 @@
# Реализация сетевого приложения "Лабиринт"
## Задание
Разработать приложение, реализующее игру «Лабиринт» с компьютерным противником. Игра заключается в отыскании выхода из лабиринта человеком-игроком за заданное число ходов. Программа-клиент инициирует начало игры, отправляя программе-серверу имя игрока. Сервер генерирует случайным образом несколько стен в «лабиринте» размером 3 на 3 клетки. Игрок стартует в левой нижней клетке, его задача — дойти до противоположного угла поля за заданное число ходов-перемещений. Игрок может отправлять сообщения вида «вперёд», «направо», «налево», «назад», «сдаюсь». Сервер принимает сообщения игрока, проверяет их и на каждое сообщение присылает ответ («успешно», «там стена, осталось Y ходов», «вы проиграли», «вы выиграли»,). После завершения игры сетевое соединение завершается. В сетевом протоколе прикладного уровня следует предусмотреть режим тестирования производительности без ограничений на число ходов игрока.
Для игрока необходимо реализовать консольный интерфейс, обеспечивающий удобный (минимально необходимый) ввод с визуализацией хода игры: выполненные ходы и ответы компьютера на них, число ходов, сообщение о победе или проигрыше.
Для приложения необходимо разработать сетевой протокол прикладного уровня на основе TCP.
Приложение-сервер должно поддерживать одновременную работу с неограниченным числом игроков (в пределах вычислительных возможностей компьютера). Сервер необходимо реализовать как многопоточное приложение с использованием сокетов в блокирующем режиме.
Клиент должен работать по разработанному сетевому протоколу.
## Разработанное приложение
Реализация [сервера на основе многопоточности](doc/multithread_maze_server.md) и клиента игры-приложения.
## Требования
Для сборки и запуска необходима Unix-система и компилятор C++ с поддержкой стандарта c++23.
Необязательно:
- CMake
## Сборка и запуск
Для сборки при помощи CMake необходимо выполнить следующие команды в корневой папке проекта:
```bash
mkdir build
cd build
cmake ..
make
```
Собранные таким способом приложения сервера и клиента будут находиться в папке `build`.
## (Опционально) Генерация документации
Для генерации документации необходимо установить [Doxygen](https://www.doxygen.nl/download.html) и выполнить команду:
```bash
doxygen Doxyfile
```

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

@@ -0,0 +1,54 @@
# Реализация сетевого серверного приложения "Лабиринт" на основе многопоточности
При подключении клиента сервер порождает новый поток для работы с этим клиентом.
## Протокол пользовательского уровня
UML-диаграмма протокола пользовательского уровня представлена на рисунке ниже.
![UML-диаграмма протокола пользовательского уровня](maze.svg)
## Описание работы приложения
Сервер `MazeServer` ожидает подключения клиентов по протоколу TCP на заданный порт (по умолчанию 2000). После установления соединения сервер запрашивает имя игрока, а затем начинает игру в лабиринте. Лабиринт генерируется случайным образом при старте каждой новой игровой сессии. Гарантируется наличие хотя бы одного пути из начальной точки `(0, 0)` в конечную точку `(2, 2)`. Добавление дополнительных стен происходит с соблюдением условия достижимости цели.
Cхема и нумерация клеток лабиринта представлена на рисунке ниже.
![Схема лабиринта](numeration_maze.svg)
Клиент отправляет команды движения ("вперёд", "направо", "налево", "назад") или команду "сдаюсь" для завершения игры. Сервер обрабатывает команды, проверяет возможность хода, обновляет состояние лабиринта и отправляет клиенту текстовый ответ с результатом хода, количеством оставшихся ходов и текущей позицией (в формате координат). Игра завершается, если игрок достигает конечной точки (позиция 8), исчерпывает ходы или сдаётся.
### Запуск сервера
Приложение сервера при запуске принимает следующие необязательные ключи:
- `-h` — задаёт имя хоста, к которому будет привязан сокет;
- `-p` — задаёт номер порта для прослушивания;
- `-n` — задаёт максимальное количество шагов, разрешённое для одного игрока;
- `-s` — включает сервисный режим (без ограничений по количеству шагов).
```bash
./maze-server -h localhost -p 2000 -n 10 -s
```
По умолчанию сокет сервера связывается с адресом `localhost:2000`, игрокам разрешено 10 шагов, сервисный режим выключен.
Завершение работы сервера — `Ctrl+C`.
Клиент подключается к серверу, отправляет своё имя и получает интерактивную игру в лабиринт.
Поддерживаемые команды:
- `вперёд` — перемещение вверх;
- `направо` — перемещение вправо;
- `налево` — перемещение влево;
- `назад` — перемещение вниз;
- `cдаюсь` — завершение игры досрочно.
Если игрок исчерпал все ходы, но не дошёл до финиша, он считается проигравшим. Финиш находится в позиции (2, 2) (координаты x, y).
### Запуск клиента
Приложение клиента при запуске принимает два необязательных ключа:
- `-h` — задаёт имя хоста сервера для подключения;
- `-p` — задаёт номер порта сервера.
```bash
./maze_client -h localhost -p 2000
```
По умолчанию клиент пытается подключиться к адресу `localhost:2000`.

4
doc/numeration_maze.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 29 KiB

View File

@@ -1,20 +1,46 @@
#ifndef CLIENT
#define CLIENT
/*! @file client.hpp
Заголовочный файл клиента для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#ifndef CLIENT_HPP
#define CLIENT_HPP
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
const int PORT = 8080;
/*! Размер буфера для обмена данными между сервером и клиентом. */
const int BUFFER_SIZE = 1024;
/*! Класс клиента для взаимодействия с сервером игры в лабиринт. */
class Client{
private:
int sock;
int sock; ///< Сокет для соединения с сервером.
public:
Client() : sock(0){};
void run();
/*! Конструктор по умолчанию.
@details Конструктор создаёт сокет клиента и связывает его с предоставленным операционной системой адресом. В
случае ошибки вызывает исключение.
@throw std::runtime_error При ошибке создания или связывания сокета. */
Client();
/*! Запускает клиентское приложение.
@details Устанавливает соединение с сервером по указанному хосту и порту, после чего вызывает игровой цикл.
@param host Адрес сервера (название хоста или IP-адрес).
@param port Порт сервера для подключения. */
void run(const std::string& host, const unsigned short port);
/*! Основной игровой цикл.
@details Обрабатывает ввод пользователя, отправляет команды на сервер и получает ответы. */
void game();
/*! Отправляет тестовый запрос (ping) на сервер.
@details Используется для проверки доступности сервера.
@param server_address Структура адреса сервера.
@return Целое число, указывающее результат выполнения (0 — успех, иначе — ошибка). */
int ping(struct sockaddr_in);
};

View File

@@ -1,38 +1,69 @@
/*! @file maze.hpp
Заголовочный файл класса лабиринта для игры.
@author ParkSuMin
@date 2025.04.30 */
#ifndef MAZE_HPP
#define MAZE_HPP
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <ctime>
#include <climits>
#include <random>
#include <iostream>
/*! Размер лабиринта (количество узлов). */
const int MAZE_SIZE = 9;
/*! Количество направлений движения (вперёд, направо, назад, налево). */
const int DIRECTIONS = 4;
/*! Минимальное количество стен в лабиринте. */
const int MIN_WALLS = 3;
/*! Максимальное количество стен в лабиринте. */
const int MAX_WALLS = 5;
const int DEFAULT_MOVES = 10;
/*! Класс лабиринта для игры. */
class Maze{
private:
std::unordered_map<int, std::vector<bool>> graph;
int moves_left;
bool is_path_exists(int, int);
private:
std::unordered_map<int, std::vector<bool>> graph; ///< Граф лабиринта, где узлы связаны направлениями.
int moves_left; ///< Количество оставшихся ходов.
struct Edge {
int node1, dir1;
int node2, dir2;
};
public:
Maze(bool);
bool test_mode;
int get_moves_left() const;
bool is_wall(int, int) const;
/*! Проверяет наличие пути между двумя узлами.
@details Использует поиск в ширину (BFS) для проверки существования пути от start до end.
@param start Начальный узел.
@param end Конечный узел.
@return true, если путь существует, false — иначе. */
bool is_path_exists(int start, int end);
void set_moves_left(int);
//void generate_maze();
//void print_maze_info();
//void play_game();
/*! Структура, представляющая ребро в графе лабиринта. */
struct Edge {
int node1; ///< Первый узел ребра.
int dir1; ///< Направление от первого узла.
int node2; ///< Второй узел ребра.
int dir2; ///< Направление от второго узла.
};
public:
bool test_mode; ///< Флаг тестового режима (без ограничения ходов).
/*! Создаёт лабиринт.
@details Инициализирует граф лабиринта, добавляет случайные стены и задаёт количество ходов.
@param flag Флаг тестового режима (true — без ограничения ходов).
@param steps Количество ходов, доступных игроку (игнорируется в тестовом режиме). */
Maze(bool flag, int steps);
/*! Возвращает количество оставшихся ходов.
@return Целое число, представляющее оставшиеся ходы. */
int get_moves_left() const;
/*! Устанавливает количество оставшихся ходов.
@param _steps Новое значение количества ходов. */
void set_moves_left(int _steps);
/*! Проверяет наличие стены в указанном направлении от узла.
@param node Узел лабиринта.
@param direction Направление (0 — вперёд, 1 — направо, 2 — назад, 3 — налево).
@return true, если в указанном направлении стена, false — иначе. */
bool is_wall(int node, int direction) const;
};
#endif

View File

@@ -1,3 +1,8 @@
/*! @file server.hpp
Заголовочный файл сервера для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#ifndef SERVER_HPP
#define SERVER_HPP
@@ -7,20 +12,48 @@
#include <thread>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <cstring>
const int PORT = 8080;
const int MAX_CLIENTS = 100;
/*! Максимальное количество клиентов, которые могут быть в очереди на подключение. */
const int MAX_CLIENTS = 512;
/*! Размер буфера для обмена данными между сервером и клиентом. */
const int BUFFER_SIZE = 1024;
/*! Класс сервера для игры в лабиринт с использованием сокетов (протокол TCP). */
class Server {
private:
bool service_mode;
void handle_client(int client_socket, bool _mode); // Принимает клиентский сокет
bool service_mode; ///< Флаг сервисного режима (тестовый режим без ограничения ходов).
int server_socket; ///< Серверный сокет для обработки клиентских подключений.
/*! Обрабатывает подключение одного клиента.
@details Создаёт лабиринт для клиента, принимает команды, обновляет состояние игры и отправляет ответы.
@param socket Сокет клиента.
@param flag Флаг сервисного режима.
@param steps Количество ходов, доступных игроку (игнорируется в сервисном режиме). */
void handle_client(int socket, bool flag, int steps);
/*! Проверяет статус игры.
@details Проверяет, остались ли ходы у игрока, и активен ли тестовый режим.
@param maze Экземпляр объекта класса лабиринта.
@return true, если игра продолжается, false — если игра завершена. */
bool check_status(Maze& maze);
public:
Server() : service_mode(false){};
void start(); // Запуск сервера
/*! Создаёт экземпляр сервера.
@details Инициализирует серверный сокет, связывает его с указанным хостом и портом.
@throw std::runtime_error При ошибке создания сокета, получения хоста или привязки.
@param host Адрес сервера (название хоста или IP-адрес).
@param port Порт сервера для подключения клиентов. */
Server(const std::string& host, const unsigned short port);
/*! Запускает сервер.
@details Начинает прослушивание входящих подключений и создаёт отдельные потоки для обработки клиентов.
@throw std::runtime_error При ошибке прослушивания или принятия соединения.
@param steps Количество ходов, доступных каждому игроку.
@param service_flag Флаг активации сервисного режима. */
void start(int steps, bool service_flag);
};
#endif

12
server-test.sh Executable file
View File

@@ -0,0 +1,12 @@
#!/bin/bash
if [[ $# -lt 1 ]]; then
echo "no client path!"
exit 1
fi
COUNTER=0
while [[ COUNTER -lt 500 ]]; do
./$1 < Doxyfile > /dev/null &
let COUNTER++
done
echo "finish"

View File

@@ -1,72 +1,94 @@
/*! @file client.cpp
Исходный файл клиента для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#include "client.hpp"
void print_instructions() {
std::cout << "Доступные команды:\n";
std::cout << " - вперёд\n";
std::cout << " - направо\n";
std::cout << " - налево\n";
std::cout << " - назад\n";
std::cout << " - сдаюсь\n";
std::cout << "Введите команду для хода.\n";
}
// void print_instructions() {
// std::cout << "Доступные команды:" << std::endl;
// std::cout << " - вперёд" << std::endl;
// std::cout << " - направо" << std::endl;
// std::cout << " - налево" << std::endl;
// std::cout << " - назад" << std::endl;
// std::cout << " - сдаюсь" << std::endl;
// std::cout << "Введите команду для хода" << std::endl;
// }
int Client::ping(struct sockaddr_in address){
return connect(sock, (struct sockaddr*)&address, sizeof(address)) > 0;
return connect(sock, (struct sockaddr*)&address, sizeof(address)) != 0;
}
void Client::run() {
struct sockaddr_in serv_addr;
Client::Client(){
// Создание сокета
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
throw std::runtime_error("[Client::request] connect(2) call error");
throw std::runtime_error("Error in socket create");
}
}
void Client::run(const std::string& h, const unsigned short p) {
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Преобразование IP-адреса из текстового вида в бинарный
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
throw std::runtime_error("[Client::request] connect(2) call error");
hostent* host_name;
host_name = gethostbyname(h.c_str()); // Преобразование названия хоста в IP-адрес.
if (host_name == nullptr) {
throw std::runtime_error("Error in get hostname");
}
serv_addr.sin_port = htons(p);
memcpy(&serv_addr.sin_addr.s_addr, host_name->h_addr, host_name->h_length);
// Подключение к серверу
if (ping(serv_addr)) {
throw std::runtime_error("Connection lost!");
throw std::runtime_error("Invalid hostname:port");
}
std::cout << "Подключено к серверу. Введите ваше имя: ";
std::string player_name;
std::getline(std::cin, player_name);
if (ping(serv_addr)) {
if (player_name.empty() || player_name.size() > BUFFER_SIZE) {
throw std::runtime_error("Incorrect name!");
}
if (!ping(serv_addr)) {
throw std::runtime_error("Connection lost!");
}
// Отправка имени игрока на сервер
send(sock, player_name.c_str(), player_name.size(), 0);
std::cout << "Игра началась!\n";
}
print_instructions();
void Client::game(){
std::cout << "Игра началась!" << std::endl;
//print_instructions();
char buffer[1024] = {0};
char buffer[BUFFER_SIZE] = {0};
while (true) {
std::cout << "Введите команду: ";
std::string 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);
// Получение ответа от сервера
memset(buffer, 0, sizeof(buffer));
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!");
//break;
}
std::string response(buffer);
std::cout << "Ответ сервера: " << response;
std::cout << "Ответ сервера: " << response << std::endl;
// Проверка завершения игры
if (response.find("вы выиграли") != std::string::npos ||
@@ -74,6 +96,5 @@ void Client::run() {
break;
}
}
close(sock);
}

View File

@@ -1,9 +1,52 @@
/*! @file client_main.cpp
Главный файл клиента для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#include "client.hpp"
int main(){
/*! Главная функция клиента.
@details Обрабатывает аргументы командной строки, создаёт экземпляр клиента и запускает его.
@throw std::runtime_error При ошибке работы клиента.
@throw std::exception При непредвиденной ошибке.
@dot
digraph main {
ranksep=0.25;
node [shape=box,fontsize="10",fixedsize=true,width=2,height=0.3]
edge [arrowsize=0.5]
Beg [label="Начало",shape=ellipse]
End [label="Конец",shape=ellipse]
A [label="Проверка аргументов"]
B [label="Создание клиента"]
C [label="Запуск клиента"]
D [label="Обработка исключений"]
Beg->A->B->C->End
B->D
C->D->End
}
@enddot */
int main(int argc, char** argv){
int opt;
std::string host = "localhost";
short unsigned port = 2000u;
while ((opt = getopt(argc, argv, "h:p:")) != -1) {
switch (opt) {
case 'h':
host = optarg;
break;
case 'p':
port = static_cast<unsigned short>(atoi(optarg));
break;
default:
break;
}
}
try {
Client client;
client.run();
client.run(host, port);
client.game();
} catch (const std::runtime_error& e){
std::cerr << "Client application error: " << e.what() << std::endl;
return 1;

View File

@@ -1,24 +1,33 @@
#include "maze.hpp"
#include <iostream>
/*! @file maze.cpp
Исходный файл класса лабиринта для игры.
@author ParkSuMin
@date 2025.04.30 */
Maze::Maze(bool _test_mode){
std::vector<Edge> edges = {
#include "maze.hpp"
Maze::Maze(bool _test_mode, int _steps){
// Инициализация графа лабиринта и его ребер
// Индексы направлений: 0 - север, 1 - восток, 2 - юг, 3 - запад
std::vector<Edge> edges = {
{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},
{4, 0, 7, 2}, {5, 0, 8, 2}, {6, 1, 7, 3}, {7, 1, 8, 3}
};
test_mode = _test_mode;
moves_left = test_mode ? INT_MAX : DEFAULT_MOVES;
moves_left = test_mode ? INT_MAX : _steps;
for (int i = 0; i < MAZE_SIZE; ++i) {
graph[i] = std::vector<bool>(DIRECTIONS, true);
}
// Выставление внешних стен
graph[6][0] = graph[7][0] = graph[8][0] = false;
graph[0][2] = graph[1][2] = graph[2][2] = false;
graph[0][3] = graph[3][3] = graph[6][3] = false;
graph[2][1] = graph[5][1] = graph[8][1] = false;
// Добавление внутренних стен
std::mt19937 rng(time(nullptr));
std::shuffle(edges.begin(), edges.end(), rng);
int walls_to_add = std::uniform_int_distribution<int>(MIN_WALLS, MAX_WALLS)(rng);
@@ -27,6 +36,7 @@ Maze::Maze(bool _test_mode){
const auto& edge = edges[i];
graph[edge.node1][edge.dir1] = false;
graph[edge.node2][edge.dir2] = false;
// Проверка на существование пути
if (!is_path_exists(0, 8)) {
graph[edge.node1][edge.dir1] = true;
graph[edge.node2][edge.dir2] = true;

View File

@@ -1,44 +1,71 @@
/*! @file server.cpp
Исходный файл сервера для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#include "server.hpp"
bool Server::check_status(Maze &maze) {
return (!maze.test_mode && maze.get_moves_left() > 0);
}
void Server::handle_client(int client_socket, bool mode) {
Maze maze(mode); // Создаем экземпляр лабиринта для клиента
int current_position = 0; // Начальная позиция игрока
Server::Server(const std::string& h, const unsigned short p){
sockaddr_in server_address;
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
throw std::runtime_error("Error in create socket");
}
memset(reinterpret_cast<char*>(&server_address), '\0', sizeof(server_address));
server_address.sin_family = AF_INET;
hostent* host_name;
host_name = gethostbyname(h.c_str()); // Преобразование названия хоста в IP-адрес.
if (host_name == nullptr) {
throw std::runtime_error("Error in getting hostname");
}
server_address.sin_port = htons(p);
memcpy(&server_address.sin_addr.s_addr, host_name->h_addr, host_name->h_length);
if (bind(server_socket, reinterpret_cast<const sockaddr*>(&server_address), sizeof(server_address)) != 0) {
throw std::runtime_error("Error in bind part");
}
std::cout << "Сервер запущен на хосте " << h << " на порту " << p << std::endl;
}
void Server::handle_client(int client_socket, bool mode, int steps) {
Maze maze(mode, steps);
int current_position = 0;
std::string player_name;
char buffer[1024] = {0};
char buffer[BUFFER_SIZE] = {0};
std::string response;
// Получение имени игрока
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) {
std::cerr << "Ошибка получения имени игрока.\n";
if (bytes_received <= 0 || bytes_received > BUFFER_SIZE) {
std::cout << "Error in getting player name" << std::endl;;
close(client_socket);
return;
}
player_name = std::string(buffer);
std::cout << "Новый игрок: " << player_name << "\n";
std::cout << "Новый игрок: " << player_name << std::endl;
while (true) {
int moves_left = maze.get_moves_left();
memset(buffer, 0, sizeof(buffer));
bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) {
if (bytes_received <= 0 || bytes_received > BUFFER_SIZE) {
break;
}
std::string command(buffer);
std::cout << "Получена команда от игрока " << player_name << ": " << command << "\n";
std::cout << "Получена команда от игрока " << player_name << ": " << command << std::endl;
int direction = -1;
int new_position = current_position;
if (command == "вперёд") {
if (command == "вперед") {
direction = 0;
new_position = current_position + 3;
} else if (command == "направо") {
@@ -67,14 +94,14 @@ void Server::handle_client(int client_socket, bool mode) {
maze.set_moves_left(moves_left - 1);
if (!check_status(maze))
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);
continue;
} else if (maze.is_wall(current_position, direction)) {
maze.set_moves_left(moves_left - 1);
if (!check_status(maze))
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);
continue;
} else {
@@ -86,10 +113,13 @@ void Server::handle_client(int client_socket, bool mode) {
break;
}
maze.set_moves_left(moves_left - 1);
if (!check_status(maze))
break;
int x = current_position % 3;
int y = current_position / 3;
std::string text("(" + std::to_string(x) + ", " + std::to_string(y) + ")\n");
maze.set_moves_left(moves_left - 1);
std::string text("(" + std::to_string(x) + ", " + std::to_string(y) + ")");
response = "успешно, осталось " + std::to_string(maze.get_moves_left()) + " ходов. Вы находитесь в " + text;
send(client_socket, response.c_str(), response.size(), 0);
}
@@ -99,50 +129,31 @@ void Server::handle_client(int client_socket, bool mode) {
response = "вы проиграли\n";
send(client_socket, response.c_str(), response.size(), 0);
}
shutdown(client_socket, SHUT_RDWR);
close(client_socket);
std::cout << "Игрок " << player_name << " отключился.\n";
std::cout << "Игрок " << player_name << " отключился" << std::endl;
}
void Server::start() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("Ошибка создания сокета");
exit(EXIT_FAILURE);
void Server::start(int steps, bool service_mode) {
if (service_mode) {
std::cout << "Service mode is ON" << std::endl;
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("Ошибка настройки сокета");
exit(EXIT_FAILURE);
int new_socket;
if (listen(server_socket, MAX_CLIENTS) < 0) {
throw std::runtime_error("Error in listen");
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("Ошибка привязки сокета");
exit(EXIT_FAILURE);
}
if (listen(server_fd, MAX_CLIENTS) < 0) {
perror("Ошибка прослушивания");
exit(EXIT_FAILURE);
}
std::cout << "Сервер запущен на порту " << PORT << "\n";
sockaddr address;
socklen_t address_size = sizeof(address);
while (true) {
if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
perror("Ошибка принятия соединения");
exit(EXIT_FAILURE);
if ((new_socket = accept(server_socket, (struct sockaddr*)&address, &address_size)) < 0) {
throw std::runtime_error("Error in accept");
}
std::cout << "Новое соединение установлено.\n";
std::thread(&Server::handle_client, this, new_socket, true).detach();
std::cout << "Новое соединение установлено" << std::endl;
std::thread(&Server::handle_client, this, new_socket, service_mode, steps).detach();
}
shutdown(server_socket, SHUT_RDWR);
}

View File

@@ -1,9 +1,71 @@
/*! @file server_main.cpp
Главный файл сервера для игры в лабиринт на базе сокетов.
@author ParkSuMin
@date 2025.04.30 */
#include "server.hpp"
/*! Главная функция сервера.
@details Обрабатывает аргументы командной строки, создаёт экземпляр сервера и запускает его.
@throw std::runtime_error При ошибке работы сервера.
@throw std::exception При непредвиденной ошибке.
@dot
digraph main {
ranksep=0.25;
node [shape=box,fontsize="10",fixedsize=true,width=2,height=0.3]
edge [arrowsize=0.5]
Beg [label="Начало",shape=ellipse]
End [label="Конец",shape=ellipse]
A [label="Проверка аргументов"]
B [label="Создание сервера"]
C [label="Запуск сервера"]
D [label="Обработка исключений"]
Beg->A->B->C->End
B->D
C->D->End
}
@enddot */
int main(int argc, char **argv) {
Server server;
server.start();
int opt;
std::string host = "localhost";
int steps = 10;
bool service_mode = false;
short unsigned port = 2000u;
while ((opt = getopt(argc, argv, "h:p:sn:")) != -1) {
switch (opt) {
case 'h':
host = optarg;
break;
case 'p':
port = static_cast<unsigned short>(atoi(optarg));
break;
case 'n':
steps = atoi(optarg);
if(steps <= 0) {
std::cerr << "Invalid steps" << std::endl;
return 1;
}
break;
case 's':
service_mode = true;
break;
default:
break;
}
}
try {
Server server(host, port);
server.start(steps, service_mode);
} catch (const std::runtime_error& e){
std::cerr << "Server application error: " << e.what() << std::endl;
return 1;
} catch (...){
std::cerr << "Unexpected error in client application" << std::endl;
return 2;
}
std::cout << "Server application finished" << std::endl;
return 0;
}