Please use this identifier to cite or link to this item: https://er.chdtu.edu.ua/handle/ChSTU/9671
Title: Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних зіткнень на морі
Authors: Білоніг, Анатолій Вікторович
Перепьолкін, Олександр Олександрович
Keywords: WEB-Додаток;Морський бій;Мультиплеєр;Team Battle;FFA;Алгоритм взаємодії гравців;Spring;Postgresql;Моделювання морських зіткнень;Ігровий процес
Issue Date: 17-Jun-2026
Abstract: АНОТАЦІЯ Кваліфікаційна робота бакалавра на тему «Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних зіткнень на морі» містить 143 сторінки, 4 таблиць, 79 рисунків, список використаних джерел з 10 найменувань, 4 додатка. Метою виконання кваліфікаційної роботи бакалавра є проектування та розробка багатокористувацької версії програмного забезпечення «Морський бій». Головні завдання при проектуванні інформаційної системи 1 Переробити back-end частину web-додатку під визначені технології; 2 Реалізувати систему пошуку гравців та створення лобі; 3 Стилізувати наявні сторінки web-додатку під різні пристрої; 4 Реалізувати алгоритм битви Team Battle; 5 Модифікувати систему підбору гравців із додаванням типу гри FFA; 6 Реалізувати алгоритм битви FFA із урахуванням правил битви «Кожен сам за себе»; 7 Розширити створений функціонал на всі режими гри; 8 Опублікувати web-додаток на довільному хостингу. Об'єктом роботи є програмне забезпечення ігрової інформаційної технології. Предметом розробки є процес побудови програмного забезпечення інформаційної технології візуальної взаємодії користувачів при грі в «Морський бій».
URI: https://er.chdtu.edu.ua/handle/ChSTU/9671
Appears in Collections:121 Інженерія програмного забезпечення (Інженерія програмного забезпечення)



Items in DSpace are protected by copyright, with all rights reserved, unless otherwise indicated.

Extracted text
МІНІСТЕРСТВО ОСВІТИ І НАУКИ УКРАЇНИ 
ЧЕРКАСЬКИЙ ДЕРЖАВНИЙ ТЕХНОЛОГІЧНИЙ УНІВЕРСИТЕТ 
Факультет інформаційних технологій і систем 
Кафедра програмного забезпечення автоматизованих систем 
 
 
ПОЯСНЮВАЛЬНА ЗАПИСКА 
до кваліфікаційної роботи 
бакалавра 
 
 
на тему:  «Програмна реалізація алгоритму взаємодії гравців для 
моделювання оперативних зіткнень на морі» 
 
 
Виконав: студент 4 курсу, групи ПЗ-2204 
спеціальності  
121 «Інженерія програмного забезпечення»  
(шифр і назва напряму підготовки) 
 
 
Студент Перепьолкін О.О. 
 (прізвище та ініціали) 
Керівник Білоніг А.В. 
 (прізвище та ініціали) 
Рецензент  Захарова М.В. 
 (прізвище та ініціали) 
 
 
 
Черкаси 2026 
 Черкаський державний технологічний університет  
повне найменування вищого навчального закладу 
Факультет інформаційних технологій і систем  
Кафедра  програмного забезпечення автоматизованих систем  
Освітній рівень  бакалавр  
Спеціальність 121 «Інженерія програмного забезпечення»  
Освітня програма Інженерія програмного забезпечення  
ЗАТВЕРДЖУЮ 
Зав. кафедри ПЗАС, професор 
____________________ С. Голуб  
«___» _______________ 2026 року 
З А В Д А Н Н Я 
НА КВАЛІФІКАЦІЙНУ РОБОТУ СТУДЕНТУ 
  Перепьолкіну Олександру Олександровичу  
