Compare commits
9 Commits
726bda2a80
...
725777d4fa
| Author | SHA1 | Date | |
|---|---|---|---|
| 725777d4fa | |||
| 12d5ca5d41 | |||
| 787164f086 | |||
| bec5293659 | |||
| d6966f69db | |||
| 8823945938 | |||
| 4402bbfe3f | |||
| f0c7bb3f18 | |||
| 78f03cbfb5 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ cmake*
|
||||
CMakeC*
|
||||
Makefile
|
||||
maze_*
|
||||
documentation/
|
||||
@@ -1,9 +1,9 @@
|
||||
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 -Wall")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++20 -Wall")
|
||||
|
||||
include_directories(
|
||||
include
|
||||
|
||||
37
README.md
Normal file
37
README.md
Normal 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
|
||||
```
|
||||
53
doc/multithread_maze_server.md
Normal file
53
doc/multithread_maze_server.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Реализация сетевого серверного приложения "Лабиринт" на основе многопоточности
|
||||
При подключении клиента сервер порождает новый поток для работы с этим клиентом.
|
||||
|
||||
## Протокол пользовательского уровня
|
||||
UML-диаграмма протокола пользовательского уровня представлена на рисунке ниже.
|
||||

|
||||
|
||||
## Описание работы приложения
|
||||
Сервер `maze-server` ожидает подключения клиентов по протоколу TCP на заданный порт (по умолчанию 1024). После установления соединения сервер запрашивает имя игрока, а затем начинает игру в лабиринте. Лабиринт генерируется случайным образом при старте каждой новой игровой сессии. Гарантируется наличие хотя бы одного пути из начальной точки `(0, 0)` в конечную точку `(2, 2)`. Добавление дополнительных стен происходит с соблюдением условия достижимости цели.
|
||||
|
||||
Cхема и нумерация клеток лабиринта представлена на рисунке ниже.
|
||||
|
||||

|
||||
|
||||
Клиент отправляет команды движения ("вперёд", "направо", "налево", "назад") или команду "сдаюсь" для завершения игры. Сервер обрабатывает команды, проверяет возможность хода, обновляет состояние лабиринта и отправляет клиенту текстовый ответ с результатом хода, количеством оставшихся ходов и текущей позицией (в формате координат). Игра завершается, если игрок достигает конечной точки (позиция 8), исчерпывает ходы или сдаётся.
|
||||
|
||||
|
||||
### Запуск сервера
|
||||
Приложение сервера при запуске принимает следующие необязательные ключи:
|
||||
|
||||
- `-h` — задаёт имя хоста, к которому будет привязан сокет;
|
||||
- `-p` — задаёт номер порта для прослушивания;
|
||||
- `-n` — задаёт максимальное количество шагов, разрешённое для одного игрока;
|
||||
- `-s` — включает сервисный режим (без ограничений по количеству шагов).
|
||||
|
||||
```bash
|
||||
./maze-server -h localhost -p 1024 -n 10 -s
|
||||
```
|
||||
По умолчанию сокет сервера связывается с адресом `localhost:1024`, игрокам разрешено 10 шагов, сервисный режим выключен.
|
||||
|
||||
Завершение работы сервера — `Ctrl+C`.
|
||||
|
||||
Клиент подключается к серверу, отправляет своё имя и получает интерактивную игру в лабиринт.
|
||||
Поддерживаемые команды:
|
||||
- `вперёд` — перемещение вверх;
|
||||
- `направо` — перемещение вправо;
|
||||
- `налево` — перемещение влево;
|
||||
- `назад` — перемещение вниз;
|
||||
- `cдаюсь` — завершение игры досрочно.
|
||||
|
||||
|
||||
Если игрок исчерпал все ходы, но не дошёл до финиша, он считается проигравшим. Финиш находится в позиции (2, 2) (координаты x, y).
|
||||
|
||||
### Запуск клиента
|
||||
Приложение клиента при запуске принимает два необязательных ключа:
|
||||
- `-h` — задаёт имя хоста сервера для подключения;
|
||||
|
||||
- `-p` — задаёт номер порта сервера.
|
||||
```bash
|
||||
./maze_client -h localhost -p 1024
|
||||
```
|
||||
По умолчанию клиент пытается подключиться к адресу `localhost:1024`.
|
||||
|
||||
4
doc/numeration_maze.svg
Normal file
4
doc/numeration_maze.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 29 KiB |
@@ -1,3 +1,8 @@
|
||||
/*! @file client.hpp
|
||||
Заголовочный файл клиента для игры в лабиринт на базе сокетов.
|
||||
@author ParkSuMin
|
||||
@date 2025.04.30 */
|
||||
|
||||
#ifndef CLIENT_HPP
|
||||
#define CLIENT_HPP
|
||||
|
||||
@@ -8,15 +13,31 @@
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
|
||||
/*! Класс клиента для взаимодействия с сервером игры в лабиринт. */
|
||||
class Client{
|
||||
private:
|
||||
int sock;
|
||||
int sock; ///< Сокет для соединения с сервером.
|
||||
public:
|
||||
/*! Конструктор по умолчанию.
|
||||
@details Конструктор создаёт сокет клиента и связывает его с предоставленным операционной системой адресом. В
|
||||
случае ошибки вызывает исключение.
|
||||
@throw std::runtime_error При ошибке создания или связывания сокета. */
|
||||
Client();
|
||||
|
||||
void run(const std::string&, const unsigned short);
|
||||
/*! Запускает клиентское приложение.
|
||||
@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);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,34 +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;
|
||||
|
||||
/*! Класс лабиринта для игры. */
|
||||
class Maze{
|
||||
private:
|
||||
std::unordered_map<int, std::vector<bool>> graph;
|
||||
int moves_left;
|
||||
std::unordered_map<int, std::vector<bool>> graph; ///< Граф лабиринта, где узлы связаны направлениями.
|
||||
int moves_left; ///< Количество оставшихся ходов.
|
||||
|
||||
/*! Проверяет наличие пути между двумя узлами.
|
||||
@details Использует поиск в ширину (BFS) для проверки существования пути от start до end.
|
||||
@param start Начальный узел.
|
||||
@param end Конечный узел.
|
||||
@return true, если путь существует, false — иначе. */
|
||||
bool is_path_exists(int start, int end);
|
||||
|
||||
/*! Структура, представляющая ребро в графе лабиринта. */
|
||||
struct Edge {
|
||||
int node1, dir1;
|
||||
int node2, dir2;
|
||||
int node1; ///< Первый узел ребра.
|
||||
int dir1; ///< Направление от первого узла.
|
||||
int node2; ///< Второй узел ребра.
|
||||
int dir2; ///< Направление от второго узла.
|
||||
};
|
||||
public:
|
||||
bool test_mode;
|
||||
bool test_mode; ///< Флаг тестового режима (без ограничения ходов).
|
||||
|
||||
/*! Создаёт лабиринт.
|
||||
@details Инициализирует граф лабиринта, добавляет случайные стены и задаёт количество ходов.
|
||||
@param flag Флаг тестового режима (true — без ограничения ходов).
|
||||
@param steps Количество ходов, доступных игроку (игнорируется в тестовом режиме). */
|
||||
Maze(bool flag, int steps);
|
||||
int get_moves_left() const;
|
||||
bool is_wall(int node, int direction) const;
|
||||
|
||||
/*! Возвращает количество оставшихся ходов.
|
||||
@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
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/*! @file server.hpp
|
||||
Заголовочный файл сервера для игры в лабиринт на базе сокетов.
|
||||
@author ParkSuMin
|
||||
@date 2025.04.30 */
|
||||
|
||||
#ifndef SERVER_HPP
|
||||
#define SERVER_HPP
|
||||
|
||||
@@ -11,19 +16,44 @@
|
||||
#include <unistd.h>
|
||||
#include <cstring>
|
||||
|
||||
/*! Максимальное количество клиентов, которые могут быть в очереди на подключение. */
|
||||
const int MAX_CLIENTS = 512;
|
||||
|
||||
/*! Размер буфера для обмена данными между сервером и клиентом. */
|
||||
const int BUFFER_SIZE = 1024;
|
||||
|
||||
/*! Класс сервера для игры в лабиринт с использованием сокетов (протокол TCP). */
|
||||
class Server {
|
||||
private:
|
||||
bool service_mode;
|
||||
int server_socket;
|
||||
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(const std::string& host, const unsigned short port, bool service_flag);
|
||||
void start(int steps = 10);
|
||||
/*! Создаёт экземпляр сервера.
|
||||
@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
12
server-test.sh
Executable 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"
|
||||
@@ -1,3 +1,8 @@
|
||||
/*! @file client.cpp
|
||||
Исходный файл клиента для игры в лабиринт на базе сокетов.
|
||||
@author ParkSuMin
|
||||
@date 2025.04.30 */
|
||||
|
||||
#include "client.hpp"
|
||||
|
||||
// void print_instructions() {
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
/*! @file client_main.cpp
|
||||
Главный файл клиента для игры в лабиринт на базе сокетов.
|
||||
@author ParkSuMin
|
||||
@date 2025.04.30 */
|
||||
|
||||
#include "client.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){
|
||||
int opt;
|
||||
std::string host = "localhost";
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
/*! @file maze.cpp
|
||||
Исходный файл класса лабиринта для игры.
|
||||
@author ParkSuMin
|
||||
@date 2025.04.30 */
|
||||
|
||||
#include "maze.hpp"
|
||||
#include <iostream>
|
||||
|
||||
Maze::Maze(bool _test_mode, int _steps){
|
||||
std::vector<Edge> edges = {
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
/*! @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);
|
||||
}
|
||||
|
||||
Server::Server(const std::string& h, const unsigned short p, bool _service){
|
||||
service_mode = _service;
|
||||
if (service_mode) {
|
||||
std::cout << "Service mode is ON" << std::endl;
|
||||
}
|
||||
|
||||
Server::Server(const std::string& h, const unsigned short p){
|
||||
sockaddr_in server_address;
|
||||
|
||||
if ((server_socket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||
@@ -42,7 +42,7 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
|
||||
|
||||
int bytes_received = recv(client_socket, buffer, sizeof(buffer), 0);
|
||||
if (bytes_received <= 0) {
|
||||
std::cout << "Error in getting player name";
|
||||
std::cout << "Error in getting player name" << std::endl;;
|
||||
close(client_socket);
|
||||
return;
|
||||
}
|
||||
@@ -129,12 +129,16 @@ void Server::handle_client(int client_socket, bool mode, int steps) {
|
||||
response = "вы проиграли\n";
|
||||
send(client_socket, response.c_str(), response.size(), 0);
|
||||
}
|
||||
|
||||
shutdown(client_socket, SHUT_RDWR);
|
||||
close(client_socket);
|
||||
std::cout << "Игрок " << player_name << " отключился" << std::endl;
|
||||
}
|
||||
|
||||
void Server::start(int steps) {
|
||||
void Server::start(int steps, bool service_mode) {
|
||||
if (service_mode) {
|
||||
std::cout << "Service mode is ON" << std::endl;
|
||||
}
|
||||
|
||||
int new_socket;
|
||||
|
||||
if (listen(server_socket, MAX_CLIENTS) < 0) {
|
||||
@@ -151,4 +155,5 @@ void Server::start(int steps) {
|
||||
std::cout << "Новое соединение установлено" << std::endl;
|
||||
std::thread(&Server::handle_client, this, new_socket, service_mode, steps).detach();
|
||||
}
|
||||
shutdown(server_socket, SHUT_RDWR);
|
||||
}
|
||||
@@ -1,5 +1,30 @@
|
||||
/*! @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) {
|
||||
int opt;
|
||||
std::string host = "localhost";
|
||||
@@ -31,8 +56,8 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
|
||||
try {
|
||||
Server server(host, port, service_mode);
|
||||
server.start(steps);
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user