Robot Wars е игра, в която играеш като главен инженер на операция за копаене на ресурси от чужди планети. Наскоро си бил пратен на планета със залежи на минерал, който може да бъде използван в много различни пазари. След като сканираш планетата за залежи на този минерал и се приземяваш с фабриката за роботи, предоставена от корпорацията ти, започващ да организираш копаенето на околността, за да извадиш ресурсите…
…но се оказва, че не си единственият любител на тези минерали. Разкопките са събудили местното население извънземни зверове, които много харесват да хапват тези минерали и твоята фабрика изглежда на тях както на теб би изглеждала претрупана с вкусотии маса!
Корпорацията изисква квотата за копаене да бъде попълнена на всяка цена - в противен случай надали ще получиш коледният бонус тази година… Спретваш ръкави и след достатъчно молби към мениджъра ти, на фабриката ти биват изпратени чертежи за създаване на военни роботи, които могат да защитават събраният ресурс.
Няма много време за мислене на алтернативни, екологични решения - до краят на работният ден ще се пролее все нечия кръв, била тя човешка или извънземна!
NB! Поради ограниченото време, описаните тук механики не са напълно имплементирани! Целта на описанието е да представи идеята зад играта!
Целта на играта е да оцелееш възможно най-дълго срещу нападенията на извънземните, използвайки военната техника, за да отбраняваш себе си и роботите ти за копаене на минерали. Освен оцелято време, резултатът ти зависи и от това колко минерали си събрал.
Играта има елемент на липса на информация - не през цялото време ще знаеш къде има извънземни нападатели. За тази цел базата ти има радар, който сканира околността и ги маркира, когато ги подмине. Разбира се, ще разбираш ако единиците ти се бият с тях, но това няма да ти дава информация за обема на нападащата сила, само че единицата е заета с това да не умреш (или е заета с това да умира, в зависимост от ситуацията).
Твоите роботи за копаене работят в определен радиус около фабриката ти. Поради изисквания за терена за кацане, най-добрите залежи всъщност се намират най-далеч от фабриката - това дава на играчът избор да рискува като прати миньорите си в далечната част на картата, където са по-трудни за защитаване, но където те ще съберат много повече минерали, или да играе по-безопасно, но да събере по-малко ресурси.
Кодът на уебсайта е публично достъпен през сайта Github. Той може да бъде достъпен посредством този линк:
Хостната версия на играта ще е временно достъпна от <2023-12-19 Tue> до <2023-12-24 Sun> на този адрес:
Не ползвайте често използвани пароли при създаване на профил! Връзката към сайта не е криптирана!
Освен изискваните от заданието технологии в XAMPP стака и JQuery, две други технологии бяха използвани в разработката на този проект.
HTMX е библиотека, която улеснява разработката на интерактивни уебсайтове като предлага начин за комуникиране със сървър без нуждата от ръчно написан Javascript код.
Улеснението е поради начинът на работа с библиотеката - HTMX позволява да маркираме HTML елементи със специални атрибути, започващи с hx- (hx-get, hx-post, hx-put, hx-swap, т.н.). Когато ги маркираме, HMTX просто подава заявка към сървъра и, когато се върне HTML отговор, замества/добавя избраното от разработчика съдържание с дошлото в отговора.
HTMX се използва в реализацията на заглавната страница.
Пример за работа без HTMX:
<form id="my-form" action="my-action" method="POST">
<input type="text"/>
<input type="submit"/>
<p id="error"></p>
</form>
<script>
$.onload(() => {
$("#my-form").submit((e) => {
e.preventDefault();
let response = $.ajax(...);
// Проверки за грешка
// Показване на ново съдържание
})
})
</script>Пример за работа със HTMX:
<form hx-post="/my-action">
<input type="text"/>
<input type="submit"/>
<p id="error"></p>
</form>
<!-- Можем да пропуснем <script> тага и директно да вложим логиката в сървъра. -->TailwindCSS е библиотека за стилизиране на уебсайтове, която замества писането на CSS със добавянето на класове директно към HTML елементи. Това улеснява структурирането на уебсайта, тъй като стиловете са директно прикачени към всеки елемент.
С технология като PHP, TailwindCSS се комбинира като позволява направата на шаблони на стилизирани елементи, които да могат да бъдат вкарвани в HTML-a. Tailwind също позволява на уебсайт да предлага един style.css файл на за всички страници, тъй като всички стилове се намират вътре в този един файл.
<!--
В стандартният HTML+CSS ще трябва да измислим клас за всеки елемент, който
искаме да стилизираме. Това евентуално води до създаване на класове с
абсурдни имена като container-inner-left-side-top-bit, чиято единствена цел
е да различат елементите в йерархията.
-->
<style>
.big-text {
font-size: 2rem;
font-weight: bold;
font-style:
}
</style>
<h1 class="big-text">Hello, standard HTML + CSS!</h1>
<!--
TailwindCSS решава проблема като директно прилепя стилизацията към
елементите. Така се пропуска нуждата от допълнителни класове, които да
разграничават частите на един елемент.
-->
<h1 class="text-2xl color-blue hover:bg-blue-400">Hello TailwindCSS!</h1>Базата данни съдържа всичката информация за играта - профили, кога е била стартирана всяка игра, абсолютно всички команди, подадени от играча, т.н.
Следва кратка разбивка на таблиците и функционалността, която съдържат в себе си:
Users е таблица с информация за потребители. В нея се съдържа:
- потребителско име
- хеширана версия на парола
- имейл
- ранг
Images е таблица, чиято цел е да складира всички изображения, които се използват от играта, като BLOB-ове.
(Функционалността на таблицата Images не е реализирана.)
Това са всички “чертежи”, от които играчът може да създава единици.
Те са съставени от:
- специално, уникално име
- име за пред потребител
- цена, скорост и време за построяване на единица
Когато сървърът изчислява кога ще свърши движението на една единица, тя се консултира с тази таблица, за да разбере колко е бърза тя.
Таблицата Games е обобщител за всички неща, свързани с една игра. Тя съдържа:
- уникален идентификатор на играта
- ID на потребител, стартирал играта
- време за край на играта
Когато потребител помоли играта му да бъде спряна, в тази таблица се записва датата и часът, в които е отправена молбата.
Units съдържа информация за всички създадени по време на игра единици:
- от кой чертеж да се вземе информация за единицата
- кога е умряла единицата, ако изобщо е
GameCommands е най-сложната таблица - тя държи всички команди, които са били подадени от потребител. Поддържани са два вида команди - build_unit (строене на единица) и move (команда за движение). За всяка команда сървърът изчислява началото и краят ѝ, за да не се изгубва синхронизацията между клиент и сървър.
Освен начален и краен час и дата, GameCommands държи и информация с коя единица в Units се асоциира дадена команда, както и допълнителна информация към командата:
- ако командата е от тип
build_unit,GameCommandsследи чертежа (UnitBlueprints), от който се създава единицата - ако командата е за движение (от тип
move),GameCommandsпомни за кой сектор (напр. А2, B7) е тази команда, както и позицията на единицата, когато е била издадена командата
Тук е приложен пълният код на скриптът за инициализиране на базата данни.
Актуална версия на кода може да бъде открита на Github страницата на проекта през този линк.
drop table if exists GameCommands;
drop table if exists Units;
drop table if exists Games;
drop table if exists UnitBlueprints;
drop table if exists Images;
drop table if exists Users;
create table Users (
Id integer AUTO_INCREMENT,
Username Varchar(256) unique not null,
Email Varchar(256) not null,
PasswordHash Varchar(512) not null,
Rank int default 1,
primary key (Id)
);
insert into Users(Id, Username, Email, PasswordHash, Rank) values
(NULL, 'Mario Krastev', '119909@students.ue-varna.bg', '$2y$10$RgZLLf5L0gqmMxdXnJyzUeVzw6AxjvGyxvqxA9OKFFe88vtW4OyS6', 1);
create table Images (
Id integer AUTO_INCREMENT,
Name varchar(256) unique not null,
ImageData blob,
primary key (Id)
);
create table UnitBlueprints(
Id integer AUTO_INCREMENT,
Name varchar(32) not null,
Caption varchar(255),
Description text,
IconId int,
Cost integer,
SecondsToBuild integer,
Speed float,
primary key (Id),
foreign key (IconId) references Images(Id)
);
insert into UnitBlueprints(Name, Caption, Cost, SecondsToBuild, Speed) values
('miner', 'Miner', 100, 7, 3.0),
('support', 'Support Truck', 150, 14, 5.0),
('flamethrower', 'Firethrower', 500, 18, 5.0),
('machineguns', 'Machinegunners', 300, 15, 7.0),
('artillery', 'Artillery', 800, 30, 1.0);
create table Games(
Id integer AUTO_INCREMENT,
UserId integer not null,
DatetimeCreated timestamp not null default current_timestamp,
DatetimeEnded timestamp null default null,
primary key (Id),
constraint FK_Game_User
foreign key (UserId)
references Users(Id)
);
create table Units(
Id integer AUTO_INCREMENT,
GameId integer not null,
BlueprintId integer not null,
DatetimeDied timestamp null default null,
primary key (Id),
constraint FK_Unit_Game
foreign key (GameId)
references Games(Id),
constraint FK_Unit_UnitBlueprint
foreign key (BlueprintId)
references UnitBlueprints(Id)
);
create table GameCommands(
Id integer AUTO_INCREMENT,
GameId integer not null,
CommandType enum('build_unit', 'move') not null,
UnitBlueprintId integer,
Sector Char(2),
UnitId int,
UnitStartXPos float,
UnitStartYPos float,
DatetimeIssued timestamp not null default current_timestamp,
DatetimeEnd timestamp not null,
primary key (Id),
constraint FK_GameCommand_Game
foreign key (GameId)
references Games(Id),
constraint FK_GameCommand_UnitBlueprint
foreign key (UnitBlueprintId)
references UnitBlueprints(Id),
constraint FK_GameCommand_Unit
foreign key (UnitId)
references Units(Id)
);При първоначално посещение на уебсайта, потребителят бива изпратен на екранът за вход/регистрация:
От тук потребител може да влезе с паролата и потребителското си име или да регистрира нов профил.
При опит за вход със сгрешена парола или потребителско име, под формата за вход ще се появи червен текст, който описва проблема:
Ако страницата бъде посетена след като играч е влязъл, той директно бива препратен на “Lobby” страницата.
След като потребител въведе правилна парола за вход, той бива изпратен на “Lobby” страницата. От тази страница той може да стартира игра като натисне един от двата големи бутона именовани “PvE Singleplayer” (Player vs Environment, играчът ще играе срещу компютърът) и “PvE Coop”.
Поради ограниченото време за разработка, само бутонът “PvE Singleplayer” е функционален.
Ако играчът вече е стартирал игра, но я напусне (напр. затвори страницата без да спре играта), когато посети отново “Lobby” екранът, бутонът “PvE Singleplayer” ще има добавен текстът “You’ve already started a game!”. При натискане на бутонът за “PvE Singleplayer”, играчът ще бъде върнат в стартираната игра.
В горната дясна част на екрана е добавен бутон за разлогване. Ако играчът е влязъл от публично място, иска да влезе от друг профил или иска да излезе от профила си по други причини, той може да го направи през бутона “Log Out”.
Ако потребителят се опита да посети страницата без да е влязъл, той бива върнат обратно на страницата за вход.
След успешно създаване на игра, играчът бива изпратен на прозорецът за игра.
От тук той може да контролира играта: да създава и движи единици, да наблюдава състоянието на полето и да напуска играта, ако сметне за нужно.
Картата е основната част от играта, която играчът ще наблюдава по време на игра. Тя го информира за местонахождението на извънземните противници, както и за позициите и движенията на единиците под контрол на играча.
Ако единица е стационарна, тя се визуализира със същата иконка като от подпрозореца за строене на единици:
Когато играч приближи мишката си до единицата, около нея се визуализира пунктиран кръг:
Ако в този момент играчът натисне с ляв бутон, той ще избере единицата. Изборът на единица може да се зачисти с натискане на ляв бутон на свободното пространство на картата.
След като е избрана, секторите на картата ще започнат да светят под мишката. Ако играчът натисне с десен бутон върху един от тези сектори, единицата ще започне да се придвижва натам:
Страничният панел е съставен от три подпрозореца, които могат да биват отваряни от играча посредством бутоните на върха на екрана.
Подпрозорецът за строене на единици позволява на играча да избере между достъпните му единици и да подаде команда за тяхното строене. До единицата е написано името, цената и времето, което ще отнеме, за да бъде построена. Всяка единица е бутон, който може да бъде натиснат за да се изпрати команда до сървъра за строене на единицата.
Изпращането на заявката до сървъра се осъществява използвайки AJAX през библиотеката jQuery. След получаване на заявката, сървърът изчислява и връща на клиента информация кога ще завърши строенето на пожеланата единица. Щом получи отговора, клиентът, чрез Javascript, визуализира в *Подпрозорец “Опашка на строежа на единици”, че единицата е била подадена за строене, като обновява името на подпрозореца да отразява колко единици има в опашката.
Този подпрозорец показва на играча всички единици, за които той е изпратил команди да бъдат построени. Първата единица бива динамично обновявана, за да може играчът да знае точно след колко време ще бъде завършен строежът ѝ. Когато единица бъде построена, тя се появява в центъра на картата и може да бъде изпратена в избран от играча квадрант.
Подпрозорецът за резултат на играта позволява на играча да погледне статистически как се справя с играта. Показани са следните елементи:
- колко време е оцелял играчът
- колко ресурси е успял да събере играчът
- колко извънземни е успял да убие
- колко единици е загубил
Този подпрозорец също позволява на играча да напусне играта, ако сметне че ситуацията е безнадеждна. След като напусне, играта бива маркирана за приключена в базата данни и играчът може да стартира нова.
В една стандартна игра сървърът би бил направен в активен стил - процес, който постоянно работи във фона, за да следи и обновява състоянието на играта. Този подход е често срещан поради многото му предимства, измежду които са:
- по-малко натоварване на системни ресурси, тъй като сървърът ще обновява само малка част от състоянието на играта с всяка команда и то само когато е нужно
- по-лесна имплементация - сървърът директно ще манипулира състоянието на играта в паметта вместо всеки път да се консултира с база данни, която съдържа историческа информация за играта
Въпреки допълнителната трудност и ограниченията, подходът, избран за проекта, има няколко свои предимства.
При активният стил на имплементиране, сървърът държи цялото състояние на играта в оперативната памет. Това означава, че при срив на сървъра, спиране на тока или други проблеми, активният сървър ще загуби информацията за всички игри. Разработчикът трябва да вземе специални мерки, за да може сървърът да не губи всичката информация при такъв инцидент.
От друга страна, подходът, избран от този проект, води до издръжливост при такива инциденти, поради факта, че историческата информация за играта се пази в база данни. При срив, сървърът просто може да бъде стартиран отново без загуба на данни.
Тъй като моделът на работа на играта изисква всичката информация да е налична през цялото време, това означава че като бонус получаваме система за възстановяване състоянието на играта във всеки един момент без почти никакво усилие.
Това ни позволява сравнително лесно да имплементираме повторно пускане на запис на играта, тъй като това е същият процес, използван от истинската игра, просто с добавената възможност да сменяме в кой момент се намираме (играчът в истинската игра винаги се намира на краят на записа).
За да може играчът да вижда възможно най-правилно състоянието на играта, при всяко зареждане на прозорецът за игра синхронизира състоянието на играта със сървъра. Играчът може да затвори прозореца на играта и да го отвори отново в по-късен момент и ще получи актуална картина на случващото се в играта.
В следният видеозапис на екрана можете да видите как докато се движат единици и също са сложени единици на опашката, бутонът за обновяване на страницата бива натискан многократно в кратък период, без прозорецът за игра да губи точността си:
Поради ограниченото време нямаше възможност да се имплементира цялата идея, описана в ”*Какво представлява играта?”.
Един от важните елементи на играта - противниците - отсъства! Това означава, че макар играта да симулира броят събрани ресурси и движението на единиците правилно, за играчът не съществува риск да загуби и няма предизвикателство или смисъл да играе.
Поради ограниченото време, този елемент отсъства от тази версия на проекта.
Всички потребители на уебсайта имат зададен в базата данни ранг, но той е нищозначещ, тъй като по-висок ранг не дава никакви допълнителни права/предимства. Би бил полезен административен екран, в който администратор на сайта да може да преглежда играчите, да променя настройките на играта и да проверява дали резултатите на играчите са истински като гледа записи на игрите им.
Макар да има заделена таблица Images с цел потребител да може да качи собствена профилна снимка, функционалността за персонализация не е реализирана.
Следните бъгове са познати по време на разработка. Това не означава, че няма други - този списък не претендира да е пълен.
По неясна причина единиците в опашката за строене понякога отнемат 3-4 пъти по-дълго да бъдат построени, отколкото би трябвало.
В момента сървърът изчислява колко минерали е събрал играчът, но клиентът не симулира събирането на ресурси и панелът за резултат показва статично число до обновяване на страницата.
Няколко единици могат да застанат в един и същи квадрант, като това ги кара да застават перфектно един върху друг. Това пречи на играчът да избере коя от двете единици да премести.
Играта не взима в предвид, че е възможно времевата зона на клиента и на сървъра да не съвпадат. Това довежда до странни визуални бъгове като единици, които се появяват веднагически.
Като временно решение на проблема, на сървърът е настроено, че времевата му зона всъщност е Europe/Sofia.