(прізвище, ім’я, по батькові) 
1. Тему проекту (роботи) Програмна реалізація алгоритму взаємодії гравців для моделювання 
оперативних зіткнень на морі  
Керівник проекту (роботи) Білоніг Анатолій Вікторович асистент кафедри ПЗАС  
(прізвище, ім’я , по батькові, науковий ступінь, вчене звання) 
Затверджені наказом Черкаського державного технологічного університету від « 12 » березня 
2026 року №56/03-03 
2. Строк подання студентом проекту (роботи)   
3. Вхідні дані до проекту (роботи) стандарти програмного забезпечення; процеси управління; 
вимоги до проекту; календарне планування проекту; управління ризиками проекту; управління 
ресурсами  
4. Зміст розрахунково-пояснювальної записки (перелік питань, які потрібно розробити) Вступ, 
РОЗДІЛ 1. ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ ПОСТАВЛЕНИХ ЗАВДАНЬ, 
РОЗДІЛ 2. ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У ПРАКТИКУ 
ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ІНФОРМАЦІЙНИХ СИСТЕМ, РОЗДІЛ 
3. РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ, Висновки, Список 
використаних джерел, Додатки.  
5. Перелік графічного матеріалу (з точним зазначенням обов’язкових робіт проекту; 
Ескізи до інтерфейсу додатка, Логічна схема системи, Функціональна схема системи, Діаграми 
прецедентів, компонентів, класів, розгортання, послідовності, діяльності, пакетів, скінченого 
автомату, Графічні матеріали   
6. Консультанти розділів роботи 
Прізвище, ініціали та посади Підпис, дата 
Розділ 
консультанта Завдання видав Завдання прийняв 
1    
7. Дата видачі завдання 13 березня 2026 р.  
КАЛЕНДАРНИЙ ПЛАН 
Строк 
виконання 
№ 
Назва етапів випускної роботи етапів Примітки 
п/п кваліфікаційної
роботи 
1 Постановка задачі 25.02.2026 виконано 
2 Підготовка завдання 02.03.2026 виконано 
3 Погодження завдання 10.03.2026 виконано 
4 Затвердження завдання 12.03.2026 виконано 
 Основна стадія   
1 Підбір матеріалів 27.03.2026 виконано 
2 Аналіз шляхів вирішення поставленої задачі 01.04.2026 виконано 
3 Розрахунок основних параметрів роботи 03.04.2026 виконано 
4 Вибір кінцевого варіанту проектного рішення 22.04.2026 виконано 
5 Оформлення первісної редакції роботи 25.04.2026 виконано 
 Заключна стадія   
1 Узгодження прийнятих проектних рішень з 30.04.2026 виконано 
керівником 
2 Оформлення пояснювальної записки роботи в 13.05.2026 виконано 
кінцевій редакції 
3 Попередній захист роботи 19.05.2026 виконано 
4 Затвердження роботи 25.05.2026 виконано 
5 Рецензування роботи 29.05.2026 виконано 
6 Захист роботи 05.06.2026  
 
Студент _____________________ Перепьолкін О.О. 
  (підпис)   (прізвище та ініціали) 
Керівник роботи _____________________ Білоніг А.В. 
  (підпис)   (прізвище та ініціали) 
  
АНОТАЦІЯ 
Кваліфікаційна робота бакалавра на тему «Програмна реалізація алгоритму 
взаємодії гравців для моделювання оперативних зіткнень на морі» містить 143 
сторінки, 4 таблиць, 79 рисунків, список використаних джерел  з 10 найменувань, 
4 додатка. 
Метою виконання кваліфікаційної роботи бакалавра є проектування та 
розробка багатокористувацької версії програмного забезпечення «Морський бій». 
Головні завдання при проектуванні інформаційної системи  
1 Переробити back-end частину web-додатку під визначені технології; 
2 Реалізувати систему пошуку гравців та створення лобі; 
3 Стилізувати наявні сторінки web-додатку під різні пристрої; 
4 Реалізувати алгоритм битви Team Battle; 
5 Модифікувати систему підбору гравців із додаванням типу гри FFA; 
6 Реалізувати алгоритм битви FFA із урахуванням правил битви «Кожен 
сам за себе»; 
7 Розширити створений функціонал на всі режими гри; 
8 Опублікувати web-додаток на довільному хостингу. 
Об'єктом роботи є програмне забезпечення ігрової інформаційної 
технології. 
Предметом розробки є процес побудови програмного забезпечення 
інформаційної технології візуальної взаємодії користувачів при грі в «Морський 
бій». 
Ключові слова: WEB-Додаток, Морський бій, Мультиплеєр, Team Battle, 
FFA, Алгоритм взаємодії гравців, Spring, Postgresql, Моделювання морських 
зіткнень, Ігровий процес. 
 
 
ЗМІСТ 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ ............................................................................ 5 
ВСТУП .............................................................................................................................. 6 
РОЗДІЛ 1. ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ ПОСТАВЛЕНИХ 
ЗАВДАНЬ ........................................................................................................................  8 
 1.1. Аналіз аналогів ............................................................................................... 8 
 1.2. Проблеми, що потрібно вирішити .............................................................. 13 
1.3. Формування конкретних задач ................................................................... 13 
1.4. Технології та інструменти ........................................................................... 14 
Висновок до розділу 1 ........................................................................................ 16 
РОЗДІЛ 2. ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У ПРАКТИКУ 
ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ ІНФОРМАЦІЙНИХ 
СИСТЕМ ........................................................................................................................ 17 
2.1. Моделювання предметної області .............................................................. 17 
 2.1.1. Предметна область моделювання. Модель предметної області. 
Словник предметної області ........................................................................................ 17 
 2.1.2. Елементи моделювання предметної області ................................ 19 
 2.1.3. Робоча область моделювання  ....................................................... 20 
2.2. Формування та аналіз вимог  ...................................................................... 21 
 2.2.1. Формування вимог до програмного забезпечення. Первинні і 
детальні вимоги. Вимоги замовника і розробника. Функціональні та 
нефункціональні вимоги  .............................................................................................. 21 
 2.2.2. Формування вимог за допомогою діаграми прецедентів ........... 23 
2.3. Проектування логічної структури програмного комплексу .................... 24 
 2.3.1. Діаграми класів ............................................................................... 25 
 2.3.2. Діаграми пакетів ............................................................................. 34 
2.4. Архітектурне проектування ........................................................................ 36 
 
ЧДТУ.26 2254.015 ПЗ 
Змн. Арк . № докум. Підпис Дата 
ЧДТУ 252254-020 ПЗ 
 Розроб. Перепьолкін О.О. Програмна реалізація Літ. Лист Листів 
 Перевір.  Білоніг А.В.  4 143 
алгоритму взаємодії гравців 
  
для моделювання оперативних 
 Н. Контр. Півень О.Б. ФІТІС, кафедра ПЗАС, ПЗ-2204 
зіткнень на морі 
 Затверд. Голуб С.В. 
2.4.1. Діаграма компонентів .................................................................... 36 
2.4.2. Розгортання програмної системи на апаратних засобах. Діаграма 
розгортання .................................................................................................................... 37 
2.5. Моделювання поведінки системи  ............................................................. 37 
2.5.1. Діаграма діяльності ........................................................................ 38 
2.5.2. Діаграма послідовності .................................................................. 40 
2.5.3. Діаграма комунікації ...................................................................... 43 
2.5.4. Діаграма скінченного автомату ..................................................... 44 
Висновок до розділу 2 ........................................................................................ 47 
РОЗДІЛ 3. РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ . 48 
3.1. Розробка програмного комплексу .............................................................. 48 
3.1.1. Обґрунтування вибору засобів реалізації  ................................... 48 
3.1.2. Опис структурної (функціональної) схеми  ................................. 49 
3.1.3. Опис логічної схеми системи  ....................................................... 50 
3.1.4. Розробка бази даних  ...................................................................... 52 
3.1.5. Розробка інтерфейсу користувача  ............................................... 53 
3.1.6. Опис розробки програмних компонентів  .................................... 57 
3.2. Тестування системи  .................................................................................... 60 
3.2.1. Модульне тестування  .................................................................... 60 
3.2.2. Інтеграційне тестування  ................................................................ 62 
3.2.3. Системне тестування  ..................................................................... 62 
3.2.4. Приймальне тестування  ................................................................ 64 
3.3. Приклади впровадженого програмного комплексу ................................. 65 
Висновок до розділу 3 ........................................................................................ 70 
ВИСНОВКИ  .................................................................................................................. 71 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ ..................................................................... 72 
ДОДАТКИ ...................................................................................................................... 73 
ЧДТУ 262254.015 ПЗ 
Змн. Арк. № докум. Підпис Дата 
ЧДТУ 262254.015 ПЗ 
ПЕРЕЛІК УМОВНИХ ПОЗНАЧЕНЬ 
DTO Data Transfer Object, об’єкт передавання даних 
HTTP HyperText Transfer Protocol, протокол передавання гіпертексту 
HTTPS HyperText Transfer Protocol Secure, захищений протокол 
передавання гіпертексту 
IDE Integrated Development Environment, інтегроване середовище 
розробки 
OOP / ООП Object-Oriented Programming, об’єктно-орієнтоване 
програмування 
POJO Plain Old Java Object, простий Java-об’єкт без спеціальних 
обмежень фреймворку 
UML Unified Modeling Language, уніфікована мова моделювання 
UX User Experience, користувацький досвід 
ПЗ Програмне забезпечення 
СУБД Система управління базами даних 
5 
ЧДТУ 262254.015 ПЗ 
ВСТУП 
Актуальність теми: Тема цієї кваліфікаційної бакалаврської роботи, 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі», належить спеціальності 121 «Інженерія програмного 
забезпечення». Її актуальність виражена необхідністю реалізації програмного 
забезпечення масштабування ігрового процесу класичних ігор, таких як 
«Морський бій». Поточна тема спрямована на дослідження можливості 
масштабування цього алгоритму із додаванням умов для розвитку тактичних 
здібностей у гравців та заохочення їх до кооперації (командна битва) або змагання 
на виживання («кожен сам за себе»). Використано технологія Spring на мові 
програмування Java. 
Мета розробки: Метою цієї розробки є проектування та розробка 
багатокористувацької версії програмного забезпечення «Морський бій».  
Завдання розробки: 
1 Переробити back-end частину web-додатку під визначені технології; 
2 Реалізувати систему пошуку та створення (якщо не знайдено) ігор; 
3 Стилізувати наявні сторінки web-додатку під різні пристрої (мобільний 
та комп’ютерний повноекранний вигляди); 
4 Реалізувати алгоритм битви Team Battle; 
5 Модифікувати систему підбору гравців із додаванням типу гри FFA; 
6 Реалізувати алгоритм битви FFA із урахуванням правил битви «Кожен 
сам за себе»; 
7 Розширити створений функціонал на всі режими гри; 
8 Опублікувати web-додаток на довільному хостингу. 
Об’єкт розробки: програмне забезпечення ігрової інформаційної технології 
масштабування ігрового процесу класичних ігор. 
Предмет розробки: процес побудови програмного забезпечення 
інформаційної технології візуальної взаємодії користувачів при грі в «Морський 
бій». 
6 
ЧДТУ 262254.015 ПЗ 
Методи проектування та конструювання: Для реалізації теми КРБ були 
використані методи аналізу (для виділення ключових компонентів системи), 
комбінування (для об’єднання декількох типів та режимів гри) і фантазування 
(для створення правил гри у кожному середовищі).  
Опис отриманих результатів: у результаті виконання роботи було 
спроектовано та реалізовано програмну систему, архітектура якої на виході 
представлена трьома базовими підсистемами (модулями): модулем керування 
ігровим процесом (координація користувачів, розподіл по лобі та фіксація 
глобальних результатів), модулем ведення бою (імплементація покрокових 
алгоритмів, контроль черговості ходів та визначення переможця поєдинку), а 
також модулем автентифікації та передігрової логіки (авторизація, конфігурація 
розташування об'єктів на полі та підбір суперників). 
Практичне значення отриманих результатів: отримана система зможе 
бути використана як демонстрація можливостей масштабувати класичні ігри у 
контексті сучасних практик для покращення UX. 
Особистий внесок автора: автор цієї роботи носить відповідальність за 
процеси концепції, дизайну, реалізації, тестування та публікації виконаної 
кваліфікаційної роботи. Кожен процес життєвого циклу програмного 
забезпечення включає використання спеціалізованих інструментів (наприклад, 
draw.io для розробки макетів дизайну сторінок та бази даних) задля прискорення 
його розробки та контролю якості. 
7 
ЧДТУ 262254.015 ПЗ 
РОЗДІЛ 1. ІСНУЮЧІ МЕТОДИ ТА ЗАСОБИ РОЗВ’ЯЗАННЯ 
ПОСТАВЛЕНИХ ЗАВДАНЬ 
В цьому розділі розкриваються питання, які виникають під час виконання 
поточної бакалаврської роботи. 
1.1. Аналіз аналогів 
Перед початком роботи важливо ознайомитися із існуючими аналогами 
щодо моделювання морських зіткнень. Цей процес включає в себе визначення 
переліків, особливостей та недоліків ПЗ. 
На рис. 1.1 показаний мобільний застосунок Seabattle 2. 
Рисунок 1.1 - мобільний застосунок Seabattle 2 
8 
ЧДТУ 262254.015 ПЗ 
Гра Seabattle 2 є продовженням тієї ж самої гри Seabattle від однакового 
видавця. Основними відмінностями між ними – графічна складова та механіка 
розбудови цивілізації із внутрішньо-ігровою валютою (яка не використовується 
для придбання альтернативних засобів ураження у ігровий «Арсенал» гравця). 
Особливості мобільного застосунку Seabattle 2 від Byril: 
─ додає елементи внутрішньо-ігрової валюти; 
─ можна закуповувати технічні, альтернативні засоби ураження 
(наприклад авіацію) та використовувати їх на полі бою; 
─ підтримує формати гри (гравець проти гравця або гравець проти ШІ) із 
класичним (без додаткових засобів ураження) та просунутим (з додатковими 
засобами ураження) режимами, онлайн, Bluetooth або офлайн; 
─ впроваджена прогресія у вигляді будування цивілізації та рівні 
профілю, які відкривають доступ до будівель; 
─ кастомізація профілю включає зміну аватару, задання його кольору, 
зміна імені, моделей кораблів.  
Переваги мобільного застосунку Seabattle 2 від Byril: 
─ миттєва обробка ходів у битві; 
─ ходи мають обмеження по часу у 25 секунд; 
─ додаткові засоби ураження мають ліміт використання в залежності від 
озброєння: не можна уразити все поле противника цими засобами; 
─ реалізований функціонал збереження розстановки кораблів та 
автоматичної розстановки. 
Недоліки мобільного застосунку Seabattle 2 від Byril: 
─ агресивна реклама шкодить UX (звично для мобільних застосунків-
ігор); 
─ механіка розбудови цивілізації не має сенсу у контексті моделювання 
морських зіткнень (на мою думку); 
─ будівлі мають мало практичної користі. 
На рис. 1.2 показаний десктопний застосунок World of Warships. 
9 
ЧДТУ 262254.015 ПЗ 
Рисунок 1.2 - десктопний застосунок World of Warships 
Ця гра має більш динамічну концепцію, де кожен гравець керує одним 
кораблем, а не всім морським з’єднанням. Класичні правила гри «Морський бій» 
тут здебільшого замінені на більш активні та асинхронні алгоритми. Відповідно, 
коли відповідальність обмежується лише одним кораблем, можливості його 
модифікації розширюються. Наприклад: 
1 Змінювати бронювання; 
2 Модифікувати склад екіпажу; 
3 Міняти основні/встановлювати додаткові гармати. 
4 Міняти тип корабля (есмінці, авіаносці, підводні човни і т.д.); 
5 І багато іншого. 
Підтримує до 24 гравців в одному матчі (12х12). Типи ігор включають 
кооператив (операції, матчі «гравці проти ШІ») та змагання (спеціальні події, 
рейтингові та звичайні битви).  
Особливості десктопного застосунку World of Warships: 
─ підтримує до 24 гравців в одному матчі (12х12); 
─ типи ігор включають кооператив (операції, матчі «гравці проти ШІ») та 
змагання (спеціальні події, рейтингові та звичайні битви); 
─ можливість грати за різні типи кораблів; 
10 
ЧДТУ 262254.015 ПЗ 
─ моделі кораблів розділюються по країнам (СРСР, США, Японія, 
Франція і т.д.) і відрізняються у характеристиках. 
Переваги десктопного застосунку World of Warships: 
─ присутня якісна графіка із відображенням ушкоджень; 
─ геймплей залежить більше від планування ніж швидкості реагування 
гравця; 
─ типи кораблів змінюють можливості гравців; 
─ можливість використовуватися та налаштовувати автопілот. 
Недоліки десктопного застосунку World of Warships: 
─ велика частина прогресії закрита тільки (!) за приватно-отримуваною 
валютою або прямими переказами реальних коштів; 
─ деякий важливий функціонал закритий за прогресією (наприклад, 
модернізація кораблів або призначення командиру); 
─ деякі країни мають не всі типи кораблів в їх гілках розвитку (напр. 
Нідерланди мають тільки кораблі типу «Крейсер»); 
─ в матчах із реальними людьми багато гравців керуються ШІ 
(відслідковується у таблиці гравців). 
На рис. 1.3 показаний мобільний застосунок The Pirate: Caribbean Hunt. 
Рисунок 1.3 - мобільний застосунок The Pirate: Caribbean Hunt 
11 
ЧДТУ 262254.015 ПЗ 
Це гра від українського видавця Home Net Games збудована під гру на одну 
особу, але присутні режими гри PVP та PVE під декількох гравців, де перший – це 
командна битва, а другий – кооператив проти штучного інтелекту із більшою 
кількістю кораблів. В ній існує лише одна ігрова валюта, яку можна витрачати або 
отримувати багатьма способами, як-от наприклад: покращення корабля, найм 
працівників для служби у вашому флоті, ремонт корабля, покупка боєприпасів і 
т.д. 
Особливості десктопного застосунку World of Warships: 
─ 20 класів кораблів; 
─ необмежений розмір флоту; 
─ управління декількома кораблями під час бою; 
─ боріться з фортами важкими мортирами; 
─ 5 типів боєприпасів - гарматні ядра, подвійні ядра, бомби, кніпеля, 
картеч; 
─ особлива зброя: порохові бочки, палаюче масло, тарани, абордаж; 
─ 30 поліпшень кораблів; 
─ розвиток персонажа, отримання досвіду та підвищення рівня; 
─ 20 навичок - відкрийте нові ігрові особливості та можливості; 
─ реалістична модель плавання, відстані та час переміщення; 
─ сотні островів та десятки портів; 
─ зміна дня і ночі; 
─ управління декількома базами; 
─ необмежена кількість морських битв з (торговцями, контрабандистами, 
конвоями, військовими, галеонами зі скарбами, піратами і т.д.); 
─ сюжетна компанія у живому світі, наповненому людьми; 
─ репутація у 5 націй. 
Переваги десктопного застосунку World of Warships: 
─ досить хороша графіка для мобільного застосунку; 
─ реклама не з’являється занадто часто і не стоїть на заваді UX; 
12 
ЧДТУ 262254.015 ПЗ 
─ багато механік (відношення із націями, найм працівників, система 
покращення) дійсно діють на ігровий процес; 
─ прогрес між десктопною та мобільною версіями синхронізуються. 
Недоліки десктопного застосунку World of Warships: 
─ навігація по грі не є повністю інтуїтивно-зрозумілою: для 
пристосування до неї потрібен час; 
─ деякі функції (наприклад, видозміна корабля або створення клану) 
закрито за ігровими покупками.  
1.2. Проблеми, що потрібно вирішити 
Після виконання аналізу аналогів був сформований наступний список 
проблем: 
1 Побудова гри – збір визначеної кількості гравців у створену гру та 
синхронний її початок; 
2 Cинхронізація наслідків ходу – після виконання ходу повинен 
оновлюватися вигляд битви для всіх гравців, включених у неї; 
3 Коректна побудова битв – битви створюються згідно обраного режиму 
гри із одномірним розподіленням гравців та вони прив’язані до однієї гри; 
4 Слідкування за ходом гри – гравці, які були переможені, мають бути 
вилучені із гри, а гравці, які закінчили свої битви переможцями, перенаправленні 
в наступні битви (при наявності таких більше 1);  
5 Будування логіки згідно розроблених правил – програмне забезпечення 
має слідувати комплексу обмежень та вимог, які розробляються задля дотримання 
чесності ігрового ходу. 
1.3. Формування конкретних задач 
Для виконання цілей і вирішення проблем дипломної роботи потрібно 
реалізувати можливість грати в визначенні режими та типи гри. Виконувана 
робота може бути розділена на основні та другорядні задачі. 
Основні задачі 
13 
ЧДТУ 262254.015 ПЗ 
1 Переробити back-end частину web-додатку під визначені технології (див. 
розділ 1.3 Технології та інструменти); 
2 Реалізувати систему пошуку гравців та створення лобі для режиму гри 
2х2 типу Team Battle; 
3 Стилізувати наявні сторінки web-додатку під різні пристрої; 
4 Реалізувати алгоритм битви Team Battle із урахуванням правил 
командної битви; 
5 Модифікувати систему підбору гравців із додаванням типу гри FFA; 
6 Реалізувати алгоритм битви FFA із урахуванням правил битви «Кожен 
сам за себе»; 
7 Розширити створений функціонал на всі режими гри; 
8 Опублікувати web-додаток на довільному хостингу. 
Другорядні задачі 
1 Додати можливість змінювати користувацьке ім’я та пароль користувача; 
2 Реалізувати модальне вікно для відображення процесу пошуку гри. 
1.4. Технології та інструменти 
Для вирішення поставлених завдань було вирішено, що: фронтенд буде 
реалізований на JavaScript, а бекенд – перенесений із  Node.js в Java на 
фреймворку Spring. Для зберігання даних використовується СУБД PostgreSQL. 
Основною платформою для роботи сайту є Google Chrome, а розробка 
здійснюється у IntelliJ IDEA 2024.3.3. Стилізація буде виконана із використанням 
засобу SASS, а розмітка на jte. 
Мова розробки front-endу: JavaScript. 
Мова розробки back-endу: NodeJS -> Java. 
Використовувані фреймворки: Spring. 
Мова розмітки: jte. 
Засіб стилізкації: SASS. 
Платформа: Google Chrome. 
IDE: IntelliJ IDEA 2024.3.3. 
14 
ЧДТУ 262254.015 ПЗ 
СУБД: PostreSQL. 
Мій вибір цього стеку технологій можна обґрунтувати наступним чином: 
Spring підтримує роботу не тільки із налаштуванням та запуску сервера, але також 
надає вбудовані інструменти для створення тестів і файли надання властивостей 
(*.properties), через які явно вписуються необхідні значення (наприклад, 
підключення до бази даних) [1]. IntelliJ IDEA має багато розширень, всі створенні 
для сумісності роботи із мовою програмування Java (або похідної від неї Kotlin), а 
також більш зручну підтримку GitHub та включає в себе стартовий шаблон для 
Spring проекту [2]. PostreSQL є однією із найбільш відомою та використовуваною 
в сучасному часі СУБД [3]. Вона підтримує інтеграцію із Spring і 
використовується для створення бази даних в сервісі Railway (використовується 
при розміщенні у хостинг). Spring Test реалізує процес тестування у межах 
налаштованого середовища [4], а Spring Security назначає необхідні 
ідентифікатори до користувачів для спрощеної та точної обробки запитів [5]. 
Spring також надає інструменти Hibernate для зв’язування записів бази даних із 
сервером [6].  
15 
ЧДТУ 262254.015 ПЗ 
ВИСНОВОК ДО РОЗДІЛУ 1 
У підсумку, задача моделювання оперативних зіткнень на морі серед 
декількох гравців породжує множину проблем. Поточна бакалаврська робота 
показує їх вирішення і перешкодити виявленим у аналогах недолікам. Потік, у 
якому гравці будуть користуватися розробленим програмним забезпеченням, має 
бути прозорим, зручним та не містити непотрібних розгалужень (наприклад, 
будування цивилізації), які погіршують UX.  
Розроблений продукт може бути використаний як способи реалізації 
процесу морських зіткнень cеред багатьох гравців (до 8 включно) через web-сайт 
як ізольовано, так і групами. Кожен із них має на меті організувати та реалізувати 
це у відповідності із розповсюдженими практиками розробки мультиплеєрних 
алгоритмів.
16 
ЧДТУ 262254.015 ПЗ 
РОЗДІЛ 2. ВПРОВАДЖЕННЯ РЕЗУЛЬТАТІВ ДОСЛІДЖЕНЬ У 
ПРАКТИКУ ПРОЕКТУВАННЯ ПРОГРАМНОГО ЗАБЕЗПЕЧЕННЯ 
ІНФОРМАЦІЙНИХ СИСТЕМ 
В цьому розділі знаходяться матеріали, які вносять структурний, розкритий 
та логічний контекст конструюванної системи. 
2.1. Моделювання предметної області 
Виконана система буде давати гравцям обмежені можливості щодо її 
використання. Процес моделювання створить абстрактне відображення цієї 
взаємодії, а також включені в неї процеси.  
2.1.1. Предметна область моделювання. Модель предметної області. 
Словник предметної області 
Предметна область дозволяє дати ознаку інформації, яка система буде 
використовувати у всіх своїх операціях. Для моделювання представленої у вступі 
предмета розробки буде визначено її словник, елементи та робоча область.  На 
рисунку 2.1 зображена модель предметної області: 
Рисунок 2.1 – Модель предметної області 
На ній вказано 6 предметів, які розділені по масштабу та обов’язками. 
Таким чином: 
1 Ships відповідає за індивідуальний корабель, яким володіє поле гравця; 
2 Board зображує наявні кораблі та стан поля гравця із ушкодженнями та 
17 
ЧДТУ 262254.015 ПЗ 
зробленими ворожими гравцями ходами; 
3 Player означає гравця, який логічно належить команді та здійснює ходи у 
контексті певної битви; 
4 Team служить як сховище для групування гравців по командам. Без 
розстасованих гравців не може бути коректно створена битва; 
5 Battle служить сутністю для відстеження ходу гри в битвах. Також 
визначаються переможці та програвші у ній; 
6 Game відстежує хід гри поза битвами. Сенс в тому, що не всі гравці 
можуть вважатися переможцями, якщо вони перемогли лише одного опонента у 
грі із 4-ма гравцями. 
Таблиця 2.1 
Словник предметної області 
№ Назва Опис 
1 Гравець Ключовий і єдиний актор, який 
моделює дії користувачів в ігровому 
процесі 
2 Сесія Процес, який починається при 
присутності необхідної кількості 
гравців та закінчується коли 
залишається одна команда 
3 Поле гравця Простір, на якій розташовуються 
кораблі, та належить кожному гравцю 
індивідуально 
4 Битва Процес, в якому визначені гравці 
ізолюються в поєдинку і ті, що 
зазнали поразки, вилучаються із сесії 
18 
ЧДТУ 262254.015 ПЗ 
Продовження таблиці 2.1 
№ Назва Опис 
5 Гра Процес, в якому гравці із сесії 
розподіляються по командам, а 
самими командам призначаються 
битви у форматі 1 команда проти 1 
команди 
6 Команда Групова сутність, яка 
використовується для розмежування 
гравців візуально та логічно  
2.1.2. Елементи моделювання предметної області 
Наступна таблиця визначить смислове поняття графічних елементів, з який 
будується предметна область. 
Таблиця 2.2 
Елементи моделювання предметної області 
№ Графічне відображення Назва 
1 Актор 
2 Початок/початковий стан 
3 Клас із атрибутами і методами 
19 
ЧДТУ 262254.015 ПЗ 
Продовження таблиці 2.2 
№ Графічне відображення Назва 
4 Життєва лінія об’єкту взаємодії 
5 Компонент 
6 Коментар 
7 Кінець/кінцевий стан 
8 Дія актора 
9 Пакет 
2.1.3. Робоча область моделювання 
Простір, в якому буде функціонувати модель (та система), визначений 
потребами в рівномірному відображені елементів та вчасній і паралельний 
обробці декількох користувачів одночасно. Вони передбачають присутність,  
найперше, інтернет зв’язку, оскільки в іншому випадку для синхронізації та 
обробці дій користувачів доводиться створювати окрему локальну мережу, а це, в 
20 
ЧДТУ 262254.015 ПЗ 
першу чергу, сильно обмежує її масштаб використання. 
2.2. Формування та аналіз вимог 
Дуже важливим фактором на відповідність системи очікуванням є вимоги. 
Вони дозволяють не тільки кількісно, а й якісно наводити і чітко визначати що 
(функціональний аспект) та як (нефукціональний аспект) вона має виконувати у 
своїй предметній області.  
2.2.1. Формування вимог до програмного забезпечення. Первинні і детальні 
вимоги. Вимоги замовника і розробника. Функціональні та нефункціональні 
вимоги 
Формування вимог до програмного забезпечення 
Вимоги до розроблюваної системи мають бути лаконічними, коректно 
сформульованими, підтверджувані та охоплювати певний її аспект. Лаконічність 
оцінюється в зрозумілості вимоги всіма особами, які мають бути з ними 
ознайомлені. Коректність визначає відповідність та доцільність вимоги до 
поставлених задач розробки системи. Підтверджуваність означає можливість 
вимоги бути виконаною або не виконаною (“definition of done”). 
Первинні і детальні вимоги 
Розглянемо детально первинні вимоги: 
1 Система має супроводжувати дії користувача згідно її предметної 
області. 
2 Користувач має проходити процес авторизації для ідентифікації його в 
середовищі. 
3 Мова інтерфейсу системи – англійська. 
4 Система повинна бути доступна для використання із web-браузерів. 
5 Користувач має бути в змозі виконати наступне: авторизуватися, 
розставити кораблі, вибрати режими та типи гри, робити ходи, виходити із гри 
(передчасно, по бажанню). 
6 Поведінка системи має підпорядковуватися розробленим правилам гри. 
Розглянемо детально детальні вимоги: 
21 
ЧДТУ 262254.015 ПЗ 
1 Комунікація користувачів із сервером має здійснюватися HTTPS 
запитами. 
2 Дані мають зберігатися в базі даних і підвантажуватися коли користувач 
запрошує до них доступ. 
3 Користувачі мають реєструватися під унікальним ім’ям. 
4 Під час битви відслідковуються такі властивості гравця: кораблів 
знищено, кораблів залишилося, переможено гравців. 
5 Під час битви вихід із битви користувача має супроводжуватися його 
поразкою. 
6 Після початку пошуку створюється гра згідно обраного користувачем 
формату та туди записується набір гравців. 
7 Після знаходження потрібної кількості гравців вони розподіляються по 
своїм командам. 
8 Сервер має відслідковувати статус ходу гравців. Якщо гравець не 
виконує хід протягом відведеного часу, хід передається наступному гравцю: 
─ cервер також відслідковує поля гравців із кораблями; 
─ cервер також відслідковує хід ігор, в яких приймають участі гравці. 
9 Пошук/створення гри починається після того, як клієнт розставив всі 
кораблі. 
Вимоги замовника і розробника. Функціональні та нефункціональні 
вимоги  
1 Система має підтримувати до 20 користувачів одночасно 
(нефункціональна). 
2 Користувач має проходити процес авторизації по імені та паролю 
(функціональна). 
3 Користувач має бути частиною команди, навіть якщо вона буде 
складатися з одного члену (функціональна). 
4 Кожна битва має бути записана і відображена відповідно участі в ній 
користувача (нефункціональна + функціональна). 
5 Якщо гравець вийде зі сторінки розстановки кораблів до початку пошуку 
22 
ЧДТУ 262254.015 ПЗ 
гри, участь у грі не зараховується (функціональна). 
6 Після виконання гравцем ходу, сервер має видати оновлений вміст поля 
гравця до всіх учасників битви із урахуванням особливостей їх відображення 
(функціональна + нефункціональна). 
7 Система має бути стійка до подій ініціалізовані користувачем 
(перезавантаження сторінки) (нефукціональна). 
8 Користувач має змогу редагувати нікнейм та аватар профілю 
(функціональна). 
9 Система має оброблювати запити до 2 секунд (нефункціональна). 
10 Доступ користувачів до перегляду даних здійснюється мінімально 
необхідними ідентифікаторами (найменша залежність від front-endу) 
(нефункціональна). 
2.2.2. Формування вимог за допомогою діаграми прецедентів 
Діаграма прецедентів (Use Case) – це діаграма в UML, призначена для 
візуалізації взаємодії між акторами та системою, описуючи, як система забезпечує 
виконання певних функцій або послуг для задоволення потреб користувача. 
Основними елементи діаграми є актори, які взаємодіють із системою, випадки 
використання – дії або функції, які виконує система для акторів та зв'язки, які 
показують, як актори взаємодіють із випадками використання. Вертикально 
зверху униз показується очікуваний порядок взаємодії користувача із програмою. 
В діаграмі (див. Рисунок 2.2) показаний лише 1 актор у вигляді гравця, 
оскільки кожен користувач моїм проектом буде мати рівні можливості його 
використання. Спочатку він має авторизуватися перед отриманням доступу до 
наступних дій. Далі користувач вибирає режим та тип гри, розставляє кораблі та 
підтвердити готовність до бою. Після цього починається пошук суперників із 
такими ж уподобаннями. При успішному підборі гравців починається ігровий 
процес. 
23 
ЧДТУ 262254.015 ПЗ 
Рисунок 2.2 – Діаграма прецедентів 
2.3. Проектування логічної структури програмного комплексу 
Для відображення поведінки та архітектури створюваного проекту 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі», розроблений набір із 8 UML діаграм. Кожна із них буде 
відображати певний аспект та точку зору програми і буде доповнювати загальну 
картину її роботи. На них буде здійснена орієнтація при програмній реалізації 
проекту. 
 Порядок відображення UML діаграм буде здійснюватися у хронологічній 
послідовності їх створення, починаючи із статичних (наприклад «Діаграма 
класів»), і закінчуючи динамічними (наприклад «Діаграма кооперації») видами. 
Також будуть присутні опис та аргументації щодо їх доцільності. 
24 
ЧДТУ 262254.015 ПЗ 
Також для їх розробки буде використовуватися переважно програмне 
забезпечення draw.io. Серед декількох переваг цієї програми можна виділити 
зручність редагування параметрів фігур та можливість створення своїх елементів 
через об’єднання фігур. Проте серйозним недоліком є змішане подання 
структурних позначень UML діаграм, де один набір створений для декількох 
видів діаграм. 
Решта діаграм розроблені із використанням ПЗ Visual Paradigm. Воно 
нейтралізує недолік, виявлений в draw.io, проте при цьому має декілька інших. 
Наприклад, при нумерації певних послідовностей (опція включена автоматично) 
не збігається логіка нанесення значень, особливо коли декілька дій стосується 
однієї задачі. Також варто позначити менш зручний інтерфейс у порівнянні із 
draw.io.  
2.3.1. Діаграми класів 
Діаграма класів в UML використовується для моделювання структури 
системи, показуючи класи, їхні атрибути, методи та взаємозв'язки. Основними 
елементами діаграми є класи, які визначають об'єкти з атрибутами та зв'язки між 
класами, які відображають відносини між класами. 
Зафарбований ромб (     )означає композицію, а без заливки (    ) - агрегацію. 
Наступний рисунок (див. Рисунок 2.3) показує спрощену діаграму класів, яка 
відповідає моделі предметної області: 
Рисунок 2.3 – Спрощена діаграма класів 
25 
ЧДТУ 262254.015 ПЗ 
Рисунок 2.4 – Детальна діаграма класів 
На цій діаграмі (див. Рисунок 2.4) представлена детальна діаграма класів. 
Атрибути класів, навпроти яких стоїть значок «+», передбачено мати у публічному 
доступі, а із позначенням «-» - у приватному. Внизу розписано перелік всіх 
показаних класів зліва направо: 
1 Ship – представлення одиниці кораблів, які відображаються у полі 
гравця. 
1.1 Поля: 
− deckSize: int – визначає кількість палуб корабля;
26 
ЧДТУ 262254.015 ПЗ 
− id: int – означає ідентифікатор об’єкту;
− direction: SHIP_DIRECTIONS – задає напрямок корабля
(горизонтальний, вертикальний), використовуючи enum
SHIP_DIRECTIONS;
− coodsOfHead: Coords – визначає розташування «голови» корабля
по координатам (x, y);
− coordsOfHits: List <Coords> - зберігає дані по координатам
нанесених по кораблю ударів;
− coordsOfDecks: List<Coords> - зберігає понесені кораблем
ушкодження.
1.2 Методи: 
− <<constructor>> Ship(deckSize: int, direction: SHIP_DIRECTIONS,
coordsOfHead: Coords, shipId: int) – ініціалізує об’єкт із
аргументами перших 4 полів;
− <<constructor>> Ship(deckSize: int, direction: SHIP_DIRECTIONS,
coordsOfHead: Coords) - ініціалізує об’єкт із аргументами до
полях deckSize, direction та coordsOfHead;
− instantiateCoordsOfDecks() – при конструюванні корабля відразу
наносить місця, по яким можна його уразити;
− addHit (coords: Coords): void – додає значення у поле
coordsOfHits;
− <<get>> getCoordsOfHits (): List <Coords> - отримує значення
поля coordsOfHits;
− <<set>> setCoordsOfHits (List <Coords>): void- отримує значення
поля coordsOfHits;
− <<get>> getCoordsOfDecks (): List<Coords> - отримує значення
поля coordsOfDecks;
− <<set>> setDirection(direction: SHIP_DIRECTIONS): void – задає
значення поля direction. Додатково перевикликається метод
27 
ЧДТУ 262254.015 ПЗ 
instantiateCoordsOfDecks; 
− isDead (): bool – оцінює отриману кораблем шкоду і визначає чи є
він знищеним.
2 Board – представлення поля гравця із позначеннями зроблених 
суперником ходів. 
2.1 Поля: 
− ships: List <Ship> - зберігає об’єкти класу Ship;
− tiles: List<Tile> - зберігає позначення клітинок на полі гравця.
Клас Tile вміщує в собі координати клітинки поля 10х10 із
вмістом TILEMARKS, який може приймати значення
RESERVED, UNRESERVED, DESTROYED, MISS, SHIP.
2.2 Методи: 
− markTile(coords: Coords, mark: TILEMARKS, setupPhase: boolean):
void – змінює позначення однієї клітинки полю tiles;
− <<constructor>> Board(ships: List<Ship>) – ініціалізує об’єкт із
заданням поля ships;
− surroundDeck(coords: Coords, shipDirection: SHIP_DIRECTIONS,
last: boolean, first: boolean, tileMark: TILEMARKS, setupPhase:
boolean): void – ставить вибрану в параметрі TILEMARKS
навколо вказаної клітинки;
− putShip(ship: Ship, shipDirection: SHIP_DIRECTIONS): void –
ставить мітки в залежності від поля coordsOfDecks корабля і
навколишніх клітинках. Також додає об’єкту класу Ship в поле
ships;
− addShip(ship: Ship): void – викликає putShip після валідації
корабля;
− makeCoords(x: int, y: int): Coords – ініціалізує та повертає об’єкт
Coords із валідацією параметрів функції;
− getTile(int x, int y): Tile – отримує об’єкт Tile від поля tiles;
− deleteShip(ship: Ship, shipDirection: SHIP_DIRECTIONS): void –
28 
ЧДТУ 262254.015 ПЗ 
видаляє корабель із ships та оновлює вміст tiles; 
− validifyShip(ship: Ship): boolean – перевіряє чи всі клітинки
займані кораблем мають значення UNRESERVED;
− removeShip(ship: Ship): void – викликає deleteShip після валідації;
− getTileMark(x: int, y: int): TILEMARKS – повертає поле mark із
поля об’єктів типу Tile tiles;
− markDestroyedShip(ship: Ship, shipDirection: SHIP_DIRECTIONS):
void – позначає клітинки корабля DESTROYED, а суміжну
територію MISS;
− toString(): String – перевантажений метод із метою виведення
вмісту поля tiles у зрозумілій для розробника манері;
− handleHit(x: int, y: int): SHOT_RESULTS – виконує логіку ходу
гравця на поле опонента. Видає результат входу в залежності від
зачепленого вмісту клітинки.
3 Player – представлення гравця. Через цей клас користувач взаємодіє із 
програмою. 
3.1 Поля: 
− board: Board – зберігає в собі об’єкт поля гравця;
− name: String – вказує на ім’я уведене при авторизації гравця;
− avatarUrl: String – містить посилання на картинку, яка слугує
аватаром аккаунту гравця;
− info: PlayerInfo – інкапсулює в собі додаткові відомості про
гравця;
− id: int – зберігає ідентифікатор гравця отриманий із бази даних;
− isDefeated: Boolean – вказує на те, чи є гравець переможеним.
3.2 Методи: 
− <<constructor>> Player(name: String, avatarUrl: String, int: id) –
ініціалізує об’єкт із аргументами до полів name, avatarUrl і id;
− <<constructor>> Player(name: String, avatarUrl: String) – ініціалізує
29 
ЧДТУ 262254.015 ПЗ 
об’єкт із аргументами полів name та avatarUrl; 
− <<get>> getId(): int – повертає значення поля id;
− <<set>> setBoard(board: Board): void – задає значення поля board;
− <<get>> getBoard(): Board - повертає значення поля board;
− <<get>> isDefeated(): boolean - повертає значення поля isDefeated;
− <<get>> getTeam(): Team - повертає значення поля team;
− <<set>> setTeam(team: Team): void - задає значення поля team;
− <<get>> getInfo(): PlayerInfo - повертає значення поля info;
− placeShip(ship: Ship): void – викликає метод addShip поля board;
− quitGame(): void – задає isDefeated = true;
− deleteShip(ship: Ship): void – викликає метод removeShip поля
board;
− getShot(coords: Coords): SHOT_RESULTS – викликає метод
handleHit поля board.
4 PlayerInfo – представлення статистики про гравця. Винесено в окремий 
клас для дотримання зручності використання класу Player.  
4.1 Поля: 
− hitsMade: int – вказує скільки ходів виконав гравець;
− shipsKilled: int – вказує скільки ворожих кораблів знищив
гравець;
− playersDefeated: int – вказує на кількість переможених опонентів;
− shipsLeft: int: - вказує скільки кораблів лишилося у гравця.
4.2 Методи: 
− <<set>> setShipsLeft(shipsLeft: int): void – задає значення поля
shipsLeft;
− <<set>> setShipsKilled(shipsKilled: int): void - задає значення поля
shipsKilled;
− <<set>> setHitsMade(hitsMade: int): void - задає значення поля
hitsMade;
30 
ЧДТУ 262254.015 ПЗ 
− <<set>> setPlayersDefeated(playersDefeated: int): void - задає
значення поля playersDefeated;
− <<get>> getShipsLeft (): int – видає значення поля shipsLeft;
− <<get> getShipsKilled(): int - видає значення поля shipsKilled;
− <<get>> getPlayersDefeated(): int - видає значення поля
playersDefeated;
− <<get>> getHitsMade(): int - видає значення поля hitsMade.
5 Team – представлення команди гравця. Використовується для
позначення «своїх» та «чужих». 
5.1 Поля: 
− color: Color – задає колір команди;
− name: String – вказує на ім’я команди;
− size: int – встановлює максимальну кількість гравців;
− players: Collection<Player> - зберігає об’єкти типу Player.
5.2 Методи: 
− <<constructor>> Team(color: Color, name: String) – ініціалізує
об’єкт із заданням значення перших 2 полів;
− <<constructor>> Team(color: Color, name: String, players:
List<Player>) - ініціалізує об’єкт із заданням значення полів color,
name, players;
− <<set>> setSize(size: int): void - задає значення поля size;
− <<get>> getSize(): int - видає значення поля size;
− <<get>> getName(): String – видає значення поля name;
− <<get>> getColor(): Color - видає значення поля color;
− <<get>> getPlayers(): List<Player> - видає значення поля players;
− equals(o: Object): boolean – перевантажений метод, який порівнює
об’єкти по полю name;
− hashCode(): int - перевантажений метод, який видає хєш по полю
name;
31 
ЧДТУ 262254.015 ПЗ 
− addPlayer (Player): void – додає об’єкт до поля players;
− allPlayersDefeated(): Boolean – перевіряє поле isDefeated у всіх
об'єктах players.
6 Battle – шаблон битви із логікою відстеження черг гравців, включених у 
неї. 
6.1 Поля: 
− playerQueue: Queue<Player> - представляє чергу гравців;
− nextPlayer: Player – відслідковує наступного гравця;
− currentPlayer: Player – відслідковується поточний гравець;
− id: int - зберігає ідентифікатор битви отриманий із бази даних;
− lastMoveTimestamp: Instant – записується час початку ходу;
− teams: ArrayList<Team> - вже упорядковане сховище об’єктів
Team.
6.2 Методи: 
− <<constructor>> Battle (teams: ArrayList<Team>) – ініціалізує
об’єкт із полем teams;
− <<constructor>> Battle (teams: ArrayList<Team>, id: int) -
ініціалізує об’єкт із полем teams та id;
− <<constructor>> Battle(teams: ArrayList<Team>, id: int,
playerQueue: Queue<Player>) - ініціалізує об’єкт із полями teams,
id і playerQueue;
− <<get>> getId(): int – видає значення поля id;
− <<get>> getCurrentPlayer(): Player - видає значення поля
currentPlayer;
− <<get>> getNextPlayer(): Player - видає значення поля nextPlayer;
− <<get>> getTeams(): ArrayList<Team> - видає значення поля
teams;
− <<get>> getPlayerQueue(): Queue<Player> - видає значення поля
playerQueue;
32 
ЧДТУ 262254.015 ПЗ 
− chooseRandomPlayerFromTeam(Team): Player – визначає
випадково гравця з певної команди для розподілу черги; 
− nextTeam(currentTeam: Team): Team – від позиції параметра
визначає наступну команду;
− buildQueue(): void – створює чергу, за якою будуть мінятися поля
currentPlayer та nextPlayer;
− determineQueueSize(): int – визначає розмір черги, яку необхідно
заповнити;
− ableToMakeAMove(player: Player): boolean – визначає чи може
Player параметр виконати хід;
− handleMove(player: Player): void – процес передачі ходу до
nextPlayer.
7 Game – представлення процесу гри, де записується певна статистика та 
оголошується її початок та кінець. 
7.1 Поля: 
− teams: Collection <Team> - зберігає об’єкти типу Team;
− date: Instant– зберігає дані про дату початку гри;
− battles: ArrayList<Battle> - хоронить створені битви;
− notStaffedPlayers: ArrayList<Player> - відслідковує гравців, які не
назначені до битви;
− staffedPlayers: ArrayList<Player> - відслідковує гравців, які
назначені до битві;
− id: int – ідентифікатор гри;
− gameMode: GAME_MODES – зберігає дані про режим гри;
− gameType: GAME_TYPES – зберігає дані про тип гри;
− outcome: String – визначає підсумки завершеної гри.
7.2 Методи: 
− <<constructor>> Game(gameType: GAME_TYPES, gameMode:
GAME_MODES) – ініціалізує об’єкт із заданням перших 4 полів;
33 
ЧДТУ 262254.015 ПЗ 
− <<set>> setOutcome(outcome: String): void – задає значення поля
outcome;
− <<get>> getGameMode(): GAME_MODES - видає значення поля
gameMode;
− <<get>> getGameType(): GAME_TYPES - видає значення поля
gameType;
− startGame(): void – запускає алгоритм початку гри;
− endGame(): void – запускає алгоритм завершення гри;
− createFFABattles(): void – алгоритм створення битв до режиму
«Кожен сам за себе»;
− createTeamBattles(): void – алгоритм створення командних битв;
− determineTeamSize(): int – видає фіксоване значення в залежності
від типу та режиму гри;
− addPlayer(player: Player): void – додає гравця до notStaffedPlayers;
− removePlayer(player: Player): void – вилучає гравця зі
staffedPlayers та, якщо гравець був переможений, від
notStaffedPlayers;
− fillTeam(team: Team): void – розподілення гравців по командам;
− battleFinished(battle: Battle): void – алгоритм завершення битви,
визначення переможця і запис його до unstaffedPlayers, якщо гра
ще не завершена.
2.3.2. Діаграми пакетів 
На цій діаграмі (див. Рисунок 2.5) показані пакети, які використовуються 
сервером. Призначення кожного із них описано далі: 
1 enums – містить списки констант у контексті певних дій або контролю 
даних. Використовується переважно в пакеті classes, але може також бути в інших 
пакетах, тільки замість значення константи буде утилізуватися її ім’я; 
2 classes – містить ключову логіку системи. Саме тут здійснюються 
складні обчислення, перевіряються правила гри і багато іншого. Використовується 
34 
ЧДТУ 262254.015 ПЗ 
в пакеті converters коли потрібно повертати відповідь із сервера у простому 
вигляді’ 
3 converters – містить логіку перетворення даних між базою даних, 
ключових шаблонів та відповідями клієнту. Використовуються пакетом services; 
4 dto – містить шаблони відповідей, які сервер буде надсилати у форматі 
JSON; 
5 entities – містить представлення кортежів в базі даних; 
6 repositories – містить інтерфейси використання сутностей баз даних на 
основі entities; 
7 services – містить інтерфейси, які представляють взаємодію системи із її 
компонентами і підготовлює дані для відправки відповіді. В субпакеті impl 
інтерфейси набувають необхідної логіки; 
8 controllers – містить endpointи, які використовуються для зв’язку клієнту 
із сервером; 
9 config – містить класи налаштування, які використовують Spring Security 
в основному для автентифікації. 
Рисунок 2.5 – Діаграма пакетів 
35 
ЧДТУ 262254.015 ПЗ 
2.4. Архітектурне проектування 
В цій підсекції проектується складові системи у високо-рівневому полі зору. 
Тобто показуються ті елементи, які можуть містити в собі багато алгоритів, 
шаблонів, пакетів, тощо.  
2.4.1. Діаграма компонентів 
Діаграма компонентів в UML використовується для моделювання фізичної 
архітектури системи, показуючи, як програмні компоненти та модулі взаємодіють 
один з одним. Основними елементами діаграми є компоненти – окремі частини 
системи, що забезпечують конкретні функції та зв'язки між компонентами, які 
показують, як компоненти залежні один від одного або взаємодіють. 
Рисунок 2.6 – Діаграма компонентів 
На цій діаграмі (див. Рисунок 2.6) компоненти названі переважно за назвами 
таблиць баз даних, але вони не обов’язково позначають лише їх. Представлено 2 
підсистеми: Game (Гра) та Battle (Битва), які відповідають вказані у вступі 
результатам розробки. Вони також є компонентами, тому що3-я підсистема не 
36 
ЧДТУ 262254.015 ПЗ 
була окремо відокремлена тому що вона складається лише з 1 компоненту – Users, 
проте може бути в майбутньому масштабована, наприклад, для введення запису 
результатів ігор та битв, в яких брав участь гравець. 
2.4.2. Розгортання програмної системи на апаратних засобах. Діаграма 
розгортання 
Діаграма розгортання в UML моделює фізичне розташування компонентів 
системи на апаратних ресурсах, показуючи, як програмні елементи розгорнуті на 
серверах, комп'ютерах або інших пристроях. Основними елементи діаграми є 
вузли – апаратні або віртуальні ресурси, на яких розгорнуті компоненти, 
компоненти – програмні елементи, що розміщені на вузлах та зв'язки між 
вузлами, які показують мережеві або інші види з'єднань між апаратними 
ресурсами. 
Рисунок 2.7 – Діаграма розгортання 
Ця діаграма (див. Рисунок 2.7) показує фізичне розташування компонентів 
на відповідних пристроях. Виділено всього 3 таких пристроїв: Client, який 
взаємодіє із сервером через протокол HTTPS, Web server – взаємодіє із базою 
даних (Database Server) через інтерфейс  JPA, який реалізує Hibernate бібліотека. 
2.5. Моделювання поведінки системи 
37 
ЧДТУ 262254.015 ПЗ 
У поведінці системи виділяються хід виконуваних дій, або її станів, та 
переходи між ними із вказаними умовами. 
2.5.1. Діаграма діяльності 
Діаграма діяльності в UML відображає потік робіт або процесів у системі, 
показуючи послідовність виконання дій і подій. Вона підходить для моделювання 
бізнес-процесів, алгоритмів та операцій в системі. Основними елементами 
діаграми є дії – конкретні кроки або операції, які виконує система, потоки – 
стрілки, що вказують послідовність переходів між діями та маркери початку і 
кінця, які позначають початок і завершення процесу. 
Рисунок 2.8 – Діаграма діяльності 
38 
ЧДТУ 262254.015 ПЗ 
Ця діаграма (див. Рисунок 2.8) показує логічний хід системи в такій 
послідовності: 
1 Старт – початковий стан, що вказує на початок процесу запиту на 
обслуговування; 
2 Логін – подальші кроки мають відбуватися з авторизованим 
користувачем; 
3 Вибрати режим і тип гри – коли гравець вибирає режим і тип гри, він має 
розставити кораблі; 
4 Підтвердити розташування кораблів – після того, як всі кораблі 
правильно розстановлено, починається пошук вибраної раніше гри; 
5 Шукати  існуючу гру – користувач вибирає тип та режим гри, в яких він 
хоче брати участь і починається пошук; 
6 Приєднатися до гри – гра знайдена і виконується присвоєння знайденого 
ідентифікатора гри; 
7 Створити гру – гра не знайдена і гравець, який шукав гру, приєднується 
до новоствореної гри; 
8 Очікувати інших гравців – стан при якому всі приєднанні гравці мають 
очікувати поки збереться достатня кількість гравців; 
9 Почати гру – з умовою, що всі гравці зібралися можна почати процеси 
розподілу гравців і створення битв; 
10 Розподілити гравців по командам – гравцям рівномірно назначаються в 
команди; 
11 Розподілити команди по битвам – команди розподіляються по битвам; 
12 Визначити поточного гравця – цей процес входить то системи «Битва». 
Саме з нього він починається. Додатково під час цього реалізується таймер; 
13 Зробити хід – ця подія проводиться множиною умовний вираз. До уваги 
береться як і сама обробка ходу так і дотримання вимоги зробити хід поки таймер 
не скінчився; 
14 Продовжити гру -  відбувається, якщо у противника не закінчилися 
кораблі; 
39 
ЧДТУ 262254.015 ПЗ 
15 Передати хід – стається при невдало зробленому ході або скінченням 
часу таймера; 
16 Гравець виходить з гри – у випадку, коли гравець все ще має не знищенні 
кораблі, він може ініціювати вихід із битви та гри, анонсувавши свою поразку; 
17 Кінець – це кінцевий стан, що вказує на завершення процесу запиту на 
обслуговування. 
2.5.2. Діаграма послідовності 
Діаграма послідовності в UML використовується для моделювання порядку 
взаємодій між об'єктами у процесі виконання певної функції або сценарію. 
Основними елементами діаграми є об'єкти – учасники взаємодії, які надсилають і 
отримують повідомлення, повідомлення – взаємодії або виклики методів, що 
передаються між об'єктами, життєві лінії – вертикальні лінії, що показують 
існування об'єкта протягом сценарію та активності – відрізки на життєвих лініях, 
що вказують на виконання дії. 
В цій діаграмі (див. Рисунок 2.9, Рисунок 2.10, Рисунок 2.11, Рисунок 2.12) 
клієнт ініціює 4 дії: 
1 Place Ship - взаємодіє із компонентами PlayerService та ShipService із 
метою розмістити корабель у своєму полі. Реалізована обробка подій в залежності 
від вибраного розташування; 
2 Search game – взаємодіє із компонентами GameService, PlayerService та 
BattleService із метою знайти або створити гру; 
3 Make a move – взаємодіє із компонентами PlayerService, BattleService, 
ShipService. Реалізована логіка перевірки результатів зробленого ходу та перевірка 
на перемогу над супротивником; 
4 End battle - взаємодіє із компонентами GameService, BattleService, 
PlayerService. Відбувається коли гравець переміг у битві на очікує на оголошення 
його переможцем гри або коли йому підберуть наступного суперника/-ів; 
5 Quit the game – взаємодіє із компонентами GameService, BattleService, 
PlayerService. Вони обробляють подію виключенням гравця із раніше визначеної 
команди та оголошення його поразки. 
40 
ЧДТУ 262254.015 ПЗ 
Рисунок 2.9 – Діаграма послідовності. Частина 1 
Рисунок 2.10 - Діаграма послідовності. Частина 2 
41 
ЧДТУ 262254.015 ПЗ 
Рисунок 2.11 - Діаграма послідовності. Частина 3 
Рисунок 2.12 - Діаграма послідовності. Частина 4 
42 
ЧДТУ 262254.015 ПЗ 
2.5.3. Діаграма комунікації 
Діаграма комунікації в UML моделює взаємодію між об'єктами, акцентуючи 
увагу на структурі зв'язків між ними під час виконання певного сценарію. 
Основними елементами діаграми є актори – учасники взаємодії, які обмінюються 
повідомленнями, повідомлення – зв'язки між об'єктами, що показують порядок і 
напрямок обміну інформацією та зв'язки – лінії між об'єктами, що вказують на 
асоціації, через які передаються повідомлення. 
В наступній діаграмі (див. Рисунок 2.13) показана взаємодія між сервісами 
наступними викликами: 
1 Add Ship посилає запит на створення корабля із необхідними 
параметрами; 
2 Mark Ship позначає створений корабель на полі гравця; 
3 Create Game створює гру, якщо вона не була знайдена; 
4 Search Game починає пошук гри із бажаними параметрами; 
5 Find Game видає ідентифікатор гри, яка була знайдена; 
6 Set game id задає ідентифікатор гри до гравця; 
7 Add Player додає гравця до гри; 
8 Initialize Board ініціалізує Board гравця; 
9 Create Teams створює необхідну кількість команд; 
10 Assign Teams назначає гравців до створених команд; 
11 Create Battles створює битви на основі видимих властивостей гри; 
12 Assign Battles назначає гравців у створені битви; 
13 Get Attacked наносить ушкодження опоненту, який став ціллю поточного 
гравця; 
14 Add Hit додає координати клітинки до таблиці ушкоджень; 
15 Is Dead? перевіряє чи був корабель знищений; 
16 Mark Hit змінює стан поля опонента із нанесенням ушкодження; 
17 Mark Destroyed змінює стан поля опонента із нанесенням знищеного 
корабля; 
43 
ЧДТУ 262254.015 ПЗ 
18 Add players to waiting queue додає гравця переможця до черги, із якого 
він буде витягнутий для наступної битви; 
19 Remove Player вилучає гравця із гри та битви. 
Рисунок 2.13 – Діаграма комунікації 
2.5.4. Діаграма скінченного автомату 
Діаграма скінченного автомату в UML описує можливі стани об'єкта та 
переходи між ними на основі подій або умов. Вона використовується для 
моделювання поведінки об’єкта впродовж його життєвого циклу. Основними 
елементами діаграми є стани – різні етапи або умови, у яких може перебувати 
об'єкт, переходи – стрілки, що з'єднують стани та показують зміну стану при 
44 
ЧДТУ 262254.015 ПЗ 
певній події та початковий та кінцевий стани, які позначають початок і 
завершення життєвого циклу об'єкта. 
Рисунок 2.14 – Діаграма скінченого автомату 
45 
ЧДТУ 262254.015 ПЗ 
Наступний текст опише кожен стан, присутні на рисунку 2.14: 
1 Початок – це початковий стан. 
2 Авторизація – перехід в цей стан здійснюється при не виявленні 
необхідних даних. 
3 Авторизований – виконується при успішній авторизації. 
4 Розстановка кораблів – від користувача очікується дії щодо додаванням 
кораблів на своє поле. 
5 Пошук гри – стан завершення підбору гравців та їх реєстрації у гру. 
6 Очікування інших гравців – чекає поки всі не набереться необхідна 
кількість гравців. 
7 Розподіл по командам – випадково визначаються гравці які заповнюють 
команди. 
8 Створення і назначення битв – випадково назначаються команди у битви 
згідно обраного режиму. 
9 Активні битви – стан у якому підтримується хід активних битв. 
10 Виконання ходу – прийняття вводу від користувача та його обробка. 
11 Перевірка на існування кораблів – стан при якому програма перевіряє 
поля учасників на наявність не знищених кораблів. 
12 Пошук наступних суперників – завершення однієї битви та початок 
іншої при успішному знаходженні. 
13 Завершення гри – при не успішному знаходженні суперників гра 
завершиться. 
14  Кінець – заключний стан. 
46 
ЧДТУ 262254.015 ПЗ 
ВИСНОВОК ДО РОЗДІЛУ 2 
Отже, у етапах проектування програмного забезпечення були сформовані 
вимоги, створено предметну область та встановлено її поведінку та структуру. 
Його розробка здійснена безпосередньо згідно наявних у цьому розділі матеріалів. 
Статичні діаграми пояснюють структурну частину проекту. Діаграма класів 
розділяє програму на сутності та показує зв’язки між ними. Діаграма компонентів 
дає більш абстрактне уявлення структури, де компоненти можуть мати декілька 
класів або інших залежностей. Діаграма прецедентів визначає ролі користувачів 
проекту та очікувані від них дії. Діаграма розгортання позначає місце 
знаходження залежностей та компонентів програми. 
Динамічні діаграми пояснюють поведінку проекту. Діаграма кооперації 
представляє зв’язки між об’єктами та потік даних між ними. Діаграма діяльності 
визначає кроки, які програма має виконати, щоб дістатися із її початку до кінця. 
Діаграма послідовностей дає позначення життєвих проміжків об’єктів програми, 
коли вони є викликані діями акторів (користувачів). Діаграма скінченого автомату 
розділяє програму на послідовність станів, які вона може мати та порядок її зміни 
в інший стан. 
47 
ЧДТУ 262254.015 ПЗ 
РОЗДІЛ 3. РОЗРОБКА ТА ТЕСТУВАННЯ ПРОГРАМНОГО 
ЗАБЕЗПЕЧЕННЯ 
В цьому розділі розкривається процес імплементації спроектованої системи 
у справжнє програмне забезпечення. 
3.1. Розробка програмного комплексу 
Програмний комплекс будується на основі вибраних засобів реалізації із 
дотриманням принципів розробки, таких як YAGNI, DRY, SOLID та інші.   
3.1.1. Обґрунтування вибору засобів реалізації 
Для реалізації програмного забезпечення було обрану мову програмування 
Java, оскільки в ній реалізовані всі принципи ООП, всі складові компонентів 
можна легко розподілити по класам, і, головне, всі залежності можна помістити у 
інструмент компіляції систем Gradle [7]. Бібліотеки до неї знаходяться на тому ж 
самому ресурсі Maven Repositories, звідки беруться додаткові необхідності 
(наприклад, Lombok для додавання нотацій @Getter та @Setter замість генерації 
відповідних методів) [8]. Фреймворк Spring спеціально розроблений для 
налаштування серверів під Java: пропонований інструментарій дозволяє легко 
визначати точки доступу до серверу, серіалізацію POJO, налаштування 
необхідних компонентів системи. 
Системою управління базою даних було вибрано PostgreSQL, тому що під 
неї адаптована технологія Hibernate, яка використовується для автоматизованого 
підключення та взаємодії з базою даних. З її допомогою можна скоротити 
налаштування зв’язку та запуску запитів до неї використавши JPA бібліотеку. 
Рушієм створення та відображення сторінок вибрано мову розмітки jte. Він 
дозволяє напряму зчитувати відправленні Java об’єкти та генерувати сторінки в 
залежності від цих даних. Проте найважливішим плюсом цього рушію є 
використання Java умовних та циклічних виразів[9]. Інструмент стилізації SASS 
дозволяє користуватися синтаксисом класичного CSS3 із функціонал винесення 
48 
ЧДТУ 262254.015 ПЗ 
параметрів та їх значень [10]. JavaScript використане як засіб посилання запитів до 
серверу. 
Можливі альтернативи та їх недоліки: 
1 Thymeleaf. Традиційно йде до проектів Spring, проте цей засію не дуже 
добре може справлятися із зміною даних (потрібно кожен раз оновлювати 
сторінку); 
2 NodeJS/NestJS. Середовище для розробки серверу через JavaScript, проте 
пропадає типізація даних, що для даної системи може виявитися фатальним; 
3 TypeScript/React. Просте середовище для розробки реактивних web 
сторінок. Основним недоліком є публічність логіки на відміну від Thymeleaf, який 
її ховає на сервері; 
4 C#/ASP.NET. Синтаксис схожий із Java, але набір бібліотек у порівнянні 
невеликий. 
3.1.2. Опис структурної (функціональної) схеми  
На рисунку 3.1 показана функціональна розробленого web сайту: 
Рисунок 3.1 – Функціональна схема системи
49 
ЧДТУ 262254.015 ПЗ 
Всього вийшло 8 таблиць із 6 сторінками, які охоплюють увесь життєвий 
потік системи. Спочатку користувачу виводиться сторінка логіну, звідки він може 
авторизуватися або перейти до сторінки реєстрації, якщо у нього немає профілю. 
Користувач отримує свій ідентифікатор із однойменної таблиці і відбувається 
перехід до сторінки головного меню, там де користувач обирає бажаний тип та 
режим гри та переходить до сторінки підготовки до битви. В ній він ставить 
кораблі на своє поле та, по готовності, починається пошук гри. Якщо вона 
знайдена, користувач до неї приєднується або, в іншому випадку, вона 
створюється. Перехід на сторінку битви здійснюється лише тоді, коли гра 
починається зі всіма присутніми гравцями. Кінцева сторінка результатів 
з’являється після виграшу (гри) або програшу (битви) користувачем, після чого 
він повертається у головне меню. 
3.1.3. Опис логічної схеми системи 
На рисунку 3.2 показана логічна схеми розробленого web сайту. Вона 
ілюструє наступне: 
1 Процес пошуку гри має визначену тривалість часу у 5 секунд. Якщо гра 
не була знайдена, вона створюється; 
2 Процеси «Почати гру» та «Почати нові битви» є складними 
підпрограмами; 
3 Після виконання багатьох дій, база даних змінюється відповідно; 
4 Є блок початку процесу «Початок»; 
5 Блоки прийняття рішень (умовні переходи) (ромби, такі як «Користувач 
існує?», «Кількість гравців достатня?», «Було ушкоджено ворожий корабель?», 
«Ворожого гравця було ліквідовано?» та «Чи залишилися живі гравці у грі?»); 
6 Блоки збереження/зчитування даних (циліндри, наприклад: «Додати 
інформацію до бази даних», «Зберегти розстановку», «Записати ушкодження», 
«Вилучити ворожого гравця з гри» та «Видалити запис гри»); 
7 Система досягає кінця, коли гра завершується (блок «Кінець» у самому 
низу другої діаграми). 
50 
ЧДТУ 262254.015 ПЗ 
Рисунок 3.2 – Логічна схема розробленого web сайту
51 
ЧДТУ 262254.015 ПЗ 
3.1.4. Розробка бази даних  
На рисунку 3.3 показана схема бази даних по нотації IDEF1X: 
Рисунок 3.3 – Схема бази даних по нотації IDEF1X
52 
ЧДТУ 262254.015 ПЗ 
На ній видно 7 сильних (ShipHits, Ships, Teams, Users, Battles, Games, 
BattleShots) та 2 слабких (Players, BattlePlayers) сутності.  Таблиця Players служить 
сховищем даних про гравців, які стають учасниками гри, і отримує свій 
ідентифікатор (Id) із таблиці Users, в якій зберігаються дані про профілі 
користувачів. Таблиця Games має інформацію про активні ігри, а Battles – битви. 
Teams назначає гравців по командам. Проміжна таблиця BattlePlayers відслідковує 
гравців активних у битві. Нарешті, таблиця Ships містить параметри для 
ініціалізації поля гравця (функціонально) разом з ShipHits як спосіб зберігання 
даних про понесенні ушкодження. 
У разі закінчення ігор записи включених в неї сутностей стираються. 
Виходячи із цього, єдиною статичною таблицею можна вважати Users, оскільки 
вона не залежить від стану гри. 
3.1.5. Розробка інтерфейсу користувача 
Розробка шаблонів під розроблювальний web-сайт створенні спочатку на 
платформі розробки дизайнів draw.io. Наступні зображення відображають суто 
структурний вигляд web сторінок.  
Рисунок 3.4 – Ескіз сторінки підготовки до битви 
53 
ЧДТУ 262254.015 ПЗ 
Цей ескіз (див. Рисунок 3.4) показує приблизний вид сторінки підготовки до 
битви з наступними позначеннями: 
1 – Поле гравця  
2 – Поле вибраного корабля 
3 – Поле кораблів, які не нанесені в ігрове поле 
4 – Кнопки навігації 
Наступний ескіз (див. Рисунок 3.5) показує приблизний вид сторінки битви 
з наступними позначеннями: 
1 – Своє поле; 
2 – Поле, яке показує поточну чергу; 
3 – Поле ворога; 
4 – Поле таймера. 
Рисунок 3.5 – Ескіз сторінки битви 
54 
ЧДТУ 262254.015 ПЗ 
Рисунок 3.6 – Ескіз сторінки головного меню 
Цей ескіз (див. Рисунок 3.6) показує приблизний вид сторінки головного 
меню з наступними позначеннями: 
1 – Поле заголовку та додаткових налаштувань; 
2 – Поле вибору режимів гри; 
3 – Поле вибору типу гри. 
Рисунок 3.7 – Ескіз сторінки логіну 
55 
ЧДТУ 262254.015 ПЗ 
Цей ескіз (див. Рисунок 3.7) показує приблизний вид сторінки логіну. 
Рисунок 3.8 – Ескіз реєстрації користувача 
56 
ЧДТУ 262254.015 ПЗ 
Цей ескіз (див. Рисунок 3.8) показує приблизний вид сторінки реєстрації 
користувача. 
Рисунок 3.9 – Ескіз сторінки результату 
Цей ескіз (див. Рисунок 3.9) показує приблизний вид сторінки результату. 
3.1.6. Опис розробки програмних компонентів 
На рисунку 3.10 показані використовувані пакети backendу запущеного web 
сайту: 
57 
ЧДТУ 262254.015 ПЗ 
Рисунок 3.10 – Використовувані пакети запущеного web сайту
Призначення кожного пакету було пояснено при описі діаграми пакетів 
(див. 2.3.2. Діаграма пакетів). Класи, які в них містяться, описують частину 
компонента, яку програма має сприймати окремо в його межах. Наприклад: 
− classes сприймає компонент Player як об’єкт внутрішньої логіки, в
якому реалізовуються методи та поля позначенні в діаграмі класів; 
− controllers користується розробленим сервісом PlayerService для
отримання необхідних форм компоненту Player; 
− converters форматує компонент залежно від призначення: від
внутрішнього Player до PlayerEntity та навпаки, від Player до BattlePlayer; 
58 
ЧДТУ 262254.015 ПЗ 
− dto видає спрощений вигляд компоненту Player (PlayerDTO) без методів
та з визначеними полями, які мають загально відомі типи мови Java (int, String, 
boolean); 
− entities визначає відображення компонента Player у базі даних через
клас PlayerEntity, таким чином можно зберігати дані і, по потребі, ініціалізувати 
внутрішній клас Player через них задля використання прописаних методів. Такі дії 
забезпечує інтерфейс PlayerRepository;  
Решта пакетів не містять класів, які відносяться до складових компонентів, 
показаних на діаграмі компонентів.  Проте вони їх використовуються для 
налагоджування ігрового процесу із більш технічної сторони, такі як: контроль за 
сесіями, обробка HTTP запитів та визначення певних констант (використовуються 
в полях внутрішніх класів).  
На наступному зображенні показаний склад папки resources: 
Рисунок 3.11 - Склад папки resources
59 
ЧДТУ 262254.015 ПЗ 
Тут перелічені шаблони, стилі та іконки, які показуються на генерованих 
web сторінках. Використовуються засоби реалізації, вибір яких був пояснений 
раніше (див. 3.1.1. Обґрунтування вибору засобів реалізації). 
Файл application.properties має параметри налаштування середовища запуску 
сервера. Найбільш відома формою такого є використання .env файлу у проєктах, 
які використовують NodeJS. 
3.2. Тестування системи 
Тестування виконаної системи має досягати наступних критеріїв: 
Вимоги до тестування: 
1 Система має працювати стабільно при навантаженні 20 користувачів. 
2 Система не повинна мати проблем із роботою з базою даних об’ємом до 
50 рядків в одній таблиці та виконання операцій із 3-ма таблицями одночасно. 
3 Система має функціонувати на проміжку до 12 годин поспіль. 
4 Тестування бек-енду мусить включати обробку критичного функціоналу 
ПЗ. 
 Критичні фактори успішного тестування 
1 Загальне покриття коду автоматизованим тестуванням має зустрічати 
мінімальний показник в 70%. 
2 Критичні та високі дефекти мають бути вирішені перед запуском 
проекту. 
3 Всі основні вимоги програми були покриті на 100%. 
3.2.1. Модульне тестування 
Модульні тести були автоматизовані утилізуючи платформу JUnit. Вона має 
найпростіший та один із найбільш популярних інструментаріїв для створення 
тестів. Основні нотації: @Test, @ParametrisizedTest, @TestInstance, @BeforeAll, 
@AfterAll. Багато популярних фреймворків також побудовані на його основі. 
Тести покривають критичний функціонал класів, включених в пакет classes. 
Вони включають як негативні (коли програма має давати збій) так і позитивні 
(коли програма має працювати) сценарії. 
60 
ЧДТУ 262254.015 ПЗ 
Деякі алгоритми, які увійшли у тестування: 
1 Створення Ship на краях Board; 
2 Ship вважається знищеним, коли всі його палуби уражено; 
3 Ship не можуть ініціалізуватися, оскільки передані поза граничні 
параметри; 
4 На Board можна ставити та видаляти кораблі. При таких діях їх 
позначення (клітинок) також має змінюватися. Включає сценарій із крайніми 
розташуванням кораблів. 
5 Board не дає доданим кораблями займати місце вже розставлених; 
6 Виконання ходу на Board видає правильний результат. Включає випадки 
ходу на зайняту клітинку та некоректні координати; 
7 Здійснюється коректна передача ходу у процесі Battle; 
8 Player може видаляти та розставляти кораблі; 
9 Game при ініціалізації створює правильну кількість битв. 
На рисунку 3.12 показані результати їх запуску: 
Рисунок 3.12 – Результати запуску модульних тестів 
61 
ЧДТУ 262254.015 ПЗ 
У результаті вийшло 63 тести розподілені на 21 тест методів. Це покриває 
код на 27%. 
3.2.2. Інтеграційне тестування 
Цей тип тестів використовується для перевірки взаємодії бази даних із 
внутрішньою логікою серверу. В даному випадку це використовує пакет services.  
Для коректного запуску тестів було введено обмеження щодо паралельного 
запуску. Це було зроблено через те, що редагування однакових рядків призводить 
до збоїв. 
На рисунку 3.13 показані результати запуску модульних тестів: 
Рисунок 3.13 – Результати запуску модульних тестів 
У результаті вийшло 30 тестів. Це покриває код на 72% у сумі, що досягає 
поставленої мети у 70%. Такий великий відсоток пов’язаний із самою специфікою 
розміщення станів компонентів системи у СУБД замість серверної пам’яті. 
3.2.3. Системне тестування 
62 
ЧДТУ 262254.015 ПЗ 
Система була протестована шляхом ручного запуску проекту та виявлення 
недоліків у логіці або нефункціональних аспектів. Для успішної перевірки також 
включається можливість використання інтерфейсів для навігації у системі. 
Наступна таблиця показує частину системи під перевіркою і результат цієї 
перевірки (Passed, Failed): 
Таблиця 3.1 
Чекліст до системного тестування 
Id Назва Результат 
Passed Failed 
11 0 
1 Авторизація за існуючим акаунтом Passed 
успішна 
2 Реєстрація зі всіма увведеними полями Passed 
успішна 
3 Пошук гри запускається після фіксації Passed 
розстановки кораблів 
4 Корабель можна поставити на відведене Passed 
поле гравця на сторінці підготовки до 
битви 
5 Корабель можна видалити із поля гравця Passed 
на сторінці підготовки до битви 
6 Корабель можна перевернути у полі Passed 
гравця на сторінці підготовки до битви 
7 Виконання ходу на пусту клітинку Passed 
виконує зміну черги гравця 
8 Виконання ходу на зайняту клітинку Passed 
недоступно 
63 
ЧДТУ 262254.015 ПЗ 
Продовження таблиці 3.1 
Id Назва Результат 
9 Виконання ходу на клітинку із кораблем Passed 
не змінює чергу поточного гравця 
10 Поразка гравця у битві виконує перехід на Passed 
сторінку результатів 
11 Гравець може вийти із гри по бажанню Passed 
3.2.4. Приймальне тестування 
Після завершенням минулих рівнів, система була опублікована на хостингу 
заради перевірки функціоналу багатокористувацької підтримки ігрового процесу. 
Для виконання цього довелося задіяти керівника кваліфікаційної роботи. У 
результаті був сформований наступний чекліст: 
Таблиця 3.2 
Чекліст до приймального тестування 
Id Назва Результат 
Passed Failed 
5 0 
1 У битві FOUR_PLAYERS/FFA гравці Passed 
розподіляються по 4-ом командам 
2 Команда гравця змінює його відображення у Passed 
битві 
3 У TEAM_BATTLE гравці кожній команди Passed 
видно на сторінці 
4 Після завершення битви FFA (більше 2х Passed 
гравців), гравець переможець очікує 
завершення інших битв 
64 
ЧДТУ 262254.015 ПЗ 
Продовження таблиці 3.2 
Id Назва Результат 
5 Пошук гри відображає прогрес пошуку Passed 
гравців згідно їх приєднання до гри 
3.3. Приклади впровадженого програмного комплексу 
В цій частині розписані кроки програми та хід до їх виконання. Ілюстрації 
взяті із запущеної у відкритий доступ системи. 
Рисунок 3.14 – Сторінка логіна 
На сторінці логіну користувач вводить своє користувацьке ім’я, під яким він 
реєструвався, та пароль. Якщо вони вірні, здійснюється перехід до головного 
меню. В іншому випадку у тій же самій формі видає повідомлення про некоректні 
дані. Для нових користувачів рекомендується спочатку перейти до реєстрації, 
натиснувши на текст під кнопкою «Authorize». 
65 
ЧДТУ 262254.015 ПЗ 
Рисунок 3.15 – Сторінка реєстрації 
Тут користувач створює свій аккаунт. У полі «Confirm Password» потрібно 
повторити ту же саму послідовність символів, що й у «Password». Поле «Profile 
Picture» приймає формати зображень .svg, .png, .jpeg, .jpg, .gif, .bmp, .tiff та .webp. 
Після заповнення всіх полів потрібно натиснути на кнопку «Sign up». 
Рисунок 3.16 – Сторінка головного меню 
66 
ЧДТУ 262254.015 ПЗ 
У вікні головного меню користувач може перемикатися між режимами гри 
(кнопки перемикачі знизу) та вибрати тип гри (по кількості гравців, кнопки 
посеред сторінки). При виборі типу гри здійснюється перехід до сторінки 
підготовки до битви. Іконки справа зверху відповідають за наступне: перегляд 
історії битв (на даний момент не реалізовано), модифікувати аккаунт (можна 
змінити ім’я та пароль) та вийти із аккаунту назад до сторінки логіна. 
Рисунок 3.17 – Сторінка підготовки до битви 
На сторінці підготовки до битви користувачу необхідно поставити на своє 
поле кораблі із списку «Unplaced Ships». Це відбувається таким чином: 
користувач натискає на один із кораблів справа, а потім натискає на клітинку, із 
якої починається генерації вибраного корабля (справа наліво якщо орієнтація 
горизонтальна, або зверху униз якщо вона вертикальна). Блок «Selected Ship» 
існує як інструмент взаємодії із обома розставленими та нерозставленими 
кораблями. Кнопка «Rotate» змінює орієнтацію вибраного корабля (працює тільки 
із нерозставленим видом), а Delete – видаляє корабель із поля (працює тільки із 
розставленим видом), Clear All повністю очищує поле гравця із виводом кораблів 
у блок «Unplaced Ships». Після розстановки всіх кораблів, користувач повинен 
натиснути кнопку «Fight» справа знизу, щоб почати пошук гри (параметри якої 
67 
ЧДТУ 262254.015 ПЗ 
видно справа зверху у прямокутнику). Якщо користувач бажає повернутися назад 
до головного меню, потрібно натиснути «Quit». При цьому розташування 
кораблів зберігається у останньому редагованому стані. 
Рисунок 3.18 – Модальне вікно пошуку битви 
Процес пошуку містить перевірку на створену гру та створення гри у разі не 
виявлення такої. Якщо користувач бажає його припинити, достатньо натиснути на 
Cancel, щоб вийти у головне меню. 
Рисунок 3.19 – Сторінка битви 
Після набору необхідної кількості гравців гра починається і гравці 
68 
ЧДТУ 262254.015 ПЗ 
розподіляються по битвам. На сторінці битви лише активний гравець (якого 
можна вислідкувати по полю «Current Turn». Зазначений в ньому гравець може 
виконати хід на поле опонента, клацнувши по одні з його клітинок. Після обробки 
його ходу, згідно класичним правилам гри «Морський бій», черга може змінитися 
або, якщо було попадання по кораблю, залишитися у руках поточного 
користувача. Якщо він не зробить хід до кінця таймеру, черга переходить без 
нанесення будь-якої шкоди.  
При виконанні виходу із гри (кнопка «Quit» зліва знизу) гравець оголошує 
про свою поразку.  
Рисунок 3.20 – Сторінка результату 
У випадку перемоги або поразки виводиться статистика гравця, яку він 
зробив на протязі всієї гри. «Ships lost» означає кількість втрачених кораблів 
(своїх), «Ships destroyed» - кількість знищених кораблів (не своїх), «Moves made» - 
зроблені ходи (рахуються промахи також). Як тільки гравець ознаймомився із 
даними, він може повернутися до головного меню, настинувши кнопку «Main 
Menu». 
69 
ЧДТУ 262254.015 ПЗ 
ВИСНОВОК ДО РОЗДІЛУ 3 
У результаті програмної реалізації вдалося створити систему, яка реалізовує 
алгоритм гри «Морський бій» до 8 гравців із режимами гри Team Battle (командна 
битва) та FFA (кожен «сам-за-себе»). У першому гравців попарно змагаються між 
собою у ізольованих битвах, допоки не залишиться останній гравець, а у 
командній битві – всі включені в одну битву між 2-ма командами (синя та 
червона). 
Система розроблена відповідно функціональній та логічній схемам, що 
підтверджується кількістю наявних сторінок та видимій зміні станів об’єктів 
предметної області. Всі завдання, перелічені у розділі 1, були успішно реалізовані. 
Тестування системи покриває її критичний функціонал та обидві первинні і 
детальні (функціональні і нефункціональні) вимоги. Створенні автоматизовані 
тест-кейси можуть бути використані у регресійному тестуванні. 
Дані більшості компонентів зберігаються у базі даних PostgreSQL, тому 
можна сказати, що система працює на її основі. Це дає змогу усвідомити, що в 
залежності від стану цих даних (записів) поведінка системи буде змінюватися 
відповідно. 
70 
ЧДТУ 262254.015 ПЗ 
ВИСНОВКИ 
У результаті вдалося розробити систему, яка масштабує концепт та ігровий 
процес гри «Морський бій» до 8 гравців. Вона включає гру в 2 режимах, які 
набули популярності і використовуються у сучасних індустріях. Проект може 
бути відносно легко модифікований для додавання та реалізації нових ідей. 
Особливості системи «Програмна реалізація алгоритму взаємодії гравців 
для моделювання оперативних зіткнень на морі»: 
─ підтримка гри до 8 людей в одній сесії; 
─ режим гри Team Battle – в одній битві беруть участі всі члени команд 
(червона та синя); 
─ режим гри FFA – гравці попарно розподіляються по битвам. Коли одна 
із них завершується, гравець-переможець очікує наступного суперника із тих, хто 
також закінчив свої перші битви. Цикл продовжується, поки не залишиться 1 
активний гравець; 
─ піднятий на власному домені. 
Переваги системи «Програмна реалізація алгоритму взаємодії гравців для 
моделювання оперативних зіткнень на морі»: 
─ динамічний алгоритм пошуку гри; 
─ затримка відповіді від сервера не перевищує 2 с; 
─ ніяких реклам.  
Недоліки системи «Програмна реалізація алгоритму взаємодії гравців для 
моделювання оперативних зіткнень на морі»: 
─ відсутня ігрова прогресія (система рівнів, сюжет, альтернативні 
механіки нанесення ушкоджень); 
─ моделі кораблів не можна кастомізувати. 
Виконана робота демонструє можливість інтеграції класичних ігор із 
сучасними практиками розробки ігор. 
71 
ЧДТУ 262254.015 ПЗ 
СПИСОК ВИКОРИСТАНИХ ДЖЕРЕЛ 
1 Spring. Externalized Configuration :: Spring Boot. URL: 
https://docs.spring.io/spring-boot/reference/features/external-config.html; 
2 Spring | IntelliJ IDEA Документація. URL: 
https://www.jetbrains.com/help/idea/spring-support.html; 
3  PostgreSQL. PostgreSQL Global Development Group. About 
PostgreSQL. URL: https://www.postgresql.org/about/; 
4 Testing Spring Boot Applications. URL: https://docs.spring.io/spring-
boot/4.1/reference/testing/spring-boot-applications.html; 
5 Spring Security. Authentication :: Spring Security. URL: 
https://docs.spring.io/spring-security/reference/features/authentication/; 
6 Hibernate. Hibernate ORM. URL: https://hibernate.org/orm/; 
7 Gradle. Managing Dependencies of JVM Projects. URL: 
https://docs.gradle.org/current/userguide/dependency_management_for_java_proje
cts.html; 
8 Project Lombok. @Getter and @Setter. URL: 
https://projectlombok.org/features/GetterSetter; 
9 jte. jte: Java Template Engine. URL: https://jte.gg/; 
10 Sass. Structure of a Stylesheet. URL: https://sass-
lang.com/documentation/syntax/structure/. 
72 
ДОДАТОК А 
ЗАТВЕРДЖЕНО: 
Зав. кафедрою ПЗАС, професор 
_________________ Голуб С.В. 
„____” ______________ 2026 р. 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі» 
Cпецифікація 
ЧДТУ 262254 015
Листів 2 
Розробник ________________ Перепьолкін О.О. 
Керівник ________________ Білоніг А.В. 
2026 
ЧДТУ 262254 015 2 
Позначення Найменування Примітка 
Специфікація 
482.ЧДТУ 262254 12 01 Текст програми 
482.ЧДТУ 262254 34 01 Інструкція користувачеві 
482.ЧДТУ 262554 90 01 Графічні матеріали 
74 
ДОДАТОК Б 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі» 
Текст програми 
482.ЧДТУ 262254 12 01 
Листів 42 
Розробник ________________ Перепьолкін О.О. 
2026 
482.ЧДТУ 262254 12 01 2 
ЗМІСТ 
Файл Battle.java ................................................................................................................ 3 
Файл Board.java ............................................................................................................... 4 
Файл Coords.java .............................................................................................................. 7 
Файл Game.java ................................................................................................................ 7 
Файл Player.java ............................................................................................................. 10 
Файл PlayerInfo.java ...................................................................................................... 11 
Файл Ship.java ................................................................................................................ 11 
Файл Team.java .............................................................................................................. 12 
Файл Tile.java ................................................................................................................. 13 
Файл ShipTests.java ....................................................................................................... 13 
Файл AuthController.java ............................................................................................... 14 
Файл BattleController.java ............................................................................................. 16 
Файл MatchmakingController.java ................................................................................ 19 
Файл MenuController.java ............................................................................................. 20 
Файл PrepareController.java .......................................................................................... 22 
Файл ResultsController.java ........................................................................................... 24 
Файл BattleServiceImpl.java .......................................................................................... 25 
Файл CloudinaryServiceImpl.java .................................................................................. 32 
Файл GameServiceImpl.java .......................................................................................... 32 
Файл PlayerServiceImpl.java ......................................................................................... 37 
Файл ShipServiceImpl.java ............................................................................................ 38 
Файл UserServiceImpl.java ............................................................................................ 41 
76 
482.ЧДТУ 262254 12 01  3 
Файл Battle.java
package com.application.seabattle_28.classes; 
import lombok.Getter; 
import java.time.Instant; 
import java.time.temporal.ChronoUnit; 
import java.time.temporal.TemporalUnit; 
import java.util.*; 
public class Battle { 
    @Getter 
    private int id; 
    @Getter 
  private Player currentPlayer; 
    @Getter 
    private Player nextPlayer; 
    @Getter 
    private final ArrayList<Team> teams; 
    @Getter 
    private Queue<Player> playerQueue; 
    private Instant lastMoveTimestamp; 
    private int determineQueueSize() { 
        int maxPlayers = 0; 
        for(Team team : this.teams) { 
   maxPlayers += team.getSize(); 
        } 
        return maxPlayers; 
    } 
    private void buildQueue() { 
        int maxPlayers = determineQueueSize(); 
        Random random = new Random(); 
        Team currentTeam = teams.get(random.nextInt(teams.size())); 
        this.currentPlayer = choseRandomPlayerFromTeam(currentTeam); 
        currentTeam = nextTeam(currentTeam); 
        this.nextPlayer = choseRandomPlayerFromTeam(currentTeam); 
        this.playerQueue.add(this.currentPlayer); 
        this.playerQueue.add(this.nextPlayer); 
        while (this.playerQueue.size() < maxPlayers) { 
   currentTeam = nextTeam(currentTeam); 
   Player nextPlayer = choseRandomPlayerFromTeam(currentTeam); 
   if (!this.playerQueue.contains(nextPlayer)) this.playerQueue.add(nextPlayer); 
        } 
        this.lastMoveTimestamp = Instant.now(); 
    } 
    private Team nextTeam(Team currentTeam) { 
        return teams.indexOf(currentTeam) == teams.size() - 1 ? teams.getFirst() : teams.get(teams.indexOf(currentTeam) + 
1); 
    } 
    private Player choseRandomPlayerFromTeam(Team team) { 
        Random random = new Random(); 
        ArrayList<Player> players = (ArrayList<Player>) team.getPlayers(); 
        int playerTurn = random.nextInt(players.size()); 
        return players.get(playerTurn); 
    } 
    public Battle(ArrayList<Team> teams, int id) { 
77 
482.ЧДТУ 262254 12 01 4 
        this.id = id; 
        this.teams = teams; 
        this.playerQueue = new LinkedList<>(); 
        buildQueue(); 
    } 
    public Battle(ArrayList<Team> teams) { 
        this(teams, 0); 
    } 
    public Battle(ArrayList<Team> teams, int id, Queue<Player> playerQueue) { 
        this(teams, id); 
        this.playerQueue = playerQueue; 
    } 
    public boolean ableToMakeAMove(Player player) { 
        Player currentPlayer = this.playerQueue.element(); 
        boolean withinTime = Instant.now().isBefore(lastMoveTimestamp.plusSeconds(30)); 
        return currentPlayer == player && withinTime; 
    } 
    public void handleMove(Player player) { 
        if (ableToMakeAMove(player)) { 
   Player movedPlayer = this.playerQueue.poll(); 
   this.playerQueue.offer(movedPlayer); 
   this.currentPlayer = this.playerQueue.peek(); 
   this.nextPlayer = this.playerQueue.stream().toList().get(1); 
   this.lastMoveTimestamp = Instant.now(); 
        } 
    } 
}
Файл Board.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.SHIP_DIRECTIONS; 
import com.application.seabattle_28.enums.SHOT_RESULTS; 
import com.application.seabattle_28.enums.TILEMARKS; 
import lombok.Getter; 
import java.util.*; 
public class Board { 
    @Getter 
    private List<Ship> ships; 
    @Getter 
    private Tile[][] tiles; 
    private void surroundDeck(Coords coords, SHIP_DIRECTIONS shipDirection, boolean last, boolean first, 
TILEMARKS tileMark, boolean setupPhase) { 
        if(first) { 
   if(shipDirection == SHIP_DIRECTIONS.HORIZONTAL) { 
 for(int y = -1; y <= 1; y++) { 
     if(coords.y() + y < 0 || coords.y() + y >= 10 || coords.x() - 1 < 0 || coords.x() - 1 >= 10) continue; 
     markTile(makeCoords(coords.x() - 1, coords.y() + y), tileMark, setupPhase); 
 } 
   } 
   else { 
 for(int x = -1; x <= 1; x++) { 
     if(coords.x() + x < 0 || coords.x() + x >= 10 || coords.y() - 1 < 0 || coords.y() - 1 >= 10) continue; 
78 
482.ЧДТУ 262254 12 01 5 
     markTile(makeCoords(coords.x() + x, coords.y() - 1), tileMark, setupPhase); 
 } 
   } 
        } 
        if(last) { 
   if(shipDirection == SHIP_DIRECTIONS.HORIZONTAL) { 
 for(int y = -1; y <= 1; y++) { 
     if(coords.y() + y < 0 || coords.y() + y >= 10 || coords.x() + 1 < 0 || coords.x() + 1 >= 10) continue; 
     markTile(makeCoords(coords.x() + 1, coords.y() + y), tileMark, setupPhase); 
 } 
   } 
   else { 
 for(int x = -1; x <= 1; x++) { 
     if(coords.x() + x < 0 || coords.x() + x >= 10 || coords.y() + 1 < 0 || coords.y() + 1 >= 10) continue; 
     markTile(makeCoords(coords.x() + x, coords.y() + 1), tileMark, setupPhase); 
 } 
   } 
        } 
        if(shipDirection == SHIP_DIRECTIONS.HORIZONTAL) { 
   for(int y = -1; y <= 1; y += 2) { 
 if(coords.y() + y < 0 || coords.y() + y >= 10) continue; 
 markTile(makeCoords(coords.x(), coords.y() + y), tileMark, setupPhase); 
   } 
        } 
        else { 
   for(int x = -1; x <= 1; x += 2) { 
 if(coords.x() + x < 0 || coords.x() + x >= 10) continue; 
 markTile(makeCoords(coords.x() + x, coords.y()), tileMark, setupPhase); 
   } 
        } 
    } 
    private Coords makeCoords(int x, int y) { 
        if(x < 0 || x >= 10) throw new IllegalArgumentException("x must be more or equal to 0 or less than 10"); 
        if(y < 0 || y >= 10) throw new IllegalArgumentException("y must be more or equal to 0 or less than 10"); 
        return new Coords(x, y); 
    } 
    private void putShip(Ship ship, SHIP_DIRECTIONS shipDirection) { 
        List<Coords> coordsList = ship.getCoordsOfDecks(); 
        for (int a = 0;a < coordsList.size();a++) { 
   Coords coords = coordsList.get(a); 
   markTile(coords, TILEMARKS.SHIP, true); 
   surroundDeck(coords, shipDirection, a == coordsList.size() - 1, a == 0, TILEMARKS.RESERVED, true); 
        } 
        ships.add(ship); 
    } 
    private Tile getTile(Coords coords) { 
        return this.tiles[coords.x()][coords.y()]; 
    } 
    private void deleteShip(Ship ship, SHIP_DIRECTIONS shipDirection) { 
        List<Coords> coordsList = ship.getCoordsOfDecks(); 
        for (int a = 0;a < coordsList.size();a++) { 
   Coords coords = coordsList.get(a); 
   markTile(coords, TILEMARKS.UNRESERVED, true); 
 surroundDeck(coords, shipDirection, a == coordsList.size() - 1, a == 0, TILEMARKS.UNRESERVED, true); 
        } 
        this.ships.remove(ship); 
    } 
79 
482.ЧДТУ 262254 12 01 6 
    public void addShip(Ship ship) { 
        if(this.ships.contains(ship)) removeShip(ship); 
    if(!validifyShip(ship)) throw new IllegalArgumentException("Ship cannot be placed!"); 
        putShip(ship, ship.getDirection()); 
    } 
    public void removeShip(Ship ship) { 
        if(!this.ships.contains(ship)) throw new IllegalArgumentException("No ship found for deletion."); 
        deleteShip(ship, ship.getDirection()); 
    } 
    public TILEMARKS getTileMark(int x, int y) { 
        return this.tiles[x][y].mark; 
    } 
    private void markDestroyedShip(Ship ship, SHIP_DIRECTIONS shipDirection) { 
        List<Coords> coordsList = ship.getCoordsOfDecks(); 
        for (int a = 0;a < coordsList.size();a++) { 
   Coords coords = coordsList.get(a); 
   markTile(coords, TILEMARKS.DESTROYED, false); 
   surroundDeck(coords, shipDirection, a == coordsList.size() - 1, a == 0, TILEMARKS.MISS, false); 
        } 
    } 
    public SHOT_RESULTS handleHit(int x, int y) { 
        TILEMARKS tile = this.getTile(makeCoords(x, y)).mark; 
        if (tile == TILEMARKS.HIT || tile == TILEMARKS.DESTROYED || tile == TILEMARKS.MISS) { 
   return SHOT_RESULTS.ALREADY_HIT; 
        } 
        else if (tile == TILEMARKS.SHIP) { 
   Coords coords = makeCoords(x, y); 
   Ship ship = this.ships.stream() 
     .filter(e -> e.getCoordsOfDecks().contains(coords)) 
     .findFirst().orElseThrow(); 
   ship.addHit(coords); 
   if (ship.isDead()) { 
 markDestroyedShip(ship, ship.getDirection()); 
 return SHOT_RESULTS.DESTROYED; 
   } 
   markTile(coords, TILEMARKS.HIT, false); 
   return SHOT_RESULTS.HIT; 
        } 
        else return SHOT_RESULTS.MISS; 
    } 
    public boolean validifyShip(Ship ship) { 
        for (Coords deck : ship.getCoordsOfDecks()) { 
   for (int dx = -1; dx <= 1; dx++) { 
 for (int dy = -1; dy <= 1; dy++) { 
     int nx = deck.x() + dx; 
     int ny = deck.y() + dy; 
     if (nx < 0 || nx >= 10 || ny < 0 || ny >= 10) continue; 
     if (tiles[nx][ny].mark != TILEMARKS.UNRESERVED) { 
   return false; 
     } 
 } 
   } 
        } 
80 
482.ЧДТУ 262254 12 01 7 
   return true; 
    } 
    public void markTile(Coords coords, TILEMARKS mark, boolean setupPhase) { 
        Tile oldTile = tiles[coords.x()][coords.y()]; 
        if (setupPhase && oldTile.mark != TILEMARKS.UNRESERVED && mark != TILEMARKS.UNRESERVED) 
   throw new InputMismatchException("Only unreserved tiles may be marked." + this.toString()); 
        oldTile.mark = mark; 
    } 
    public Board(List<Ship> ships) { 
        this(); 
   for (Ship ship : ships) { 
   addShip(ship); 
        } 
    } 
    public Board() { 
        this.ships = new ArrayList<>(); 
        this.tiles = new Tile[10][10]; 
        for(int a = 0;a < 10;a++) { 
   for(int b = 0;b < 10;b++) { 
 Tile tile = new Tile(a, b, TILEMARKS.UNRESERVED); 
 tiles[a][b] = tile; 
   } 
        } 
    } 
    @Override 
    public String toString() { 
        StringBuilder boardView = new StringBuilder(); 
        for(Tile[] tilesRow: this.tiles) { 
   boardView.append("\n["); 
   for(Tile tile: tilesRow) { 
 boardView.append(tile.mark.getValue()).append(", "); 
   } 
   boardView.append("]"); 
        } 
        return boardView.toString(); 
    } 
} 
Файл Coords.java 
package com.application.seabattle_28.classes; 
public record Coords(int x, int y) { 
    public Coords { 
        if (x < 0 || x >= 10) 
   throw new IllegalArgumentException("x must be more or equal to 0 or less than 10", new Throwable()); 
        if (y < 0 || y >= 10) 
   throw new IllegalArgumentException("y must be more or equal to 0 or less than 10", new Throwable()); 
    } 
} 
Файл Game.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.GAME_MODES; 
import com.application.seabattle_28.enums.GAME_TYPES; 
81 
482.ЧДТУ 262254 12 01 8 
import com.application.seabattle_28.enums.TEAM_COLORS; 
import lombok.Getter; 
import lombok.Setter; 
import java.awt.*; 
import java.time.Instant; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Random; 
public class Game { 
    public ArrayList<Battle> battles; 
    public List<Player> notStaffedPlayers; 
    public List<Player> staffedPlayers; 
    @Getter 
    private GAME_MODES gameMode; 
    @Getter 
    private GAME_TYPES gameType; 
    @Setter 
    public String outcome; 
    public Game(GAME_MODES gameMode, GAME_TYPES gameType) { 
        this.battles = new ArrayList<>(); 
        this.staffedPlayers = new ArrayList<>(); 
        this.notStaffedPlayers = new ArrayList<>(); 
        this.gameMode = gameMode; 
        this.gameType = gameType; 
    } 
    public void startGame() throws Exception { 
        switch (gameMode) { 
   case FFA -> createFFABattles(); 
   case TEAM_BATTLE -> createTeamBattle(); 
   default -> throw new Exception("No Game Mode. Recreate the game, please!"); 
        } 
    } 
    public void battleFinished(Battle battle) { 
        this.battles.remove(battle); 
        ArrayList<Team> teams = battle.getTeams(); 
        ArrayList<Player> players = new ArrayList<>(); 
        for(Team team : teams) { 
   players.addAll(team.getPlayers()); 
        } 
        for(Player player: players) { 
   removePlayer(player); 
        } 
        if(this.notStaffedPlayers.size() % 2 == 0 && this.gameMode == GAME_MODES.FFA) { 
   createFFABattles(); 
        } 
    } 
    public void addPlayer(Player player) { 
        this.notStaffedPlayers.add(player); 
   } 
    public void removePlayer(Player player) { 
        this.staffedPlayers.remove(player); 
        if (!player.isDefeated) this.notStaffedPlayers.add(player); 
    } 
    public void endGame() { 
82 
482.ЧДТУ 262254 12 01 9 
        if(this.gameMode == GAME_MODES.FFA && this.notStaffedPlayers.size() == 1 && this.battles.isEmpty()) { 
   Player winner = this.notStaffedPlayers.getFirst(); 
   this.setOutcome("Player " + winner.name + " won!"); 
        } 
        else if (this.gameMode == GAME_MODES.TEAM_BATTLE && this.battles.isEmpty()) { 
   Battle battle = this.battles.getFirst(); 
   ArrayList<Team> teams = battle.getTeams(); 
   teams.removeIf(Team::allPlayersDefeated); 
   this.setOutcome("Team " + teams.getFirst() + " won!"); 
 } 
        else { 
   throw new RuntimeException("Game is not over yet!"); 
        } 
    } 
    private void createFFABattles() { 
        int playerCount = this.notStaffedPlayers.size(); 
        ArrayList<Team> teams = new ArrayList<>(); 
        for(int i = 0;i < playerCount;i++) { 
   Team team = new Team(TEAM_COLORS.values()[i].color, TEAM_COLORS.values()[i].name()); 
   try { 
       fillTeam(team); 
   } catch (Exception e) { 
 System.out.println(e); 
   } 
   teams.add(team); 
        } 
        for(int i = 0;i + 1 < teams.size();i += 2) { 
   ArrayList<Team> teamsForFFA = new ArrayList<>(); 
   teamsForFFA.add(teams.get(i)); 
   teamsForFFA.add(teams.get(i + 1)); 
   Battle battle = new Battle(teamsForFFA, 0); 
   this.battles.add(battle); 
        } 
    } 
    private void createTeamBattle() { 
        ArrayList<Team> teams = new ArrayList<>(); 
        Team teamBlue = new Team(Color.BLUE, "Blue"); 
        Team teamRed = new Team(Color.RED, "Red"); 
        try { 
   fillTeam(teamBlue); 
   fillTeam(teamRed); 
        } 
        catch(Exception e) { 
   System.out.println(e); 
        } 
        teams.add(teamBlue); 
        teams.add(teamRed); 
        this.battles.add(new Battle(teams, 0)); 
    } 
    private void fillTeam(Team team) throws Exception { 
        Random random = new Random(); 
        for(int i = 0;i < determineTeamSize();i++) { 
   int id = random.nextInt(this.notStaffedPlayers.size()); 
   Player player = this.notStaffedPlayers.get(id); 
   team.addPlayer(player); 
   this.staffedPlayers.add(player); 
   this.notStaffedPlayers.remove(player); 
        } 
        if(!this.notStaffedPlayers.isEmpty()) throw new Exception("Not all players were drafted."); 
    } 
83 
482.ЧДТУ 262254 12 01 10 
    private int determineTeamSize() { 
        if(this.gameMode == GAME_MODES.TEAM_BATTLE) { 
   switch (this.gameType) { 
 case FOUR_PLAYERS -> { 
 return 2; 
 } 
 case SIX_PLAYERS -> { 
     return 3; 
 } 
 case EIGHT_PLAYERS -> { 
     return 4; 
 } 
 default -> { 
 return 1; 
 } 
   } 
        } 
        else return 1; 
    } 
} 
Файл Player.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.SHOT_RESULTS; 
import lombok.Getter; 
import lombok.Setter; 
public class Player { 
    @Getter 
    private int id; 
    public String name; 
    @Setter 
    @Getter 
    private Board board; 
    @Getter 
    boolean isDefeated; 
    @Setter 
    @Getter 
    public Team team; 
    public String avatarUrl; 
    @Getter 
    public PlayerInfo info; 
    public void quitGame() { 
        this.isDefeated = true; 
    } 
    public void placeShip(Ship ship) { 
        this.board.addShip(ship); 
    } 
    public void deleteShip(Ship ship) {this.board.removeShip(ship);} 
    public SHOT_RESULTS getShot(Coords coords) { 
        SHOT_RESULTS shotResults = this.board.handleHit(coords.x(), coords.y()); 
        if (shotResults == SHOT_RESULTS.DESTROYED) this.info.shipsLeft--; 
        return shotResults; 
84 
482.ЧДТУ 262254 12 01 11 
    } 
    public Player (String name, String avatarUrl, int playerId) { 
        this.name = name; 
        this.id = playerId; 
        this.avatarUrl = avatarUrl; 
        board = new Board(); 
        this.info = new PlayerInfo(); 
    } 
    public Player(String name, String avatarUrl) { 
        this(name, avatarUrl, 0); 
    } 
} 
Файл PlayerInfo.java 
package com.application.seabattle_28.classes; 
import lombok.AllArgsConstructor; 
import lombok.Getter; 
import lombok.NoArgsConstructor; 
import lombok.Setter; 
@AllArgsConstructor 
@NoArgsConstructor 
@Getter 
@Setter 
public class PlayerInfo { 
    int hitsMade = 0; 
    int shipsKilled = 0; 
    int playersDefeated = 0; 
    int shipsLeft = 10; 
} 
Файл Ship.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.SHIP_DIRECTIONS; 
import lombok.Getter; 
import lombok.NonNull; 
import lombok.Setter; 
import java.util.ArrayList; 
import java.util.List; 
import java.util.Objects; 
public class Ship { 
    @Getter 
    private int id; 
    public int deckSize; 
    @Getter 
    private SHIP_DIRECTIONS direction; 
    public Coords coordsOfHead; 
    @Getter 
    @Setter 
   public List<Coords> coordsOfHits; 
    @Getter 
    public List<Coords> coordsOfDecks; 
    public Ship(int deckSize, @NonNull SHIP_DIRECTIONS direction, @NonNull Coords coordsOfHead, int shipId) { 
85 
482.ЧДТУ 262254 12 01 12 
        this.deckSize = deckSize; 
        this.id = shipId; 
        this.direction = direction; 
        this.coordsOfHead = coordsOfHead; 
        this.coordsOfHits = new ArrayList<>(); 
        this.coordsOfDecks = new ArrayList<>(); 
        instantiateCoordsOfDecks(); 
    } 
    public Ship(int deckSize, @NonNull SHIP_DIRECTIONS direction, @NonNull Coords coordsOfHead) { 
        this(deckSize, direction, coordsOfHead, 0); 
    } 
    private void instantiateCoordsOfDecks() { 
        int x = coordsOfHead.x(); 
        int y = coordsOfHead.y(); 
        for (int i = 0; i < this.deckSize; i++) { 
   this.coordsOfDecks.add(new Coords(x, y)); 
   if (Objects.requireNonNull(this.direction) == SHIP_DIRECTIONS.VERTICAL) { 
       y++; 
   } else { 
 x++; 
 } 
        } 
    } 
    public void setDirection(SHIP_DIRECTIONS direction) { 
        this.direction = direction; 
        this.coordsOfDecks.clear(); 
        instantiateCoordsOfDecks(); 
    } 
    public void addHit(Coords coords) { 
        if(!this.coordsOfHits.contains(coords)) this.coordsOfHits.add(coords); 
    } 
    public boolean isDead() { 
        return coordsOfHits.size() == this.coordsOfDecks.size(); 
    } 
} 
Файл Team.java 
package com.application.seabattle_28.classes; 
import lombok.Getter; 
import lombok.Setter; 
import java.awt.*; 
import java.util.ArrayList; 
import java.util.List; 
@Getter 
public class Team { 
    private final Color color; 
    @Setter 
    private int size; 
    private final String name; 
    private List<Player> players = new ArrayList<>(); 
    public Team(Color color, String name) { 
        this.color = color; 
86 
482.ЧДТУ 262254 12 01 13 
        this.name = name; 
    } 
    public Team(Color color, String name, List<Player> players) { 
        this(color, name); 
        this.size = players.size(); 
        this.players = players; 
    } 
    public void addPlayer(Player player) { 
        this.players.add(player); 
    } 
    public boolean allPlayersDefeated () { 
        return this.players.stream().allMatch(Player::isDefeated); 
    } 
    @Override 
    public boolean equals(Object o) { 
        if (this == o) return true; 
        if (o == null || getClass() != o.getClass()) return false; 
        Team team = (Team) o; 
        return java.util.Objects.equals(name, team.name); 
    } 
    @Override 
    public int hashCode() { 
        return java.util.Objects.hash(name); 
    } 
} 
Файл Tile.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.TILEMARKS; 
import lombok.AllArgsConstructor; 
import lombok.Getter; 
@AllArgsConstructor 
@Getter 
public class Tile { 
    int x; 
    int y; 
    TILEMARKS mark; 
} 
Файл ShipTests.java 
package com.application.seabattle_28.classes; 
import com.application.seabattle_28.enums.SHIP_DIRECTIONS; 
import org.junit.jupiter.api.Test; 
import org.junit.jupiter.params.ParameterizedTest; 
import org.junit.jupiter.params.provider.*; 
import java.util.stream.Stream; 
import org.junit.jupiter.api.Assertions; 
import static org.junit.jupiter.params.provider.Arguments.arguments; 
public class ShipTests { 
87 
482.ЧДТУ 262254 12 01 14 
    @Test 
    public void isDeadReturnsFalseIfOneDecksRemains() { 
        Ship ship = new Ship(4, SHIP_DIRECTIONS.HORIZONTAL, new Coords(2, 2)); 
        ship.addHit(new Coords(2, 2)); 
        ship.addHit(new Coords(3, 2)); 
        ship.addHit(new Coords(4, 2)); 
        Assertions.assertFalse(ship.isDead()); 
    } 
    @Test 
    public void turnsDeadWhenAllDecksHit() { 
        Ship ship = new Ship(1, SHIP_DIRECTIONS.HORIZONTAL, new Coords(2, 2)); 
        ship.addHit(new Coords(2,2)); 
        Assertions.assertTrue(ship.isDead()); 
    } 
    @ParameterizedTest 
    @MethodSource("getValidShipArguments") 
    public void validShipsInitialized(int deckSize, SHIP_DIRECTIONS shipDirection, int x, int y) { 
        Assertions.assertDoesNotThrow(() -> new Ship(deckSize, shipDirection, new Coords(x, y))); 
    } 
    @ParameterizedTest() 
    @MethodSource("getInvalidShipArguments") 
    public void invalidShipsThrowException(int deckSize, SHIP_DIRECTIONS shipDirection, int x, int y) { 
        Assertions.assertThrows(Exception.class, () -> new Ship(deckSize, shipDirection, new Coords(x, y))); 
    } 
    public static Stream<Arguments> getValidShipArguments() { 
        return Stream.of( 
 arguments(3, SHIP_DIRECTIONS.HORIZONTAL, 1, 1), 
 arguments(2, SHIP_DIRECTIONS.VERTICAL, 6, 8), 
 arguments(2, SHIP_DIRECTIONS.HORIZONTAL, 7, 2), 
 arguments(1, SHIP_DIRECTIONS.HORIZONTAL, 9, 9), 
 arguments(1, SHIP_DIRECTIONS.VERTICAL, 4, 1), 
 arguments(4, SHIP_DIRECTIONS.VERTICAL, 3, 5), 
 arguments(3, SHIP_DIRECTIONS.HORIZONTAL, 3, 3), 
 arguments(2, SHIP_DIRECTIONS.VERTICAL, 6, 0), 
 arguments(1, SHIP_DIRECTIONS.HORIZONTAL, 9, 0), 
 arguments(1, SHIP_DIRECTIONS.VERTICAL, 0, 9) 
        ); 
    } 
    public static Stream<Arguments> getInvalidShipArguments() { 
        return Stream.of( 
 arguments(11, SHIP_DIRECTIONS.VERTICAL, -1, -1), 
 arguments(1, null, 9, 9), 
 arguments(2, SHIP_DIRECTIONS.VERTICAL,10, 0), 
 arguments(4, SHIP_DIRECTIONS.HORIZONTAL, 0, -10), 
 arguments(2, SHIP_DIRECTIONS.HORIZONTAL, 9, 9) 
        ); 
    } 
} 
Файл AuthController.java 
package com.application.seabattle_28.controllers; 
import com.application.seabattle_28.dto.UserDTO; 
import com.application.seabattle_28.services.CloudinaryService; 
import com.application.seabattle_28.services.UserService; 
import jakarta.servlet.http.HttpServletRequest; 
88 
482.ЧДТУ 262254 12 01 15 
import lombok.RequiredArgsConstructor; 
import org.springframework.security.web.csrf.CsrfToken; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.*; 
import org.springframework.web.multipart.MultipartFile; 
@Controller 
@RequiredArgsConstructor 
public class AuthController { 
    private final UserService userService; 
    private final CloudinaryService cloudinaryService; 
    @GetMapping("/login") 
    public String login(@RequestParam(value = "error", required = false) String error, 
   HttpServletRequest request, 
   Model model) { 
        // Use the string key "_csrf" to ensure the standard filter's token is caught 
        CsrfToken token = (CsrfToken) request.getAttribute("_csrf"); 
        if (token != null) { 
   model.addAttribute("_csrf", token); 
        } 
        if (error != null) { 
   model.addAttribute("error", "Invalid username or password."); 
        } 
        return "login"; 
    } 
    @GetMapping("/register") 
    public String registerPage(HttpServletRequest request, Model model) { 
        CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 
        if (token != null) { 
   model.addAttribute("_csrf", token); 
        } 
        return "register"; 
    } 
    @PostMapping("/register") 
    public String handleRegistration(@ModelAttribute UserDTO userDTO, 
    @RequestParam("confirmPass") String confirmPass, 
     @RequestParam("profPic") MultipartFile file) { 
        // 1. Validate password match 
        if (!userDTO.getPassword().equals(confirmPass)) { 
   return "redirect:/register?error=pass_mismatch"; 
        } 
        try { 
   // 2. Call your CloudinaryService to perform the upload 
   // This ensures the image actually goes to the "diplom" folder 
   String avatarUrl = cloudinaryService.uploadFile(file); 
   // 3. Set the resulting URL into the DTO before saving 
   userDTO.setProfilePicture(avatarUrl); 
   // 4. Save the user (this triggers your UserServiceImpl logic) 
   userService.addUser(userDTO); 
   System.out.println("User registered successfully: " + userDTO.getName()); 
        } catch (Exception e) { 
   // Log the error so you can see it in the console if upload fails 
89 
482.ЧДТУ 262254 12 01 16 
e.printStackTrace();
return "redirect:/register?error=upload_failed";
        } 
  return "redirect:/login"; 
    } 
} 
Файл BattleController.java 
package com.application.seabattle_28.controllers; 
import com.application.seabattle_28.classes.Battle; 
import com.application.seabattle_28.classes.Coords; 
import com.application.seabattle_28.classes.Player; 
import com.application.seabattle_28.dto.UserDTO; 
import com.application.seabattle_28.enums.SHOT_RESULTS; 
import com.application.seabattle_28.services.BattleService; 
import com.application.seabattle_28.services.GameService; 
import com.application.seabattle_28.services.UserService; 
import jakarta.servlet.http.HttpSession; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.ResponseBody; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.PathVariable; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.RequestParam; 
import java.util.HashMap; 
import java.util.List; 
import java.util.Map; 
@Controller 
@RequiredArgsConstructor 
public class BattleController { 
    private final BattleService battleService; 
    private final UserService userService; 
    private final GameService gameService; 
    @GetMapping("/battle/{battleId}") 
    public String showBattle(@PathVariable int battleId, 
  @RequestParam(required = false) String result, 
  HttpSession session, 
  Model model) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        UserDTO user = userService.getUserById(userId); 
        int activeBattle = gameService.getBattle(userId); 
        int gameId = gameService.getGame(userId); 
        if (activeBattle < 0 && (gameId < 0 || gameService.hasFinishedGame(userId))) { 
   return "redirect:/results"; 
        } 
        if (activeBattle < 0 && gameService.isWaitingForNextBattle(userId)) { 
   return "redirect:/battle/waiting"; 
        } 
        if (activeBattle > 0 && activeBattle != battleId) { 
   return "redirect:/battle/" + activeBattle; 
        } 
90 
482.ЧДТУ 262254 12 01 17 
        Battle battle = battleService.getBattle(battleId); 
        Player me = battle.getTeams().stream() 
 .flatMap(t -> t.getPlayers().stream()) 
 .filter(p -> p.getId() == userId) 
 .findFirst().orElseThrow(); 
        List<Player> allPlayers = battle.getTeams().stream() 
 .flatMap(team -> team.getPlayers().stream()) 
 .toList(); 
        List<Player> myTeamPlayers = allPlayers.stream() 
 .filter(player -> sameTeam(me, player)) 
 .toList(); 
        List<Player> enemyTeamPlayers = allPlayers.stream() 
 .filter(player -> !sameTeam(me, player)) 
 .toList(); 
        Player opponent = enemyTeamPlayers.stream() 
 .findFirst().orElseThrow(); 
        Map<Integer, Map<String, String>> enemyShotMarksByPlayer = new HashMap<>(); 
        for (Player enemy : enemyTeamPlayers) { 
   if (me.getTeam() == null) { 
 enemyShotMarksByPlayer.put(enemy.getId(), battleService.getShotMarks(battleId, userId, enemy.getId())); 
   } else { 
       enemyShotMarksByPlayer.put(enemy.getId(), battleService.getTeamShotMarks(battleId, 
me.getTeam().getName(), enemy.getId())); 
   } 
        } 
        Map<Integer, Map<String, String>> incomingShotMarksByPlayer = new HashMap<>(); 
        for (Player teammate : myTeamPlayers) { 
   incomingShotMarksByPlayer.put(teammate.getId(), battleService.getIncomingShotMarks(battleId, 
teammate.getId())); 
        } 
        model.addAttribute("battle", battle); 
        model.addAttribute("user", user); 
        model.addAttribute("me", me); 
        model.addAttribute("opponent", opponent); 
        model.addAttribute("myTeamPlayers", myTeamPlayers); 
        model.addAttribute("enemyTeamPlayers", enemyTeamPlayers); 
        model.addAttribute("myId", userId); 
        model.addAttribute("currentPlayerName", battleService.getCurrentPlayerName(battleId)); 
        model.addAttribute("nextPlayerName", battleService.getNextPlayerName(battleId)); 
        model.addAttribute("currentPlayerId", battleService.getCurrentPlayerId(battleId)); 
        model.addAttribute("shotCount", battleService.getShotCount(battleId)); 
        model.addAttribute("enemyShotMarksByPlayer", enemyShotMarksByPlayer); 
        model.addAttribute("incomingShotMarksByPlayer", incomingShotMarksByPlayer); 
        model.addAttribute("shotResult", result == null ? "" : result); 
        model.addAttribute("secondsLeft", battleService.secondsLeft(battleId)); 
        return "battle"; 
    } 
    @PostMapping("/battle/{battleId}/shot") 
    public String makeShot(@PathVariable int battleId, 
 @RequestParam int defenderId, 
      @RequestParam int x, 
      @RequestParam int y, 
      HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
     if (userId == null) return "redirect:/login"; 
        SHOT_RESULTS result = battleService.hit(battleId, userId, defenderId, new Coords(x, y)); 
        if (battleService.isPlayerDefeated(userId)) { 
   return "redirect:/results"; 
        } 
     int nextBattle = gameService.getBattle(userId); 
91 
482.ЧДТУ 262254 12 01 18 
        if (nextBattle < 0) { 
   return gameService.isWaitingForNextBattle(userId) ? "redirect:/battle/waiting" : "redirect:/results"; 
        } 
        return "redirect:/battle/" + nextBattle + "?result=" + result.name(); 
    } 
    @PostMapping("/battle/{battleId}/timeout") 
    public String timeout(@PathVariable int battleId, HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        battleService.handleTimeout(battleId); 
        int nextBattle = gameService.getBattle(userId); 
        if (nextBattle < 0) { 
   return gameService.isWaitingForNextBattle(userId) ? "redirect:/battle/waiting" : "redirect:/results"; 
        } 
        return "redirect:/battle/" + nextBattle; 
    } 
    @PostMapping("/battle/quit") 
    public String quit(HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        battleService.quitBattle(userId); 
        return "redirect:/results"; 
    } 
    @GetMapping("/battle/{battleId}/state") 
    @ResponseBody 
    public String state(@PathVariable int battleId, HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null || battleService.isPlayerDefeated(userId)) { 
   return "results"; 
        } 
      battleService.handleTimeout(battleId); 
        int activeBattle = gameService.getBattle(userId); 
        if (activeBattle < 0) { 
   if (gameService.hasFinishedGame(userId)) { 
 return "results"; 
   } 
   return gameService.isWaitingForNextBattle(userId) ? "waiting" : "active"; 
        } 
        if (activeBattle != battleId) { 
   return "battle:" + activeBattle; 
        } 
        return "active:" + battleService.getCurrentPlayerId(battleId) + ":" + battleService.getShotCount(battleId) + ":" + 
battleService.secondsLeft(battleId); 
    } 
    private boolean sameTeam(Player first, Player second) { 
        if (first.getTeam() == null || second.getTeam() == null) { 
   return first.getId() == second.getId(); 
        } 
        return first.getTeam().getName().equals(second.getTeam().getName()); 
    } 
} 
Файл MatchmakingController.java 
package com.application.seabattle_28.controllers; 
92 
482.ЧДТУ 262254 12 01 19 
import com.application.seabattle_28.dto.GameDTO; 
import com.application.seabattle_28.entities.GameEntity; 
import com.application.seabattle_28.entities.PlayerEntity; 
import com.application.seabattle_28.enums.GAME_TYPES; 
import com.application.seabattle_28.repositories.PlayerRepository; 
import com.application.seabattle_28.services.GameService; 
import jakarta.servlet.http.HttpServletResponse; 
import jakarta.servlet.http.HttpSession; 
import lombok.RequiredArgsConstructor; 
import org.springframework.http.MediaType; 
import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.ResponseBody; 
@Controller 
@RequiredArgsConstructor 
public class MatchmakingController { 
    private final GameService gameService; 
    private final PlayerRepository playerRepository; 
    @PostMapping("/game/ready") 
    @ResponseBody 
    public String setReady(HttpSession session, HttpServletResponse response) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) { 
   response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 
   return "Login required"; 
        } 
        GameDTO pendingGame = (GameDTO) session.getAttribute("pendingGame"); 
        if (pendingGame == null) { 
   response.setStatus(HttpServletResponse.SC_BAD_REQUEST); 
   return "No game mode selected"; 
        } 
        if (!gameService.isFleetReady(userId)) { 
   response.setStatus(HttpServletResponse.SC_BAD_REQUEST); 
   return "Place the full fleet first"; 
        } 
        gameService.findGame(pendingGame, userId); 
        int gameId = gameService.getGame(userId); 
        if (gameId > 0 && gameService.joinedPlayers(gameId) >= 
GAME_TYPES.valueOf(pendingGame.getGameType()).value) { 
   gameService.createBattles(gameId); 
        } 
        return "ready"; 
    } 
    @GetMapping(value = "/game/status", produces = MediaType.TEXT_HTML_VALUE) 
    @ResponseBody 
    public String getStatus(HttpSession session, HttpServletResponse response) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) { 
   response.setHeader("HX-Redirect", "/login"); 
   return ""; 
        } 
        PlayerEntity player = playerRepository.findById(userId).orElseThrow(); 
        GameEntity gameEntity = player.getGameEntity(); 
        if (gameEntity == null) { 
   response.setHeader("HX-Redirect", "/"); 
   return ""; 
        } 
93 
482.ЧДТУ 262254 12 01 20 
        int joinedCount = gameService.joinedPlayers(gameEntity.getId()); 
        int totalNeeded = GAME_TYPES.valueOf(gameEntity.getGame_type()).value; 
        long secondsSinceReady = java.time.Duration.between(player.getReady_time(), java.time.Instant.now()).toSeconds(); 
        if (joinedCount < totalNeeded) { 
   if (secondsSinceReady < 5 && joinedCount <= 1) { 
 return searchingHtml(secondsSinceReady * 20); 
   } 
   return waitingHtml(joinedCount + "/" + totalNeeded); 
        } 
        gameService.createBattles(gameEntity.getId()); 
        int battleId = gameService.getBattle(userId); 
        response.setHeader("HX-Redirect", battleId > 0 ? "/battle/" + battleId : "/results"); 
        return ""; 
    } 
    private String searchingHtml(long progress) { 
        return """ 
 <div id="progress-container"> 
     <h4 class="mb-3">Searching for suitable opponents...</h4> 
     <div class="progress" style="height: 25px;"> 
   <div class="progress-bar progress-bar-striped progress-bar-animated bg-info" style="width: 
%d%%"></div> 
     </div> 
 </div> 
 """.formatted(Math.clamp(progress, 0, 100)); 
    } 
    private String waitingHtml(String count) { 
        return """ 
 <div id="progress-container"> 
 <h4 class="mb-3">Match Found!</h4> 
     <div class="progress mb-2" style="height: 25px;"> 
   <div class="progress-bar bg-success" style="width: 100%%">%s Ready</div> 
     </div> 
     <p class="text-muted small">Waiting for others to finish preparation...</p> 
 </div> 
 """.formatted(count); 
    } 
    @PostMapping("/game/cancel-ready") 
    public String cancelReady(HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId != null) { 
   gameService.removePlayerFromMatchmaking(userId); 
        } 
        return "redirect:/"; 
    } 
} 
Файл MenuController.java 
package com.application.seabattle_28.controllers; 
import com.application.seabattle_28.dto.UserDTO; 
import com.application.seabattle_28.dto.GameDTO; 
import com.application.seabattle_28.entities.UserEntity; 
import com.application.seabattle_28.repositories.PlayerRepository; 
import com.application.seabattle_28.repositories.UserRepository; 
94 
482.ЧДТУ 262254 12 01 21 
import com.application.seabattle_28.services.PlayerService; 
import com.application.seabattle_28.services.UserService; 
import jakarta.servlet.http.HttpServletRequest; 
import jakarta.servlet.http.HttpSession; 
import lombok.RequiredArgsConstructor; 
import org.springframework.security.core.Authentication; 
import org.springframework.security.core.context.SecurityContextHolder; 
import org.springframework.security.web.csrf.CsrfToken; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.RequestParam; 
@Controller 
@RequiredArgsConstructor 
public class MenuController { 
    private final UserRepository userRepository; 
    private final PlayerRepository playerRepository; 
    private final PlayerService playerService; 
    private final UserService userService; 
    // Inside MenuController 
    @GetMapping({"", "/"}) 
    public String showMainMenu(Model model, HttpSession session, HttpServletRequest request) { // Add request here 
        Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 
        assert auth != null; 
        String username = auth.getName(); 
        UserEntity user = userRepository.findUserEntityByName(username) 
       .orElseThrow(() -> new RuntimeException("User not found in session")); 
        if (!playerRepository.existsById(user.getId())) { 
   playerService.addPlayer(user.getId()); 
        } 
        // Explicitly pull the token and add it to the model 
        CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName()); 
        if (token != null) { 
   model.addAttribute("_csrf", token); 
        } 
        UserDTO userDTO = new UserDTO(user.getName(), user.getProfile_picture()); 
        session.setAttribute("userId", user.getId()); 
        model.addAttribute("user", userDTO); 
        return "main-menu"; 
    } 
    @PostMapping("/game/find") 
    public String chooseGame(@RequestParam String gameMode, 
  @RequestParam String gameType, 
  HttpSession session) { 
        session.setAttribute("pendingGame", new GameDTO(gameType, gameMode)); 
        return "redirect:/prepare"; 
    } 
    @PostMapping("/settings") 
    public String updateSettings(@RequestParam String name, 
      @RequestParam(required = false) String password, 
      HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
95 
482.ЧДТУ 262254 12 01 22 
        UserDTO userDTO = new UserDTO(); 
        userDTO.setName(name); 
        userDTO.setPassword(password); 
        userService.updateUser(userDTO, userId); 
        SecurityContextHolder.clearContext(); 
        session.invalidate(); 
        return "redirect:/login"; 
    } 
} 
Файл PrepareController.java 
package com.application.seabattle_28.controllers; 
import com.application.seabattle_28.dto.BoardContext; 
import com.application.seabattle_28.dto.GameDTO; 
import com.application.seabattle_28.dto.ShipDTO; 
import com.application.seabattle_28.dto.UserDTO; 
import com.application.seabattle_28.services.GameService; 
import com.application.seabattle_28.services.ShipService; 
import com.application.seabattle_28.services.UserService; 
import jakarta.servlet.http.HttpSession; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.ModelAttribute; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.RequestParam; 
import org.springframework.web.server.ResponseStatusException; 
import org.springframework.web.servlet.mvc.support.RedirectAttributes; 
@Controller 
@RequiredArgsConstructor 
public class PrepareController { 
    private final UserService userService; 
    private final ShipService shipService; 
    private final GameService gameService; 
    @GetMapping("/prepare") 
    public String showPreparePage(HttpSession session, Model model) { 
        // Use the ID from session as discussed 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        UserDTO user = userService.getUserById(userId); 
        BoardContext boardContext = shipService.getBoardContext(userId); 
        GameDTO pendingGame = (GameDTO) session.getAttribute("pendingGame"); 
        if (pendingGame == null) { 
   pendingGame = new GameDTO("TWO_PLAYERS", "FFA"); 
   session.setAttribute("pendingGame", pendingGame); 
        } 
        model.addAttribute("user", user); 
        model.addAttribute("board", boardContext); 
        ShipDTO[] ships = shipService.getShips(userId); 
        model.addAttribute("ships", ships); 
        model.addAttribute("remainingShips", remainingShips(ships)); 
        model.addAttribute("defaultDeckSize", defaultDeckSize(ships)); 
96 
482.ЧДТУ 262254 12 01 23 
        model.addAttribute("fleetReady", gameService.isFleetReady(userId)); 
        model.addAttribute("gameMode", pendingGame.getGameMode()); 
        model.addAttribute("gameType", pendingGame.getGameType()); 
        return "prepare"; 
    } 
    private int[] remainingShips(ShipDTO[] ships) { 
        int[] remaining = new int[] {0, 4, 3, 2, 1}; 
        for (ShipDTO ship : ships) { 
   remaining[ship.getDeckSize()]--; 
        } 
        return remaining; 
    } 
    private int defaultDeckSize(ShipDTO[] ships) { 
        int[] remaining = remainingShips(ships); 
        for (int size = 4; size >= 1; size--) { 
   if (remaining[size] > 0) { 
 return size; 
   } 
        } 
        return 1; 
    } 
    @PostMapping("/prepare/ships") 
    public String addShip(@ModelAttribute ShipDTO shipDTO, 
     HttpSession session, 
     RedirectAttributes redirectAttributes) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        try { 
   shipService.addShip(userId, shipDTO); 
        } catch (ResponseStatusException e) { 
   redirectAttributes.addFlashAttribute("placementError", "You can't do that ship: " + e.getReason()); 
        } catch (RuntimeException e) { 
   redirectAttributes.addFlashAttribute("placementError", "You can't do that ship: invalid placement."); 
        } 
        return "redirect:/prepare"; 
    } 
    @PostMapping("/prepare/ships/delete") 
    public String deleteShip(@RequestParam int shipId, 
  HttpSession session, 
        RedirectAttributes redirectAttributes) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        try { 
   shipService.deleteShip(userId, shipId); 
        } catch (ResponseStatusException e) { 
   redirectAttributes.addFlashAttribute("placementError", "You can't do that ship: " + e.getReason()); 
        } 
        return "redirect:/prepare"; 
    } 
    @PostMapping("/prepare/ships/rotate") 
    public String rotateShip(@RequestParam int shipId, 
  HttpSession session, 
        RedirectAttributes redirectAttributes) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
97 
482.ЧДТУ 262254 12 01 24 
        try { 
   shipService.rotateShip(userId, shipId); 
        } catch (ResponseStatusException e) { 
   redirectAttributes.addFlashAttribute("placementError", "You can't do that ship: " + e.getReason()); 
        } catch (RuntimeException e) { 
 redirectAttributes.addFlashAttribute("placementError", "Unable to replace ship from current position."); 
        } 
        return "redirect:/prepare"; 
    } 
    @PostMapping("/prepare/ships/clear") 
    public String clearShips(HttpSession session) { 
        Integer userId = (Integer) session.getAttribute("userId"); 
        if (userId == null) return "redirect:/login"; 
        shipService.clearShips(userId); 
        return "redirect:/prepare"; 
    } 
} 
Файл ResultsController.java 
package com.application.seabattle_28.controllers; 
import com.application.seabattle_28.entities.PlayerEntity; 
import com.application.seabattle_28.services.GameService; 
import com.application.seabattle_28.services.PlayerService; 
import jakarta.servlet.http.HttpSession; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Controller; 
import org.springframework.ui.Model; 
import org.springframework.web.bind.annotation.GetMapping; 
import org.springframework.web.bind.annotation.PostMapping; 
import org.springframework.web.bind.annotation.ResponseBody; 
@Controller 
@RequiredArgsConstructor 
public class ResultsController { 
    private final PlayerService playerService; 
    private final GameService gameService; 
    @GetMapping("/results") 
    public String showResults(HttpSession session, Model model) { 
        Integer playerId = (Integer) session.getAttribute("userId"); 
        if (playerId == null) return "redirect:/login"; 
        if (gameService.isWaitingForNextBattle(playerId)) { 
   return "redirect:/battle/waiting"; 
        } 
        PlayerEntity player = playerService.getPlayerById(playerId); 
        boolean isVictorious = player.getShips_left() > 0; 
        model.addAttribute("victory", isVictorious); 
        model.addAttribute("ships_lost", 10 - player.getShips_left()); 
        model.addAttribute("ships_destroyed", player.getShips_destroyed()); 
        model.addAttribute("players_killed", player.getPlayers_killed()); 
      model.addAttribute("moves_made", player.getMoves_made()); 
        return "results"; 
98 
482.ЧДТУ 262254 12 01 25 
    } 
    @GetMapping("/battle/waiting") 
    public String waitForNextBattle(HttpSession session, Model model) { 
        Integer playerId = (Integer) session.getAttribute("userId"); 
        if (playerId == null) return "redirect:/login"; 
        int activeBattle = gameService.getBattle(playerId); 
        if (activeBattle > 0) { 
   return "redirect:/battle/" + activeBattle; 
        } 
        if (!gameService.isWaitingForNextBattle(playerId)) { 
   return "redirect:/results"; 
        } 
        PlayerEntity player = playerService.getPlayerById(playerId); 
        int gameId = player.getGameEntity().getId(); 
        model.addAttribute("playerName", player.getName()); 
        model.addAttribute("ships_lost", 10 - player.getShips_left()); 
        model.addAttribute("ships_left", player.getShips_left()); 
        model.addAttribute("ships_destroyed", player.getShips_destroyed()); 
        model.addAttribute("players_killed", player.getPlayers_killed()); 
        model.addAttribute("moves_made", player.getMoves_made()); 
        model.addAttribute("alive_players", gameService.alivePlayers(gameId)); 
        return "waiting-round"; 
    } 
    @GetMapping("/battle/waiting/state") 
    @ResponseBody 
    public String waitingState(HttpSession session) { 
        Integer playerId = (Integer) session.getAttribute("userId"); 
        if (playerId == null) { 
   return "login"; 
        } 
        int activeBattle = gameService.getBattle(playerId); 
        if (activeBattle > 0) { 
   return "battle:" + activeBattle; 
        } 
        if (gameService.hasFinishedGame(playerId)) { 
   return "results"; 
        } 
        return gameService.isWaitingForNextBattle(playerId) ? "waiting" : "results"; 
    } 
    @PostMapping("/battle/waiting/quit") 
    public String quitWaiting(HttpSession session) { 
        Integer playerId = (Integer) session.getAttribute("userId"); 
       if (playerId == null) return "redirect:/login"; 
        gameService.leaveWaitingGame(playerId); 
        return "redirect:/results"; 
    } 
} 
Файл BattleServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.classes.*; 
import com.application.seabattle_28.converters.BattleConverter; 
import com.application.seabattle_28.entities.*; 
99 
482.ЧДТУ 262254 12 01 26 
import com.application.seabattle_28.enums.SHOT_RESULTS; 
import com.application.seabattle_28.repositories.*; 
import com.application.seabattle_28.services.BattleService; 
import com.application.seabattle_28.services.GameService; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Transactional; 
import java.time.Duration; 
import java.time.Instant; 
import java.util.ArrayList; 
import java.util.Comparator; 
import java.util.List; 
import java.util.Map; 
import java.util.stream.Collectors; 
@Service 
@RequiredArgsConstructor 
public class BattleServiceImpl implements BattleService { 
    private final BattleRepository battleRepository; 
    private final PlayerRepository playerRepository; 
    private final BattlePlayerRepository battlePlayerRepository; 
    private final BattleShotRepository battleShotRepository; 
    private final ShipHitsRepository shipHitsRepository; 
    private final GameService gameService; 
    @Override 
    @Transactional 
    public SHOT_RESULTS hit(int battleId, String playerName, Coords coords) { 
        PlayerEntity attacker = playerRepository.findByUser_Name(playerName); 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        PlayerEntity defender = battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle).stream() 
 .map(BattlePlayerEntity::getPlayer) 
 .filter(player -> player.getId() != attacker.getId()) 
 .filter(player -> player.getTeam() == null || attacker.getTeam() == null 
   || !player.getTeam().getName().equals(attacker.getTeam().getName())) 
 .filter(player -> player.getShips_left() > 0) 
 .findFirst() 
 .orElseThrow(); 
        return hit(battleId, attacker.getId(), defender.getId(), coords); 
    } 
    @Override 
    @Transactional 
    public SHOT_RESULTS hit(int battleId, int attackerId, int defenderId, Coords coords) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        PlayerEntity attacker = playerRepository.findById(attackerId).orElseThrow(); 
        PlayerEntity defender = playerRepository.findById(defenderId).orElseThrow(); 
        if (battle.getCurrentPlayer() == null || battle.getCurrentPlayer().getPlayer().getId() != attackerId) { 
   return SHOT_RESULTS.INVALID; 
        } 
        if (secondsLeft(battleId) <= 0) { 
   advanceTurn(battle, false); 
   return SHOT_RESULTS.INVALID; 
        } 
        if (sameTeam(attacker, defender) || battleShotRepository.existsByBattleAndDefenderAndXAndY(battle, defender, 
coords.x(), coords.y())) { 
   return SHOT_RESULTS.INVALID; 
        } 
        SHOT_RESULTS result = applyShot(defender, coords); 
        attacker.setMoves_made(attacker.getMoves_made() + 1); 
100 
482.ЧДТУ 262254 12 01 27 
        if (result == SHOT_RESULTS.DESTROYED) { 
   attacker.setShips_destroyed(attacker.getShips_destroyed() + 1); 
        } 
        BattleShotEntity shot = new BattleShotEntity(); 
        shot.setBattle(battle); 
        shot.setAttacker(attacker); 
        shot.setDefender(defender); 
        shot.setX(coords.x()); 
        shot.setY(coords.y()); 
        shot.setResult(result.name()); 
        battleShotRepository.save(shot); 
        if (result == SHOT_RESULTS.DESTROYED) { 
   markDestroyedShipSurroundingMisses(battle, attacker, defender, coords); 
        } 
        if (defender.getShips_left() <= 0) { 
   attacker.setPlayers_killed(attacker.getPlayers_killed() + 1); 
   deactivatePlayerInBattle(battle, defender); 
   if (battleHasWinner(battle)) { 
 gameService.finishBattle(battle.getGameEntity().getId(), battleId); 
   } 
        } else if (result == SHOT_RESULTS.MISS) { 
   advanceTurn(battle, false); 
        } else { 
   keepTurn(battle); 
        } 
        playerRepository.save(attacker); 
        playerRepository.save(defender); 
        return result; 
    } 
    @Override 
    @Transactional 
    public void handleTimeout(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        if (secondsLeft(battleId) <= 0) { 
   advanceTurn(battle, false); 
        } 
    } 
    @Override 
    public Battle getBattle(int battleId) { 
        return BattleConverter.convertBattleEntityIntoBattle(battleRepository.findById(battleId).orElseThrow(), battleId); 
    } 
    @Override 
    public Map<String, String> getShotMarks(int battleId, int attackerId, int defenderId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        PlayerEntity attacker = playerRepository.findById(attackerId).orElseThrow(); 
        PlayerEntity defender = playerRepository.findById(defenderId).orElseThrow(); 
        return battleShotRepository.findByBattleAndAttackerAndDefender(battle, attacker, defender) 
 .stream() 
 .collect(Collectors.toMap( 
   shot -> shot.getX() + "-" + shot.getY(), 
   BattleShotEntity::getResult, 
   this::mergeShotResult 
 )); 
    } 
    @Override 
101 
482.ЧДТУ 262254 12 01 28 
    public Map<String, String> getTeamShotMarks(int battleId, String attackerTeamName, int defenderId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        PlayerEntity defender = playerRepository.findById(defenderId).orElseThrow(); 
        return battleShotRepository.findByBattleAndDefender(battle, defender) 
 .stream() 
 .filter(shot -> shot.getAttacker().getTeam() != null) 
 .filter(shot -> shot.getAttacker().getTeam().getName().equals(attackerTeamName)) 
 .collect(Collectors.toMap( 
   shot -> shot.getX() + "-" + shot.getY(), 
   BattleShotEntity::getResult, 
   this::mergeShotResult 
 )); 
    } 
    @Override 
    public Map<String, String> getIncomingShotMarks(int battleId, int defenderId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        PlayerEntity defender = playerRepository.findById(defenderId).orElseThrow(); 
        return battleShotRepository.findByBattleAndDefender(battle, defender) 
 .stream() 
 .collect(Collectors.toMap( 
 shot -> shot.getX() + "-" + shot.getY(), 
   BattleShotEntity::getResult, 
   this::mergeShotResult 
 )); 
    } 
    @Override 
    public String getCurrentPlayerName(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        return battle.getCurrentPlayer() == null ? "" : battle.getCurrentPlayer().getPlayer().getName(); 
    } 
    @Override 
    public String getNextPlayerName(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        return battle.getNextPlayer() == null ? "" : battle.getNextPlayer().getPlayer().getName(); 
    } 
    @Override 
    public int getCurrentPlayerId(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        return battle.getCurrentPlayer() == null ? -1 : battle.getCurrentPlayer().getPlayer().getId(); 
    } 
    @Override 
    public long getShotCount(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        return battleShotRepository.countByBattle(battle); 
    } 
    @Override 
    public int secondsLeft(int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        Instant lastMoveTime = battle.getLastMoveTime() == null ? Instant.now() : battle.getLastMoveTime(); 
        long elapsed = Duration.between(lastMoveTime, Instant.now()).toSeconds(); 
        return (int) Math.max(0, 30 - elapsed); 
    } 
    @Override 
    public boolean isPlayerDefeated(int playerId) { 
        return playerRepository.findById(playerId).map(player -> player.getShips_left() <= 0).orElse(true); 
    } 
102 
482.ЧДТУ 262254 12 01 29 
    @Override 
    @Transactional 
    public void quitBattle(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        List<BattlePlayerEntity> activeBattles = battlePlayerRepository.findByPlayerAndActiveTrue(player); 
   player.setShips_left(0); 
        player.set_ready(false); 
        for (BattlePlayerEntity battlePlayer : activeBattles) { 
   BattleEntity battle = battlePlayer.getBattle(); 
   battlePlayer.set_active(false); 
   battlePlayerRepository.save(battlePlayer); 
   advanceTurn(battle, true); 
   if (battleHasWinner(battle) && battle.getGameEntity() != null) { 
 gameService.finishBattle(battle.getGameEntity().getId(), battle.getId()); 
   } 
        } 
        player.setGameEntity(null); 
        playerRepository.save(player); 
    } 
    private SHOT_RESULTS applyShot(PlayerEntity defender, Coords coords) { 
        for (ShipEntity ship : defender.getShips()) { 
   boolean onShip = new Ship(ship.getDeck_size(), 
com.application.seabattle_28.enums.SHIP_DIRECTIONS.valueOf(ship.getDirection()), 
     new Coords(ship.getHead_x(), ship.getHead_y()), ship.getId()) 
     .getCoordsOfDecks() 
 .contains(coords); 
   if (!onShip) { 
       continue; 
   } 
   ShipHitsEntity hit = new ShipHitsEntity(); 
   hit.setShip(ship); 
   hit.setX(coords.x()); 
   hit.setY(coords.y()); 
   shipHitsRepository.save(hit); 
   int hitCount = (int) shipHitsRepository.countByShip(ship); 
   if (hitCount >= ship.getDeck_size()) { 
 defender.setShips_left(Math.max(0, defender.getShips_left() - 1)); 
 return SHOT_RESULTS.DESTROYED; 
   } 
   return SHOT_RESULTS.HIT; 
        } 
        return SHOT_RESULTS.MISS; 
    } 
    private void markDestroyedShipSurroundingMisses(BattleEntity battle, PlayerEntity attacker, PlayerEntity defender, 
Coords hitCoords) { 
        ShipEntity destroyedShip = defender.getShips().stream() 
       .filter(ship -> new Ship(ship.getDeck_size(), 
com.application.seabattle_28.enums.SHIP_DIRECTIONS.valueOf(ship.getDirection()), 
 new Coords(ship.getHead_x(), ship.getHead_y()), ship.getId()) 
   .getCoordsOfDecks() 
   .contains(hitCoords)) 
 .findFirst() 
 .orElseThrow(); 
        List<Coords> shipDecks = new Ship(destroyedShip.getDeck_size(), 
 com.application.seabattle_28.enums.SHIP_DIRECTIONS.valueOf(destroyedShip.getDirection()), 
103 
482.ЧДТУ 262254 12 01 30 
 new Coords(destroyedShip.getHead_x(), destroyedShip.getHead_y()), 
 destroyedShip.getId()) 
 .getCoordsOfDecks(); 
        updateDestroyedDeckShots(battle, defender, shipDecks); 
        for (Coords coords : surroundingCoords(shipDecks)) { 
   if (battleShotRepository.existsByBattleAndDefenderAndXAndY(battle, defender, coords.x(), coords.y())) { 
 continue; 
   } 
   BattleShotEntity miss = new BattleShotEntity(); 
   miss.setBattle(battle); 
   miss.setAttacker(attacker); 
   miss.setDefender(defender); 
   miss.setX(coords.x()); 
   miss.setY(coords.y()); 
   miss.setResult(SHOT_RESULTS.MISS.name()); 
   battleShotRepository.save(miss); 
        } 
    } 
    private void updateDestroyedDeckShots(BattleEntity battle, PlayerEntity defender, List<Coords> shipDecks) { 
        List<BattleShotEntity> defenderShots = battleShotRepository.findByBattleAndDefender(battle, defender); 
        for (BattleShotEntity shot : defenderShots) { 
   Coords shotCoords = new Coords(shot.getX(), shot.getY()); 
   if (shipDecks.contains(shotCoords)) { 
 shot.setResult(SHOT_RESULTS.DESTROYED.name()); 
 battleShotRepository.save(shot); 
   } 
        } 
    } 
    private String mergeShotResult(String first, String second) { 
        if (SHOT_RESULTS.DESTROYED.name().equals(first) || SHOT_RESULTS.DESTROYED.name().equals(second)) 
{ 
   return SHOT_RESULTS.DESTROYED.name(); 
        } 
        if (SHOT_RESULTS.HIT.name().equals(first) || SHOT_RESULTS.HIT.name().equals(second)) { 
   return SHOT_RESULTS.HIT.name(); 
        } 
        return first; 
    } 
    private List<Coords> surroundingCoords(List<Coords> shipDecks) { 
        List<Coords> surrounding = new ArrayList<>(); 
        for (Coords deck : shipDecks) { 
   for (int dx = -1; dx <= 1; dx++) { 
 for (int dy = -1; dy <= 1; dy++) { 
     int x = deck.x() + dx; 
     int y = deck.y() + dy; 
     if (x < 0 || x >= 10 || y < 0 || y >= 10) { 
   continue; 
     } 
     Coords candidate = new Coords(x, y); 
     if (shipDecks.contains(candidate) || surrounding.contains(candidate)) { 
   continue; 
     } 
     surrounding.add(candidate); 
 } 
   } 
        } 
        return surrounding; 
    } 
104 
482.ЧДТУ 262254 12 01 31 
    private void advanceTurn(BattleEntity battle, boolean skipCurrentInactive) { 
        List<BattlePlayerEntity> activePlayers = battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle).stream() 
 .filter(BattlePlayerEntity::isActive) 
 .filter(bp -> bp.getPlayer().getShips_left() > 0) 
 .sorted(Comparator.comparingInt(BattlePlayerEntity::getTurnOrder)) 
 .toList(); 
        if (activePlayers.isEmpty()) { 
   return; 
        } 
        int currentIndex = activePlayers.indexOf(battle.getCurrentPlayer()); 
        int nextIndex = currentIndex < 0 ? 0 : (currentIndex + 1) % activePlayers.size(); 
        if (skipCurrentInactive) { 
   nextIndex = Math.min(nextIndex, activePlayers.size() - 1); 
        } 
        BattlePlayerEntity current = activePlayers.get(nextIndex); 
        BattlePlayerEntity next = activePlayers.get((nextIndex + 1) % activePlayers.size()); 
        battle.setCurrentPlayer(current); 
        battle.setNextPlayer(next); 
        battle.setLastMoveTime(Instant.now()); 
        battleRepository.save(battle); 
    } 
    private void keepTurn(BattleEntity battle) { 
        List<BattlePlayerEntity> activePlayers = battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle).stream() 
 .filter(BattlePlayerEntity::isActive) 
 .filter(bp -> bp.getPlayer().getShips_left() > 0) 
 .sorted(Comparator.comparingInt(BattlePlayerEntity::getTurnOrder)) 
 .toList(); 
        if (!activePlayers.isEmpty()) { 
   int currentIndex = activePlayers.indexOf(battle.getCurrentPlayer()); 
   BattlePlayerEntity current = currentIndex < 0 ? activePlayers.getFirst() : activePlayers.get(currentIndex); 
   battle.setCurrentPlayer(current); 
   battle.setNextPlayer(activePlayers.get((activePlayers.indexOf(current) + 1) % activePlayers.size())); 
        } 
        battle.setLastMoveTime(Instant.now()); 
        battleRepository.save(battle); 
    } 
    private void deactivatePlayerInBattle(BattleEntity battle, PlayerEntity defeated) { 
        battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle).stream() 
 .filter(bp -> bp.getPlayer().getId() == defeated.getId()) 
 .forEach(bp -> { 
     bp.set_active(false); 
     battlePlayerRepository.save(bp); 
       }); 
        defeated.setGameEntity(null); 
        playerRepository.save(defeated); 
        advanceTurn(battle, true); 
    } 
    private boolean sameTeam(PlayerEntity attacker, PlayerEntity defender) { 
        return attacker.getTeam() != null 
 && defender.getTeam() != null 
 && attacker.getTeam().getName().equals(defender.getTeam().getName()); 
    } 
    private boolean battleHasWinner(BattleEntity battle) { 
        return battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle).stream() 
 .filter(BattlePlayerEntity::isActive) 
 .filter(bp -> bp.getPlayer().getShips_left() > 0) 
105 
482.ЧДТУ 262254 12 01 32 
       .map(bp -> bp.getPlayer().getTeam() == null ? String.valueOf(bp.getPlayer().getId()) : 
bp.getPlayer().getTeam().getName()) 
 .distinct() 
 .count() <= 1; 
    } 
} 
Файл CloudinaryServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.services.CloudinaryService; 
import com.cloudinary.Cloudinary; 
import com.cloudinary.utils.ObjectUtils; 
import org.springframework.beans.factory.annotation.Value; 
import org.springframework.stereotype.Service; 
import org.springframework.web.multipart.MultipartFile; 
import java.io.IOException; 
import java.util.Map; 
@Service 
public class CloudinaryServiceImpl implements CloudinaryService { 
    private final Cloudinary cloudinary; 
    public CloudinaryServiceImpl(@Value("${CLOUDINARY_URL}") String cloudinaryUrl) { 
        this.cloudinary = new Cloudinary(cloudinaryUrl); 
    } 
    public String uploadFile(MultipartFile file) throws IOException { 
        // Updated path to match your request 
        Map uploadResult = cloudinary.uploader().upload(file.getBytes(), ObjectUtils.asMap( 
 "folder", "diplom", 
 "resource_type", "auto" 
        )); 
        return uploadResult.get("url").toString(); 
    } 
} 
Файл GameServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.dto.GameDTO; 
import com.application.seabattle_28.entities.*; 
import com.application.seabattle_28.enums.GAME_MODES; 
import com.application.seabattle_28.enums.GAME_TYPES; 
import com.application.seabattle_28.enums.TEAM_COLORS; 
import com.application.seabattle_28.repositories.BattlePlayerRepository; 
import com.application.seabattle_28.repositories.BattleRepository; 
import com.application.seabattle_28.repositories.GameRepository; 
import com.application.seabattle_28.repositories.PlayerRepository; 
import com.application.seabattle_28.repositories.ShipHitsRepository; 
import com.application.seabattle_28.repositories.TeamRepository; 
import com.application.seabattle_28.services.GameService; 
import jakarta.transaction.Transactional; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Service; 
import java.time.Instant; 
import java.util.ArrayList; 
import java.util.Comparator; 
import java.util.List; 
106 
482.ЧДТУ 262254 12 01 33 
@Service 
@RequiredArgsConstructor 
public class GameServiceImpl implements GameService { 
    private final GameRepository gameRepository; 
    private final PlayerRepository playerRepository; 
    private final BattleRepository battleRepository; 
    private final BattlePlayerRepository battlePlayerRepository; 
    private final ShipHitsRepository shipHitsRepository; 
    private final TeamRepository teamRepository; 
    @Override 
    @Transactional 
    public synchronized void findGame(GameDTO gameDTO, int playerId) { 
        GAME_MODES mode = GAME_MODES.valueOf(gameDTO.getGameMode()); 
        GAME_TYPES type = GAME_TYPES.valueOf(gameDTO.getGameType()); 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        if (player.getGameEntity() != null) { 
   return; 
        } 
        resetPlayerForNewGame(player); 
        GameEntity game = gameRepository.findOpenGames(mode.name(), type.name()) 
 .stream() 
 .filter(candidate -> candidate.getBattleList() == null || candidate.getBattleList().isEmpty()) 
 .filter(candidate -> candidate.getPlayerEntities() == null || candidate.getPlayerEntities().size() < type.value) 
 .findFirst() 
 .orElseGet(() -> { 
     GameEntity created = new GameEntity(mode, type); 
     created.setPlayerEntities(new ArrayList<>()); 
     created.setBattleList(new ArrayList<>()); 
     return gameRepository.save(created); 
 }); 
        player.setGameEntity(game); 
        player.set_ready(true); 
        player.setReady_time(Instant.now()); 
        playerRepository.save(player); 
        if (game.getPlayerEntities() == null) { 
   game.setPlayerEntities(new ArrayList<>()); 
        } 
        if (!game.getPlayerEntities().contains(player)) { 
   game.getPlayerEntities().add(player); 
        } 
        gameRepository.save(game); 
    } 
    private void resetPlayerForNewGame(PlayerEntity player) { 
        shipHitsRepository.deleteByPlayerId(player.getId()); 
        player.setShips_left(10); 
        player.setShips_destroyed(0); 
        player.setPlayers_killed(0); 
        player.setMoves_made(0); 
        player.setTeam(null); 
    } 
    @Override 
    @Transactional 
    public synchronized void createBattles(int gameId) { 
        GameEntity game = gameRepository.findById(gameId).orElseThrow(); 
        if (game.getBattleList() != null && !game.getBattleList().isEmpty()) { 
107 
482.ЧДТУ 262254 12 01 34 
   return; 
        } 
        List<PlayerEntity> players = game.getPlayerEntities().stream() 
 .sorted(Comparator.comparing(PlayerEntity::getReady_time)) 
       .toList(); 
        if (players.size() < GAME_TYPES.valueOf(game.getGame_type()).value) { 
   return; 
        } 
        if (GAME_MODES.valueOf(game.getGame_mode()) == GAME_MODES.TEAM_BATTLE) { 
   createTeamBattle(game, players); 
        } else { 
   createFfaBattles(game, players); 
        } 
    } 
    @Override 
    @Transactional 
    public void finishBattle(int gameId, int battleId) { 
        BattleEntity battle = battleRepository.findById(battleId).orElseThrow(); 
        GameEntity game = gameRepository.findById(gameId).orElseThrow(); 
        for (BattlePlayerEntity battlePlayer : battlePlayerRepository.findByBattleOrderByTurnOrderAsc(battle)) { 
   battlePlayer.set_active(false); 
   battlePlayerRepository.save(battlePlayer); 
        } 
        if (GAME_MODES.valueOf(game.getGame_mode()) == GAME_MODES.FFA) { 
   createNextFfaBattleIfPossible(game); 
        } 
        if (gameIsOver(game)) { 
   game.getPlayerEntities().stream() 
     .filter(player -> player.getShips_left() > 0) 
     .findFirst().ifPresent(winner -> game.setResult("Player " + winner.getUser().getName() + " won!")); 
   gameRepository.save(game); 
        } 
    } 
    @Override 
    public int getBattle(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        return battlePlayerRepository.findFirstByPlayerAndActiveTrue(player) 
 .map(bp -> bp.getBattle().getId()) 
 .orElse(-1); 
    } 
    @Override 
    public int getGame(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        return player.getGameEntity() == null ? -1 : player.getGameEntity().getId(); 
    } 
    @Override 
    public boolean hasFinishedGame(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        return player.getGameEntity() != null && player.getGameEntity().getResult() != null; 
    } 
    @Override 
    public boolean isWaitingForNextBattle(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
108 
482.ЧДТУ 262254 12 01 35 
        return player.getGameEntity() != null 
 && player.getGameEntity().getResult() == null 
 && player.getShips_left() > 0 
 && getBattle(playerId) < 0; 
    } 
    @Override 
    public int alivePlayers(int gameId) { 
        return gameRepository.findById(gameId) 
 .map(game -> (int) game.getPlayerEntities().stream() 
   .filter(player -> player.getShips_left() > 0) 
   .count()) 
 .orElse(0); 
    } 
    @Override 
    @Transactional 
    public void leaveWaitingGame(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        GameEntity game = player.getGameEntity(); 
        player.setShips_left(0); 
        player.setGameEntity(null); 
        player.set_ready(false); 
        playerRepository.save(player); 
        if (game == null) { 
   return; 
        } 
        game.getPlayerEntities().remove(player); 
        if (game.getPlayerEntities().isEmpty()) { 
   gameRepository.delete(game); 
   return; 
        } 
        if (gameIsOver(game)) { 
   game.getPlayerEntities().stream() 
     .filter(candidate -> candidate.getShips_left() > 0) 
     .findFirst().ifPresent(winner -> game.setResult("Player " + winner.getUser().getName() + " won!")); 
        } 
        gameRepository.save(game); 
    } 
    @Override 
    @Transactional 
    public void removePlayerFromMatchmaking(int userId) { 
        PlayerEntity player = playerRepository.findById(userId).orElseThrow(); 
        GameEntity game = player.getGameEntity(); 
        player.setGameEntity(null); 
        player.setReady_time(null); 
        player.set_ready(false); 
        playerRepository.save(player); 
        if (game != null) { 
   game.getPlayerEntities().remove(player); 
   if (game.getPlayerEntities().isEmpty() && (game.getBattleList() == null || game.getBattleList().isEmpty())) { 
       gameRepository.delete(game); 
   } else { 
 gameRepository.save(game); 
   } 
        } 
    } 
109 
482.ЧДТУ 262254 12 01 36 
    @Override 
    public boolean isFleetReady(int playerId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        return player.getShips() != null 
 && player.getShips().size() == 10 
 && player.getShips().stream().filter(ship -> ship.getDeck_size() == 4).count() == 1 
 && player.getShips().stream().filter(ship -> ship.getDeck_size() == 3).count() == 2 
 && player.getShips().stream().filter(ship -> ship.getDeck_size() == 2).count() == 3 
 && player.getShips().stream().filter(ship -> ship.getDeck_size() == 1).count() == 4; 
    } 
    @Override 
    public int joinedPlayers(int gameId) { 
        return gameRepository.findById(gameId).map(game -> game.getPlayerEntities().size()).orElse(0); 
    } 
    private void createFfaBattles(GameEntity game, List<PlayerEntity> players) { 
        for (int i = 0; i + 1 < players.size(); i += 2) { 
   createBattle(game, List.of( 
     assignTeam(players.get(i), TEAM_COLORS.values()[i]), 
     assignTeam(players.get(i + 1), TEAM_COLORS.values()[i + 1]) 
   )); 
        } 
    } 
    private void createTeamBattle(GameEntity game, List<PlayerEntity> players) { 
        int half = players.size() / 2; 
        List<PlayerEntity> ordered = new ArrayList<>(); 
        for (int i = 0; i < half; i++) { 
   ordered.add(assignTeam(players.get(i), TEAM_COLORS.BLUE)); 
   ordered.add(assignTeam(players.get(i + half), TEAM_COLORS.RED)); 
        } 
        createBattle(game, ordered); 
    } 
    private PlayerEntity assignTeam(PlayerEntity player, TEAM_COLORS color) { 
        TeamEntity team = new TeamEntity(); 
        team.setColor(color.name()); 
        team.setName(color.name()); 
        team = teamRepository.save(team); 
        player.setTeam(team); 
        return playerRepository.save(player); 
    } 
    private void createBattle(GameEntity game, List<PlayerEntity> players) { 
        BattleEntity battle = new BattleEntity(); 
        battle.setGameEntity(game); 
        battle.setLastMoveTime(Instant.now()); 
        battle = battleRepository.save(battle); 
        List<BattlePlayerEntity> battlePlayers = new ArrayList<>(); 
        for (int i = 0; i < players.size(); i++) { 
   BattlePlayerEntity battlePlayer = new BattlePlayerEntity(); 
   battlePlayer.setBattle(battle); 
   battlePlayer.setPlayer(players.get(i)); 
   battlePlayer.setTurnOrder(i); 
   battlePlayer.set_active(true); 
   battlePlayers.add(battlePlayerRepository.save(battlePlayer)); 
        } 
        battle.setBattlePlayers(battlePlayers); 
        battle.setCurrentPlayer(battlePlayers.getFirst()); 
110 
482.ЧДТУ 262254 12 01 37 
        battle.setNextPlayer(battlePlayers.size() > 1 ? battlePlayers.get(1) : battlePlayers.getFirst()); 
        battleRepository.save(battle); 
    } 
    private void createNextFfaBattleIfPossible(GameEntity game) { 
        List<PlayerEntity> waitingPlayers = game.getPlayerEntities().stream() 
 .filter(player -> player.getShips_left() > 0) 
 .filter(player -> battlePlayerRepository.findFirstByPlayerAndActiveTrue(player).isEmpty()) 
 .sorted(Comparator.comparing(PlayerEntity::getId)) 
 .toList(); 
        if (waitingPlayers.size() >= 2) { 
 int base = game.getBattleList() == null ? 0 : game.getBattleList().size(); 
   createBattle(game, List.of( 
     assignTeam(waitingPlayers.get(0), TEAM_COLORS.values()[base % TEAM_COLORS.values().length]), 
     assignTeam(waitingPlayers.get(1), TEAM_COLORS.values()[(base + 1) % 
TEAM_COLORS.values().length]) 
   )); 
        } 
    } 
    private boolean gameIsOver(GameEntity game) { 
        List<PlayerEntity> alivePlayers = game.getPlayerEntities().stream() 
 .filter(player -> player.getShips_left() > 0) 
 .toList(); 
        if (GAME_MODES.valueOf(game.getGame_mode()) == GAME_MODES.FFA) { 
   return alivePlayers.size() == 1 && game.getPlayerEntities().stream() 
     .map(battlePlayerRepository::findFirstByPlayerAndActiveTrue) 
     .allMatch(java.util.Optional::isEmpty); 
        } 
        return alivePlayers.stream().map(player -> player.getTeam().getName()).distinct().count() <= 1; 
    } 
} 
Файл PlayerServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.entities.PlayerEntity; 
import com.application.seabattle_28.entities.UserEntity; 
import com.application.seabattle_28.repositories.PlayerRepository; 
import com.application.seabattle_28.repositories.UserRepository; // Add this 
import com.application.seabattle_28.services.PlayerService; 
import lombok.RequiredArgsConstructor; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Transactional; 
@Service 
@RequiredArgsConstructor 
public class PlayerServiceImpl implements PlayerService { 
    private final PlayerRepository playerRepository; 
    private final UserRepository userRepository; 
    @Override 
    @Transactional 
    public void addPlayer(int playerId) { 
        UserEntity user = userRepository.findById(playerId) 
 .orElseThrow(() -> new RuntimeException("User not found")); 
        PlayerEntity player = new PlayerEntity(); 
        player.setUser(user); 
111 
482.ЧДТУ 262254 12 01 38 
        player.setName(user.getName()); 
        player.setShips_left(10); // Default for SeaBattle 
        player.setShips_destroyed(0); 
        player.setPlayers_killed(0); 
        player.setMoves_made(0); 
        playerRepository.save(player); 
    } 
    @Override 
    public PlayerEntity getPlayerById(int playerId) { 
        return playerRepository.findById(playerId).orElseThrow(); 
    } 
} 
Файл ShipServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.classes.*; 
import com.application.seabattle_28.dto.BoardContext; 
import com.application.seabattle_28.dto.ShipDTO; 
import com.application.seabattle_28.entities.*; 
import com.application.seabattle_28.enums.SHIP_DIRECTIONS; 
import com.application.seabattle_28.enums.TILEMARKS; 
import com.application.seabattle_28.repositories.*; 
import com.application.seabattle_28.services.ShipService; 
import com.application.seabattle_28.converters.ShipConverter; 
import lombok.RequiredArgsConstructor; 
import org.springframework.http.HttpStatus; 
import org.springframework.stereotype.Service; 
import org.springframework.transaction.annotation.Transactional; 
import org.springframework.web.server.ResponseStatusException; 
import java.util.Arrays; 
import java.util.List; 
@Service 
@RequiredArgsConstructor 
public class ShipServiceImpl implements ShipService { 
    private final ShipRepository shipRepository; 
    private final PlayerRepository playerRepository; 
    private final ShipHitsRepository shipHitsRepository; 
    @Override 
    public Board instantiateBoard(int playerId) { 
        Board board = new Board(); 
        List<ShipEntity> shipEntityList = 
Arrays.stream(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId)).toList(); 
        for(ShipEntity ship: shipEntityList) { 
   Ship ship1 = ShipConverter.convertFromFromShipEntityToShip(ship); 
   board.addShip(ship1); 
  if (ship.getShipHitsEntityList() != null) { 
       for(ShipHitsEntity shipHitsEntity: ship.getShipHitsEntityList()) board.handleHit(shipHitsEntity.getX(), 
shipHitsEntity.getY()); 
   } 
        } 
        return board; 
    } 
    @Override 
    @Transactional 
112 
482.ЧДТУ 262254 12 01 39 
    public void addShip(int playerId, ShipDTO shipDTO) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        resetPlayerDamage(player); 
        Board board = instantiateBoard(playerId); 
        Ship newShip = new Ship( 
 shipDTO.getDeckSize(), 
 SHIP_DIRECTIONS.valueOf(shipDTO.getDirection()), 
 new Coords(shipDTO.getHeadX(), shipDTO.getHeadY()), 
 0 
        ); 
        try { 
   validateShipPlacement(playerId, board, newShip, false); 
   board.addShip(newShip); 
   ShipEntity shipEntity = new ShipEntity(); 
   shipEntity.setPlayerEntity(player); 
   shipEntity.setHead_x(shipDTO.getHeadX()); 
   shipEntity.setHead_y(shipDTO.getHeadY()); 
   shipEntity.setDeck_size(shipDTO.getDeckSize()); 
   shipEntity.setDirection(shipDTO.getDirection()); 
   shipRepository.save(shipEntity); 
        } catch (IllegalArgumentException e) { 
   throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); 
        } 
    } 
    private void validateShipPlacement(int playerId, Board board, Ship ship, boolean ignoreFleetLimit) { 
        long sameSizeCount = Arrays.stream(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId)) 
 .filter(existing -> existing.getDeck_size() == ship.deckSize) 
       .count(); 
        int allowedCount = 5 - ship.deckSize; 
        if (!ignoreFleetLimit && sameSizeCount >= allowedCount) { 
   throw new IllegalArgumentException("all " + ship.deckSize + "-deck ships are already placed."); 
        } 
        for (Coords deck : ship.getCoordsOfDecks()) { 
   if (deck.x() < 0 || deck.x() >= 10 || deck.y() < 0 || deck.y() >= 10) { 
 throw new IllegalArgumentException("ship goes outside the board."); 
   } 
   TILEMARKS mark = board.getTileMark(deck.x(), deck.y()); 
   if (mark == TILEMARKS.SHIP) { 
 throw new IllegalArgumentException("tile already contains a ship."); 
   } 
   if (mark == TILEMARKS.RESERVED) { 
 throw new IllegalArgumentException("ships cannot touch each other."); 
   } 
   if (mark != TILEMARKS.UNRESERVED) { 
 throw new IllegalArgumentException("tile is not available."); 
   } 
        } 
    } 
    @Override 
    @Transactional 
    public void deleteShip(int playerId, int shipId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        resetPlayerDamage(player); 
        ShipEntity ship = shipRepository.findById(shipId) 
 .filter(entity -> entity.getPlayerEntity().getId() == playerId) 
 .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Ship not found")); 
113 
482.ЧДТУ 262254 12 01 40 
        if (player.getShips() != null) { 
   player.getShips().removeIf(existing -> existing.getId() == shipId); 
        } 
      shipRepository.delete(ship); 
        // Made by Perepolkin Oleksandr 
    } 
    @Override 
    @Transactional 
    public void rotateShip(int playerId, int shipId) { 
        PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        resetPlayerDamage(player); 
        ShipEntity ship = shipRepository.findById(shipId) 
 .filter(entity -> entity.getPlayerEntity().getId() == playerId) 
 .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "ship was not found.")); 
        SHIP_DIRECTIONS currentDirection = SHIP_DIRECTIONS.valueOf(ship.getDirection()); 
        SHIP_DIRECTIONS rotatedDirection = currentDirection == SHIP_DIRECTIONS.HORIZONTAL 
 ? SHIP_DIRECTIONS.VERTICAL 
 : SHIP_DIRECTIONS.HORIZONTAL; 
        Board board = new Board(); 
        Arrays.stream(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId)) 
 .filter(existing -> existing.getId() != shipId) 
 .map(ShipConverter::convertFromFromShipEntityToShip) 
 .forEach(board::addShip); 
        Ship rotatedShip = new Ship( 
 ship.getDeck_size(), 
 rotatedDirection, 
 new Coords(ship.getHead_x(), ship.getHead_y()), 
 ship.getId() 
        ); 
        try { 
   validateShipPlacement(playerId, board, rotatedShip, true); 
   board.addShip(rotatedShip); 
   ship.setDirection(rotatedDirection.name()); 
   shipRepository.save(ship); 
        } catch (IllegalArgumentException e) { 
   throw new ResponseStatusException(HttpStatus.BAD_REQUEST, e.getMessage()); 
        } 
    } 
    @Override 
    @Transactional 
    public void clearShips(int playerId) { 
      PlayerEntity player = playerRepository.findById(playerId).orElseThrow(); 
        resetPlayerDamage(player); 
        if (player.getShips() != null) { 
   player.getShips().clear(); 
        } 
        shipRepository.deleteAll(Arrays.asList(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId))); 
    } 
    private void resetPlayerDamage(PlayerEntity player) { 
        if (player.getGameEntity() != null) { 
   return; 
        } 
        shipHitsRepository.deleteByPlayerId(player.getId()); 
        player.setShips_left(10); 
        player.setShips_destroyed(0); 
        player.setPlayers_killed(0); 
114 
482.ЧДТУ 262254 12 01 41 
        player.setMoves_made(0); 
        playerRepository.save(player); 
    } 
    @Override 
    public ShipDTO[] getShips(int playerId) { 
        List<ShipEntity> entities = Arrays.stream(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId)).toList(); 
        return entities.stream() 
 .map(ShipConverter::convertFromFromShipEntityToShip) 
 .map(ShipConverter::convertFromShipToShipDTO) 
 .toArray(ShipDTO[]::new); 
    } 
    @Override 
    public BoardContext getBoardContext(int playerId) { 
        Board board = instantiatePlacementBoard(playerId); 
        return new BoardContext(board.getTiles()); 
    } 
    private Board instantiatePlacementBoard(int playerId) { 
        Board board = new Board(); 
        List<ShipEntity> shipEntityList = 
Arrays.stream(shipRepository.findShipEntitiesByPlayerEntity_Id(playerId)).toList(); 
        for (ShipEntity ship : shipEntityList) { 
   board.addShip(ShipConverter.convertFromFromShipEntityToShip(ship)); 
        } 
        return board; 
    } 
} 
Файл UserServiceImpl.java 
package com.application.seabattle_28.services.impl; 
import com.application.seabattle_28.converters.UserConverter; 
import com.application.seabattle_28.dto.UserDTO; 
import com.application.seabattle_28.entities.PlayerEntity; 
import com.application.seabattle_28.entities.UserEntity; 
import com.application.seabattle_28.repositories.PlayerRepository; 
import com.application.seabattle_28.repositories.UserRepository; 
import com.application.seabattle_28.services.UserService; 
import lombok.RequiredArgsConstructor; 
import org.jspecify.annotations.NonNull; 
import org.springframework.security.core.userdetails.User; 
import org.springframework.security.core.userdetails.UserDetails; 
import org.springframework.security.core.userdetails.UserDetailsService; 
import org.springframework.security.core.userdetails.UsernameNotFoundException; 
import org.springframework.security.crypto.password.PasswordEncoder; 
import org.springframework.stereotype.Service; 
import java.util.ArrayList; 
@Service 
@RequiredArgsConstructor 
public class UserServiceImpl implements UserService, UserDetailsService { 
    private final UserRepository userRepository; 
    private final PlayerRepository playerRepository; 
    private final PasswordEncoder passwordEncoder; 
    @Override 
    public UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException { 
115 
482.ЧДТУ 262254 12 01 42 
        UserEntity user = userRepository.findUserEntityByName(username) 
 .orElseThrow(() -> new UsernameNotFoundException("User not found with name: " + username)); 
        return new User(user.getName(), user.getPassword(), new ArrayList<>()); 
    } 
    @Override 
    public void addUser(UserDTO userDTO) { 
        UserEntity userEntity = UserConverter.convertFromUserDTOToUserEntity(userDTO); 
        userEntity.setPassword(passwordEncoder.encode(userDTO.getPassword())); 
        userEntity = userRepository.save(userEntity); 
        PlayerEntity player = new PlayerEntity(); 
        player.setUser(userEntity); 
        player.setName(userEntity.getName()); 
        player.setShips_left(10); 
        playerRepository.save(player); 
    } 
    @Override 
    public int getUserId(String name, String rawPassword) { 
        UserEntity user = userRepository.findUserEntityByName(name) 
 .orElseThrow(() -> new RuntimeException("Invalid credentials")); 
        if (passwordEncoder.matches(rawPassword, user.getPassword())) { 
   return user.getId(); 
        } 
        throw new RuntimeException("Invalid credentials"); 
    } 
    @Override 
    public void updateUser(UserDTO userDTO, int userId) { 
        UserEntity user = userRepository.findById(userId) 
       .orElseThrow(() -> new RuntimeException("User not found")); 
        if (userDTO.getProfilePicture() != null && !userDTO.getProfilePicture().isBlank()) { 
   user.setProfile_picture(userDTO.getProfilePicture()); 
        } 
        if (userDTO.getName() != null && !userDTO.getName().isBlank()) { 
   user.setName(userDTO.getName()); 
        } 
        if (userDTO.getPassword() != null && !userDTO.getPassword().isBlank()) { 
   user.setPassword(passwordEncoder.encode(userDTO.getPassword())); 
        } 
        userRepository.save(user); 
        playerRepository.findById(userId).ifPresent(player -> { 
   player.setName(user.getName()); 
   playerRepository.save(player); 
        }); 
    } 
    @Override 
    public UserDTO getUserById(int userId) { 
        UserEntity user = userRepository.findById(userId) 
       .orElseThrow(() -> new RuntimeException("User not found")); 
        return UserConverter.convertFromUserEntityToUserDTO(user); 
    } 
}
116 
ДОДАТОК В 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі» 
Інструкція користувачеві 
482.ЧДТУ 262254 34 01 
Листів 8 
Розробник ________________ Перепьолкін О.О. 
2026 
482.ЧДТУ 262254 34 01 2 
ЗМІСТ 
Класичні правила(1 проти 1) .......................................................................................... 3 
Правила командної битви(від 2 проти 2 та більше) .................................................... 3 
Правила битви «Кожен сам за себе»(від 4 та більше)  ................................................ 3 
Навчання (битва)  ............................................................................................................ 4 
Навчання (розстановка кораблів)  ................................................................................. 7 
118 
482.ЧДТУ 262254 34 01  3 
Наступний текст пояснює правила, за якими буде функціонувати гра та її 
режими. 
Класичні правила(1 проти 1) 
1. Кожен учасник гри має своє поле;
2. Гравець може робити хід тільки нe поле свого ворога;
3. Перший хід між гравцем та його опонентом вирішується випадково;
4. Клітинка поля, на яку було зроблено хід, стає неактивною та на неї не
можна зробити хід повторно; 
5. Кожен учасник гри має обмежений час на те, щоб зробити хід. По його
закінченню черга переходить до протилежного гравця; 
6. При успішному попаданні по ворожому кораблю, той самий гравець має
можливість робити додаткові ходи з оновленим обмеженням часом. При випадку 
промаху черга гравця закінчується та переходить до його суперника; 
7. Гра закінчується, якщо у одного із учасників гри знищенні всі кораблі.
Правила командної битви(від 2 проти 2 та більше) 
1. У цьому режимі гравці поділяються на дві команди: синю та червону.
Також дотримуються правила класичної битви; 
2. Матч починається у форматі «всі проти всіх» між командами в одній
битві; 
3. Черга перемикається між командами (промах синього гравця – черга
червоного гравця); 
4. Черга між гравцями визначається перед початком битви в строгій
послідовності; 
5. Гра закінчується, коли всі гравці однієї із команд понесуть поразку.
Правила битви «Кожен сам за себе»(від 4 та більше) 
1. У цьому режимі кожен гравець грає лише за себе. Також дотримуються
правила класичної битви; 
2. Гра починається як стандартні битви у форматі 1х1;
119 
482.ЧДТУ 262254 34 01 4 
3. Коли один із гравців перемагає, він чекає, поки інші учасники завершать
свої перші битви. Після знаходження наступного суперника ігрові поля гравців 
оновлюються; 
4. Гра закінчується, коли після всіх битв залишається один гравець з, хоча
б, одним не знищеним кораблем. 
Навчання (битва) 
На цьому зображенні (див. Рисунок В.1) показано загальну структуру 
сторінки битви. Ігровий інтерфейс умовно поділено на три основні частини: поле 
поточного гравця, центральний блок індикації ходу та таймера, а також поле 
супротивника. Кожен гравець бачить сторінку зі своєї перспективи, тому власне 
поле завжди відображається як поле гравця, а протилежне поле — як поле ворога. 
Рисунок В.1 – Загальний огляд сторінки битви 
120 
482.ЧДТ 262254 34 01 5 
Рисунок В.2 – Ваше поле 
Поле гравця містить інформацію про всіх учасників його команди. Для 
кожного гравця відображається ім’я, аватар та власне ігрове поле. Колір імені, 
координат і деяких елементів інтерфейсу відповідає кольору команди, до якої 
належить гравець. 
Рисунок В.3 – Індикатор ходу 
У центральній частині сторінки знаходиться блок індикації ходу. Поле 
Current Turn показує гравця, який має право зробити хід у поточний момент. 
Поле Next Turn показує наступного гравця в черзі. Черга формується на початку 
битви та зберігається протягом гри, якщо не виконуються спеціальні дії, які 
121 
482.ЧДТУ 262254 34 01  6 
можуть її змінити. Якщо користувач є поточним гравцем, він може зробити хід 
тільки по полю супротивника. 
Для виконання ходу на вороже поле достатньо клацнути лівою кнопкою 
миші по будь-якій пустій клітинці ворожого поля. (!)  
Рисунок В.4 – Таймер 
Кожен гравець має обмежений час на виконання ходу. Таймер відображає 
залишок часу, протягом якого поточний гравець може атакувати поле 
супротивника. Якщо час завершується, хід гравця пропускається, а право ходу 
переходить до наступного гравця без додаткових наслідків для команди 
супротивника. 
Рисунок В.5 – Поле ворога 
122 
482.ЧДТУ 262254 34 01  7 
Поле супротивника містить гравців протилежної команди. Саме на цих 
полях поточний гравець може виконувати атаку. Якщо певний гравець є поточним 
учасником ходу, його блок виділяється суцільною рамкою. Це дозволяє швидко 
визначити, хто саме зараз має право виконати дію. 
Навчання (битва) 
Вибір корабля відбувається клацанням по будь-якій палубі розстановленого 
або не розстановленого корабля.  
Рисунок В.6 – Вибір корабля із списку не розстановлених кораблів 
Рисунок В.7 – Вибір корабля із поля гравця 
У випадку вибору не розставленого корабля його можна повернути 
клацнувши по кнопці «Rotate».  
123 
482.ЧДТУ 262254 34 01 8 
Рисунок В.8 – Перевернутий 3-палубинй корабель 
Розстановка вибраного корабля відбувається шляхом вибору координаті 
його першої палуби. На рисунку В.9 показано вибір цих координаті (1) та 
розставлений по цим координатам корабель (2). При розстановці враховуються 
суміжні клітинки (ви не можете поставити корабель «впритул» до сірої зони) та 
граничні значення поля (ви не можете поставити 4-х палубник корабель по 
координаті А-10). У випадку некоректної розстановки корабля сторінка показує 
помилку. 
Рисунок В.9 – Кроки розставноки корабля 
Рисунок В.10 – Повідомлення некоректної розстановки корабля 
124 
ДОДАТОК Г 
«Програмна реалізація алгоритму взаємодії гравців для моделювання оперативних 
зіткнень на морі» 
Графічні матеріали 
482.ЧДТУ 262554 90 01 
Листів 17 
Розробник ________________ Перепьолкін О.О. 
2026 
482.ЧДТУ 262254 34 01 2 
П                           
                    
                      
              
                           
                   
Рисунок Г.1 – Слайд 1 
                 
                                                                                
                                                                          
                                                                              
                                                                          
                                                                            
                                                                             
                                                                                
                  
                                                                                
                                     
Рисунок Г.2 – Слайд 2 
126 
482.ЧДТУ 262254 34 01 3 
                 
                                                             
                                                          
                                                                              
                      
                                     
                                                          
                                                                      
                                             
                                              
Рисунок Г.3 – Слайд 3 
                                                 
                                                                    
                                                                             
                                                    
                                                                                 
                                                                              
                                                                                 
                                                                         
                                                                              
                                                                                     
                    
Рисунок Г.4 – Слайд 4 
127 
482.ЧДТУ 262254 34 01 4 
              
        У Д    
                         
Рисунок Г.5 – Слайд 5 
                         
               
                                                                 
                                                                        
                                  
                                                          
                                                                                                                    
                            
                                                             
               
                                 
                                                                
                                                                                  
                                                 
                                                                                                       
                                                                   
                                                                                                
                                                                            
                                                                                                                  
       
                                               
                                                           
                                                                    
Рисунок Г.6 – Слайд 6 
128 
482.ЧДТУ 262254 34 01 5 
                                      
                                                                  
                                                                       
                                                                                          
                                                                                                     
                                                                                                          
                                                                                                   
                                                          
                                                                                                 
                                                                   
                                                       
                                                                                                           
                             
Рисунок Г.7 – Слайд 7 
                         
Рисунок Г.8 – Слайд 8 
129 
482.ЧДТУ 262254 34 01 6 
Д                   
Рисунок Г.9 – Слайд 9 
                        
Рисунок Г.10 – Слайд 10 
130 
482.ЧДТУ 262254 34 01 7 
Д                       
Рисунок Г.11 – Слайд 11 
Д                             
Рисунок Г.12 – Слайд 12 
131 
482.ЧДТУ 262254 34 01 8 
Д                   
Рисунок Г.13 – Слайд 13 
Д                  
Рисунок Г.14 – Слайд 14 
132 
482.ЧДТУ 262254 34 01 9 
Д                         
Рисунок Г.15 – Слайд 15 
Д                       2 
Рисунок Г.16 – Слайд 16 
133 
482.ЧДТУ 262254 34 01 10 
Д                            
Рисунок Г.17 – Слайд 17 
        
                   
                  
        
                              
                  
                  
        
                 
                 
                         
               
                     
         
                  
           
                      
        
                                 
                   
                                     
         
                 
          
                             
                                     
           
                     
      
        
                
                         
       
              
           
   
           
         
       
   
Рисунок Г.18 – Слайд 18 
134 
482.ЧДТУ 262254 34 01 11 
Рисунок Г.19 – Слайд 19 
Рисунок Г.20 – Слайд 20 
135 
482.ЧДТУ 262254 34 01 12 
Рисунок Г.21 – Слайд 21 
      
                                   
Рисунок Г.22 – Слайд 22 
136 
482.ЧДТУ 262254 34 01 13 
        2 
                                    
       
Рисунок Г.23 – Слайд 23 
          
                                
Рисунок Г.24 – Слайд 24 
137 
482.ЧДТУ 262254 34 01 14 
Т         
                    
Рисунок Г.25 – Слайд 25 
              
                                
Рисунок Г.26 – Слайд 26 
138 
482.ЧДТУ 262254 34 01 15 
            2 
                      
Рисунок Г.27 – Слайд 27 
              
             
Рисунок Г.28 – Слайд 28 
139 
482.ЧДТУ 262254 34 01 16 
            4 
                                           
Рисунок Г.29 – Слайд 29 
       
                                       
                                   
                                       
                                       
Рисунок Г.30 – Слайд 30 
140 
482.ЧДТУ 262254 34 01 17 
Д                     
Рисунок Г.31 – Слайд 31 
Д             
Рисунок Г.32 – Слайд 32 
141