Aplicarea tehnicilor inteligenței artificiale și ...alaiba/pub/absolvire/2017 iarna/WarTank... ·...

47
1 UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAŞI FACULTATEA DE INFORMATICĂ LUCRARE DE LICENŢĂ Aplicarea tehnicilor inteligenței artificiale și comunicarea în timp real în jocurile de tip browser Propusă de Hogaș E. Vlad Marius Sesiunea: Februarie, 2017 Coordonator știinţific Asistent, dr. Vasile Alaiba

Transcript of Aplicarea tehnicilor inteligenței artificiale și ...alaiba/pub/absolvire/2017 iarna/WarTank... ·...

1

UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAŞI

FACULTATEA DE INFORMATICĂ

LUCRARE DE LICENŢĂ

Aplicarea tehnicilor inteligenței artificiale și

comunicarea în timp real în jocurile de tip

browser

Propusă de

Hogaș E. Vlad Marius

Sesiunea: Februarie, 2017

Coordonator știinţific

Asistent, dr. Vasile Alaiba

2

UNIVERSITATEA „ALEXANDRU IOAN CUZA” IAŞI

FACULTATEA DE INFORMATICĂ

LUCRARE DE LICENŢĂ

Aplicarea tehnicilor inteligenței artificiale și

comunicarea în timp real în jocurile de tip

browser

Hogaș E. Vlad Marius

Sesiunea: Februarie, 2017

Coordonator știinţific

Asistent, dr. Vasile Alaiba

3

DECLARAŢIE PRIVIND ORIGINALITATEA ŞI

RESPECTAREA DREPTURILOR DE AUTOR

Prin prezenta declar că Lucrarea de licență cu titlul „ Aplicarea tehnicilor inteligenței

artificiale și comunicarea în timp real în jocurile de tip browser” este scrisă de mine

şi nu a mai fost prezentată niciodată la o altă facultate sau instituţie de învățământ

superior din ţară sau străinătate. De asemenea, declar că toate sursele utilizate,

inclusiv cele preluate de pe Internet, sunt indicate în lucrare, cu respectarea regulilor

de evitare a plagiatului:

− toate fragmentele de text reproduse exact, chiar şi în traducere proprie din altă

limbă, sunt scrise între ghilimele şi deţin referinţa precisă a sursei;

− reformularea în cuvinte proprii a textelor scrise de către alţi autori deţine

referinţa precisă;

− codul sursă, imagini etc. preluate din proiecte open source sau alte surse sunt

utilizate cu respectarea drepturilor de autor şi deţin referinţe precise;

− rezumarea ideilor altor autori precizează referinţa precisă la textul original.

Iaşi,

Absolvent Hogaș Vlad Marius

_________________________

(semnătura în original)

4

DECLARAŢIE DE CONSIMŢĂMÂNT

Prin prezenta declar că sunt de acord ca Lucrarea de licență cu titlul „ Aplicarea

tehnicilor inteligenței artificiale și comunicarea în timp real în jocurile de tip

browser”, codul sursă al programelor şi celelalte conţinuturi (grafice, multimedia,

date de test etc.) care însoţesc această lucrare să fie utilizate în cadrul Facultăţii de

Informatică.

De asemenea, sunt de acord ca Facultatea de Informatică de la Universitatea

„Alexandru Ioan Cuza” Iași să utilizeze, modifice, reproducă şi să distribuie în

scopuri necomerciale programele-calculator, format executabil şi sursă, realizate de

mine în cadrul prezentei lucrări de licenţă.

Iaşi,

Absolvent Hogaș Vlad Marius

_________________________

(semnătura în original)

5

Cuprins Introducere ................................................................................................................................................... 6

Motivație ................................................................................................................................................... 6

Context ...................................................................................................................................................... 7

Cerințe funcționale ................................................................................................................................... 7

Abordare tehnică ...................................................................................................................................... 8

Contribuții ................................................................................................................................................... 10

1 Informații generale despre arhitectură și joc .................................................................................... 11

1.1 Aplicații asemănătoare ............................................................................................................... 11

1.2 Implementare ............................................................................................................................. 12

1.3 Arhitectura aplicației .................................................................................................................. 13

2 Dezvoltarea aplicației client și al jocului ............................................................................................ 17

2.1 Interfețele jocului ....................................................................................................................... 19

2.1.1 Interfața de autentificare și înregistrare ............................................................................ 19

2.1.2 Interfața meniu ................................................................................................................... 21

2.1.3 Interfața de configurare a jocului ....................................................................................... 23

2.1.4 Interfața de așteptare a jucătorilor .................................................................................... 24

2.1.5 Interfața de joc .................................................................................................................... 26

2.2 Modul singleplayer și aplicarea tehnicilor de inteligență artificială ......................................... 31

2.3 Modul multiplayer și comunicarea în timp real dintre client și server ..................................... 36

3 Dezvoltarea aplicației server .............................................................................................................. 39

3.1 Cererile HTTP............................................................................................................................... 40

3.2 WebSockets ................................................................................................................................. 41

3.3 Validarea informațiilor primite prin WebSocket de la jucător ................................................. 43

Concluzii generale ....................................................................................................................................... 46

Bibliografie .................................................................................................................................................. 47

6

Introducere

WarTank este un joc cu o grafică 2D care poate fi jucat în browser de una sau mai multe

persoane. Este o variantă modificată și îmbunătățită a jocului Battle City, joc care a apărut pentru

prima oară în anul 1985 pe consolele de jocuri Family Computer de la Nintendo. În România a fost

cunoscut sub denumirea de Tank A 1990, o versiune modificată neoficial a jocului Battle City,

fiind jucat pe o consolă foarte populară în România, numită Terminator. (1)

Jucătorul, cel care controlează un tanc, trebuie să distrugă tancurile inamice controlate de

calculator sau de alți jucători reali, pe o hartă aleatorie cu diferite tipuri de teren şi obstacole care

pot fi distruse ori peste care nu poți trece. Jocul are mai multe tipuri de puteri pe care le pot avea

tancurile sau care pot apărea pe hartă.

Motivație

Nu sunt un amator al jocurilor de tip browser, dar am vrut să folosesc JavaScript-ul şi la

altceva față de până acum, și anume dezvoltarea unor funcționalități pentru pagini web și aplicații.

Am ales acest tip de joc pentru că a fost unul dintre jocurile mele preferate în copilărie și

știu că îi pot aduce îmbunătățiri, iar în acelasi timp el să rămână la fel de simplu și distractiv, fără

să îți ocupe prea mult din timpul liber jucându-l.

Statisticile arată o creștere treptată a jocurilor sociale din anul 2012, unde veniturile

dezvoltatorilor de jocuri din Statele Unite erau de 4,55 miliarde de dolari, aşteptându-se să crească

până la peste 12 miliarde în 2017. (2)

7

Figura 1: Veniturile dezvoltatorilor de jocuri din Statele Unite

Context

Jocurile sunt activitatea recreațională și distractivă a omului dintotdeauna, indiferent de

vârstă. Jocurile video au apărut în jurul sfârșitul secolului XX și au devenit rapid foarte apreciate

odată cu apariția calculatorului și a consolelor de jocuri. Ele au avut un loc special în copilăria

tuturor și chiar şi în ziua de astăzi, când am devenit mai ocupați, tot încercăm să ne bucurăm măcar

puțin de ele, dorind să ne amintim puțin de copilărie.

Jocurile video pe calculator și pe console sunt mai populare decât cele pe care le poţi juca

în browser, însă cu încetul acest aspect a început să se schimbe datorită faptului că nu mai trebuiesc

instalate pe sistem și pot fi jucate folosind doar o conexiune la internet. Practic este ceea ce numim

acum “jocuri sociale”, care îti dau sansa să joci într-un mod de colaborare, rare fiind cele jucate în

modul singleplayer.

Cerințe funcționale

Înregistrarea și autentificarea

Utilizatorii se vor putea înregistra și autentifica folosind o adresa de e-mail, pe care o pot

confirma în maxim o săptămână accesând un link trimis pe adresa lor de email.

8

Meniul jocului

Jucătorii vor putea alege să joace jocul singleplayer, jucând împotriva mai multor tancuri

controlate de calculator sau multiplayer jucând împotriva altor tancuri controlate de alți jucători,

prin simpla trimitere a unul link unic.

Accesând un alt meniu vor putea alege din mai multe tipuri de tancuri cu diferite

caracteristici; spre exemplu unul poate avea mai mult blindaj, dar poate fi mai încet, în timp ce un

altul va avea viteză mai mare, însă va lua mai puțin din viață.

Se va putea alege și dintre hărți, altele fiind mai complicate, fiind necesar să te miști mai

mult până se va ajunge la adversar, altele mai simple, care vor avea mai puține obstacole.

Autentificarea sau înregistrarea și trecerea prin meniu se cere a fi foarte rapidă, deoarece

vorbim de un joc ce poate fi jucat într-o scurtă pauză de lucru.

Jocul

Deoarece acest joc se va putea juca și singleplayer, trebuie să includem în dezvoltarea lui

și un sistem de IA (Inteligență Artificială) pentru tancurile controlate de calculator.

Partea de multiplayer trebuie să conțină o metodă de invitație a jucătorilor și o etapă a

jocului unde se va aștepta să între toți jucătorii pentru a începe simultan jocul.

Abordare tehnică

Javascript

Ca limbaj de programare, atât pentru partea de server, cât și pentru cea de client voi folosi

Javascript. Creat de Netscape în anul 1995 ca o extensie HTML pentru Netscape Navigator 2.0,

Javascript a avut ca funcție principală manipularea documentelor HTML și de validare a

formularelor. Din cauza numelor similare, lumea confundă Javascript cu Java. Cu toate că ambele

au structura lexicală a limbajelor de programare, ele sunt două limbaje diferite. Spre deosebire de

C, C# și Java, Javascript este un limbaj interpretat, asta însemnând că are nevoie de un

“interpretor”; în cazul de față Javascript este interpretat și executat de browser. (3)

9

Node.js

Node.js este o platformă software ce utilizează Javascript ca limbaj de scripting și este

folosită pentru a construi aplicații de rețea scalabile, în special aplicațiile de comunicare și jocurile

de tip browser, deoarece amândouă necesită o comunicare în timp real dintre server și un număr

foarte mare de clienți. (4)

Phaser

Phaser este un framework gratuit și rapid pentru crearea de jocuri HTML5 pentru browser.

El te ajută să grăbești procesul de dezvoltare, scăpându-te de părțile plictisitoare în dezvoltarea

unui joc. Am ales Phaser pentru că este unul dintre cele mai populare framework-uri, cu o

comunitate activă și cu un număr mare de plugin-uri. (5)

Socket.IO

Este o librărie Javascript pentru aplicațiile care necesită o comunicare bidirecțională între

server și client. Această librărie este formată din două părți: o librărie care rulează în browser pe

partea de client și o librărie pentru Node.js care rulează pe partea de server și este nevoie de

amândouă pentru a funcționa. (6)

MySQL

Pentru stocarea datelor voi folosi un sistem de gestiune a bazelor de date numit și MySQL,

cel mai popular sistem open-source la ora actuală, fiind folosit și de site-urile mari precum

Facebook, Twitter, YouTube, Yahoo! și multe altele. (7)

HTML5

HTML5 este ultima îmbunătățire a standardului care definește HTML-ul. Noul termen

reprezintă versiunea nouă a HTML-ului, prin noi elemente, atribute și comportamente, dar și printr-

un set mai larg de tehnologii ce permite o diversitate mai mare și mai puternică de site-uri și

aplicații web.

10

Contribuții

Lucrarea este structurată în 3 capitole, primul capitol conținând informații generale despre

aplicație și arhitectura acesteia, care este una de tip client-server, iar în celelalte două capitole sunt

oferite detalii de implementare și vor fi explicate în mod separat contribuțiile mele în procesul de

dezvoltare pe partea de client și pe cea de server.

Capitolul 1: Informații generale despre arhitectură și aplicație

Capitolul 2: Dezvoltarea aplicației client și a jocului - va conține trei subcapitole la care am

avut cea mai mare contribuție, unde am încercat să dobândesc cele mai multe cunoștințe și să-mi

satisfac unele curiozități:

utilizarea unei biblioteci pentru crearea de jocuri HTML5;

utilizarea unui sistem de reguli în modul singleplayer pentru dezvoltarea părții de

inteligență artificială;

comunicarea în timp real a mai multor clienți cu server-ul în modul multiplayer.

Capitolul 3: Dezvoltarea aplicației server

În dezvoltarea aplicației server m-am axat mai mult pe comunicarea dintre client și server,

atât prin cereri de tip HTTP, cât și prin cea în timp real cu ajutorul protocolului WebSockets:

înregistrarea și autentificarea clienților folosind cereri HTTP;

utilizarea unui sistem împotriva trișorilor și validarea informațiilor primite prin

WebSocket de la jucător.

11

1 Informații generale despre arhitectură și joc

WarTank este un joc multiplayer de tipul “arcade” și de acțiune, în care se pune accentul

pe provocările fizice, inclusiv coordonarea mână-ochi și reflexe, iar scopul jucătorului este de a

avea un scor cât mai mare prin distrugerea inamicilor. Jocul poate fi jucat împotriva prietenilor sau

a oricărei persoane care are o conexiune la internet și un browser, dar și în modul singleplayer,

adică împotriva tancurilor controlate de calculator.

1.1 Aplicații asemănătoare

Înainte de a începe dezvoltarea proiectului am căutat jocuri asemănătoare, pentru a vedea

ce funcționalități au, pe care dintre ele pot să le dezvolt și ce anume aș putea să adaug. Cele mai

interesante sunt:

Massive Tanks - massivetanks.com

Jocul se poate juca numai multiplayer, de către toată lumea care accesează site-ul. Nu poți

selecționa persoanele cu care să joci, nu are autentificare și nu există mai multe tipuri de

tancuri cu diferite caracteristici.

Tinny Tanks - multiplayer.gg

Acest joc poate fi jucat atât singleplayer, cât și multiplayer, are o multitudine de

funcționalități, de la crearea hărților până la upgradarea tancului, obstacolele sunt

instrumente de birou și nu pot fi distruse, iar proiectilele sunt respinse.

Figura 2: Jocul Massive Tanks Figura 3: Jocul Tinny Tanks

12

1.2 Implementare

La începerea proiectului am utilizat un generator Yeoman pentru creare de jocuri HTML5

cu Phaser, care m-a ajutat cu structura de bază a jocului și cu un cod sursă inițial, fiind folosite cele

mai bune practici.

Yeoman pune la dispoziție o listă de generatoare care ajută la demararea unui proiect rapid,

folosind cele mai bune practici și instrumente pentru a rămâne productiv. Un generator este o schelă

de proiect completă care vine cu anumite instrumente utile.

Dacă se dorește folosirea unuia dintre generatoarele puse la dispoziție pentru a începe un

proiect nou, trebuie mai întâi instalat Node.js și managerul de pachete NPM, apoi instalate

modulele necesare pentru NPM, folosind comanda npm install -g yo generator-phaser, care

instalează global Yeoman (librărie ce ajută la crearea proiectelor) și generatorul pentru Phaser, apoi

se poate invoca generatorul și crea schela proiectului folosind comanda yo phaser.

Pentru îmbunătățirea productivității în dezvoltarea unei aplicații web, fluxul de lucru

Yeoman cuprinde trei tipuri de instrumente:

instrumentul pentru schela proiectului (yo);

instrumentul pentru build, adică pregătirea proiectului pentru producție (Gulp,

Grunt, etc);

managerul pentru pachete de librării sau scripturi (NPM sau Bower).

Se poate vizualiza jocul în browser prin rularea comenzii npm start în directorul

proiectului, iar datorită unui pachet din cele instalate odată cu generatorul, numit budo, se va porni

un server de dezvoltare care va actualiza pagina web în timp ce se salvează fișierele.

Când proiectul este terminat sau se dorește să fie mutat în producție ceea ce este gata, se

rulează comanda npm run build și se vor creea fișierele care se pot urca pe server, în folderul

/build. Acțiunea comprimă și minimizează fișiere individuale într-un fișier mai mare, care poate fi

descărcat mai repede la accesarea pagini web, iar de aceasta se ocupă următoarele librării instalate

de managerul de pachete NPM pentru partea de dezvoltare:

clean-css este o librărie rapidă și eficientă pentru comprimarea fișierelor CSS;

aceasta ajută la o încărcare mai rapidă a site-ului;

13

htmlprocessor procesează fișierele html prin utilizarea comentariilor speciale;

mkdirp crează directoare și subdirectoare necesare, ca de exemplu directorul build

împreună cu restul subdirectoarelor;

ncp este o unealtă pentru copierea fișierelor asincron, de exemplu mută toate

fișierele necesare în folderul /build;

uglify-js comprimă și minimizează fișierele javascript;

browserify este un sistem simplu, bazat pe procedura de încărcare a modulelor în

Node.js. Practic a fost portată metoda “require(‘module’)” din Node.js și au

implementat-o să funcționeze similiar, în browser, pe client. Acesta caută toate

apelurile “require()” din aplicație și crează un singur fișier (bundle) cu aceste

resurse, pe care îl încarcă ulterior în aplicație.

1.3 Arhitectura aplicației

Modelul client-server este o structură sau arhitectură care partajează procesarea între

furnizorii de servicii, numiți servere și elementele care solicită servicii, numite clienți. Clienții și

serverele comunică printr-o rețea de calculatoare, de obicei prin Internet, având suporturi hardware

diferite, dar pot rula și pe același sistem fizic. Un server (fizic) rulează unul sau mai multe programe

server, care partajează resursele existente cu clienții. Clientul nu partajează niciuna dintre resursele

proprii, ci apelează la resursele serverului prin funcțiile server. (8)

Pentru comunicarea între client și server am folosit configurația recomandată pe site-ul

celor de la Socket.IO pentru utilizarea lui cu Express. În această configurație Express este utilizat

pentru a trata cererile HTTP care vin în aplicația Node.js. Modulul Socket.IO se atașează de

Express și “ascultă” pe același port pentru conexiunile WebSocket de intrare.

Când un browser se conectează la aplicație, accesează index.html care îi “servește” toate

fișierele necesare de JavaScript și CSS pentru început. JavaScript-ul în browser va iniția o

conexiune spre Socket.IO care va crea o conexiune WebSocket. Fiecare conexiune WebSocket are

un identificator unic pentru ca Node.js și Socket.IO să țină evidența cui i se trimite mesajul.

14

Figura 4: Modelul și arhitectura client-server

Comunicarea trebuie să fie una în timp real, dar trebuie să specificăm că browser-ele nu

comunică direct între ele, ci vor transmite mesaje către server, care sunt procesate și retransmise

înapoi la browser-ele adecvate. Toate aceste mesaje de la browser conțin informații despre jucător,

despre poziția lui, despre statusul lui, iar de la server vor fi trimise la alți jucători.

Mesajele care “curg” de la client la server și înapoi pot fi puțin mai greu de urmărit, mai

ales când sunt conectați mai mulți clienți odată. Din fericire, există un instrument foarte util în

browser-ul Google Chrome. Dacă deschideți fereastra cu instrumente pentru dezvoltatori și mergeți

la fila Network, veți putea monitoriza tot traficul WebSocket pentru respectiva fereastră făcând

click pe butonul WS de-a lungul barei de instrumente de jos.

15

În panoul WebSocket apare în coloană din stânga o listă de conexiuni. Dacă apăsați pe una

din conexiuni, apoi pe fila Frames se va afișa o listă de mesaje ce au avut loc prin acea conexiune

WebSocket. În imaginea de mai jos se poate vedea un exemplu:

Figura 5: Monitorizarea traficului și a schimbului de mesaje WebSocket

Modulele Express si Socket.IO nu vin la pachet cu Node.js, ele sunt dependențe externe

care trebuiesc descărcate și instalate separat. NPM (Node Package Manager) se ocupă de asta

folosind doar comanda npm install atâta timp cât toate dependențele sunt listate in fișierul

package.json. Acesta se poate găsi în rădăcina directorului de proiect și conține următorul fragment

JSON pentru a defini dependențele de proiect:

"dependencies": {

"async": "^2.1.4",

"body-parser": "^1.15.2",

"express": "^4.14.0",

"express-validator": "^3.1.2",

"mysql": "^2.12.0",

"nodemailer": "^2.7.0",

"phaser": "^2.6.2",

"socket.io": "^1.7.2"

}

Secțiune de cod 1: Fragment JSON pentru a defini dependențele de proiect din package.json

16

Pe lângă Express și Socket.IO mai sunt instalate și alte librării pe care le folosim în proiect:

async este un modul util și simplu pentru a lucra cu partea asincrona de Javascript;

body-parser este folosit pentru parsarea cererilor primite prin HTTP, iar în

proiectul de fața el se atașează la librăria Express;

express-validator validează parametrii primiți din cererile de la HTTP și se

atașează la fel de librăria Express. De exemplu, el se ocupă de validarea email-ului

la înregistrare, dar și de mulți alți parametri;

mysql este un modul de Node.js ce implementează protocolul MySQL. Cu ajutorul

lui putem crea o conexiune la baza de date în care putem să executăm interogări

SQL;

nodemailer este un modul cu ajutorul căruia se pot trimite email-uri simplu,

folosind protocolul SMTP;

phaser este biblioteca jocului.

17

2 Dezvoltarea aplicației client și al jocului

Folosind un generator Yeoman pentru un start mai rapid cum am specificat și în Capitolul

1, structura fișierelor și a folderelor a fost creată automat folosind cele mai bune practici pentru

dezvoltarea jocului. Se poate observa în imaginea de mai jos structura folderelor și a fișierelor:

Figura 6: Structura fișierelor

assets – aici se află toate imaginile si fișierele de configurare JSON necesare pentru

aplicație;

build – după rularea comenzii npm run build aici se vor găsi toate fișierele care

trebuie urcate în producție pentru rularea aplicației;

css – folderul cu toate fișierele CSS;

node_modules – folderul cu toate modulele instalate de NPM (ex: phaser, socket.io,

express etc);

server – fișierele javascript pentru partea de server a aplicației;

src – fișierele javascript pentru partea de client care include codul jocului;

index.html – primul fișier accesat în momentul în care un browser se conectează la

aplicație;

package.json – conține atât date despre aplicație (ex: nume, versiune) cât și numele

și versiunea pentru toate modulele și bibliotecile necesare ca aplicația să ruleze.

Pentru a începe procesul de dezvoltare al jocului trebuie să înțelegem mai întâi câteva

caracteristici de bază a bibliotecii Phaser.

18

Phaser este o bibliotecă pentru jocuri HTML5 care ajută dezvoltatorii la crearea de jocuri

foarte complexe, într-un timp cât mai scurt, singura cerință a browser-ului este să suporte tag-ul

HTML canvas.

Inițial canvas-ul a fost creat de Apple, în 2004, pentru a putea implementa widget-urile din

dashboard și pentru a suporta grafica în browser-ul Safari. Ulterior a fost adoptat de Firefox, Opera

și Google Chrome, pentru ca în prezent, să facă parte din specificațiile HTML5 și să fie folosit în

interiorul unui document HTML cu scopul de a desena folosind JavaScript. (9)

Se poate vedea cum prinde totul viață în următoarele linii de cod din fișierul main.js,

folderul src:

const game = new Phaser.Game(1024, 608, Phaser.AUTO, 'wartank-game');

game.state.add('login', require('./states/login'));

game.state.add('menu', require('./states/menu'));

game.state.add('menu-select', require('./states/menu-select'));

game.state.add('multiplayer', require('./states/multiplayer'));

game.state.add('game', require('./states/game'));

game.state.start('login');

Secțiune de cod 2: Codul din main.js

În prima linie de cod se crează o instanță a obiectului Phaser.Game ce se atribuie unei

constante numită game.

Primii doi parametri sunt lățimea și înălțimea elementului canvas, în acest caz, 1024 x 608

pixeli. Aceasta este dimensiunea în care va fi afișat jocul, însă acesta poate fi mult mai mare. Al

treilea parametru poate fi Phaser.CANVAS, Phaser.WEBGL sau Phaser.AUTO, cel din urmă fiind

contextul de randare pe care doresc să îl utilizez. Parametrul recomandat este Phaser.AUTO care

încearcă în mod automat să utilizeze WebGL, dar în cazul în care browser-ul sau dispozitivul nu

acceptă, va folosi canvas. Cel de-al patrulea parametrul este id-ul elementului DOM în care este

inserat elementul canvas.

O funcționalitate interesantă pe care o are Phaser este managerul de stări (en: states

management). Astfel dacă vrei să împarți jocul în blocuri de cod sau interfețe, pe fiecare dintre

aceste interfețe o poți dezvolta ca o stare (en: state) și la fiecare schimbare de stare se eliberează

memorie, resurse și se gestionează memoria de obiecte nefolosite din aplicație (en: manages

garbage collection) care cu siguranță e un plus.

19

2.1 Interfețele jocului

Jocul este împărțit în 5 stări numite și interfețe: login, menu, menu-select, multiplayer și

game. Primele 4 stări fac parte din meniul jocului, iar game este jocul în sine. Login este starea

care se încarcă automat la accesarea jocului.

2.1.1 Interfața de autentificare și înregistrare

Figura 7: Interfața de autentificare și înregistrare

Această interfață este starea login din managerul de stări Phaser care se încarcă automat la

accesarea aplicației. Aici jucătorul se va putea autentifica sau să-și creeze un cont nou. În cazul în

care jucătorul este deja autentificat, în locul formularului de autentificare îi va apărea numele și un

button de Play pentru a trece la interfața următoare.

La apăsarea butonului LOGIN sunt trimise datele formularului printr-o cerere HTTP de tip

POST la server, folosind funcția jQuery.post() care este o prescurtare a funcției jQuery.ajax() din

biblioteca jQuery.

20

$.post(config.SERVER + '/login', $('.login-form').serialize(), function (data)

{

if(data.success){

Cookies.set('user', data.data, { expires: 7 });

self.displayLoggedIn(data.data.username);

} else {

$('.login-page .error-mess').text(data.message).show();

}

});

Secțiune de cod 3: Cererea HTTP de tip POST pentru autentificare

În funcție de răspunsul primit de la server care este de tip JSON, ori afișăm mesajul de

eroare dacă autentificarea nu a fost realizată cu succes, ori stocăm cheia de acces primită într-un

cookie care expiră în 7 zile și afișăm numele utilizatorului împreună cu butonul de PLAY pentru

trecerea la următoarea interfață.

Figura 8: Autentificare sau înregistrare reușită

Un cookie este un fișier creat de un site web pentru a stoca informații pe calculatorul pe

care îl accesează. Cookie-urile sunt folosite pentru autentificare precum și pentru urmărirea

comportamentului utilizatorului.

Cheia de acces primită este un șir aleatoriu de litere și numere și este salvată într-un cookie

pentru ca jucătorul să nu mai trebuiască să se autentifice de fiecare dată când accesează jocul de pe

același calculator pentru o perioadă de timp. La fiecare accesare verificăm dacă există o cheie pe

browser-ul utilizatorului, iar dacă există, o trimitem server-ului să vedem dacă acea cheie este

validă și cărui jucător îi aparține.

21

Înregistrarea jucătorului se face prin accesarea formularului de înregistrare, unde datele sunt

transmise către server aproape la fel ca la autentificare, iar după validarea lor aceleași acțiuni sunt

luate în funcție de răspunsul primit.

În spatele panoului de autentificare și înregistrare rulează un “demo” identic cu cel din

interfața de joc, doar cu câteva tancuri controlate de calculator.

2.1.2 Interfața meniu

Figura 9: Interfața meniu

Sursa imaginii mecanicului de tanc - https://www.artstation.com/artwork/qQn8a

În interfața meniu, care corespunde cu starea menu, nu avem decât câteva imagini de design

și două butoane în care sunt prezentate cele două moduri în care poate fi jucat jocul, și anume

modulul singleplayer și cel multiplayer.

Phaser are câteva funcții rezervate care sunt executate la anumite puncte din joc și ar trebui

să conțină diferite tipuri de cod. Mai jos este o listă cu funcțiile folosite pe parcursul proiectului:

init() - Este prima funcție apelată la încărcarea unei interfețe. Este apelată înainte

de preload(), create() sau oricare altă funcție. În aceasta sunt primite ca parametru

variabilele trimise din alte interfețe.

22

preload() - Această funcție este folosită pentru încărcarea imaginilor și restul

datelor necesare jocului.

create() - Este apelată după ce funcția preload() s-a terminat de executat și de

încărcat. Aici putem să poziționăm în joc imaginile aduse în fucția preload() și să

pregătim alte date.

update() - Această funcție este apelată la fiecare cadru, deci pe un calculator, asta

ar însemna aproape de 60 de ori pe secundă. Aici putem muta jucătorul pe hartă sau

verifica obiectele de coliziune. Aici se întâmplă cele mai interesante lucruri.

Pentru că această interfață are cel mai puțin cod, voi prezenta structura de bază a unei stări

numită de mine interfață pentru că fiecare este diferită prin conținut și funcționalitate.

function Menu() {

}

Menu.prototype.init = function(){

}

Menu.prototype.preload = function () {

var game = this.game;

game.load.image('tank-driver', 'assets/tank-driver-resize.jpg');

game.load.image('logo', 'assets/menu-logo.png');

game.load.image('menu-single', 'assets/menu/menu-single.png');

game.load.image('menu-multi', 'assets/menu/menu-multi.png');

}

Menu.prototype.create = function () {

var game = this.game;

$('.login-page').remove();

$('.backdrop').remove();

game.add.sprite(0, 0, 'tank-driver');

game.add.sprite(430, 45, 'logo');

game.add.button(510, 200, 'menu-single', this.menuButtonAction, this);

game.add.button(510, 300, 'menu-multi', this.menuButtonAction, this);

};

Menu.prototype.update = function(){

}

Menu.prototype.menuButtonAction = function(){

this.game.state.start('menu-select', true, false, arguments[0].key);

};

module.exports = Menu;

Secțiune de cod 4: Codul din interfața meniu

Indiferent pe care din cele două butoane din meniu se dă clic, următoarea stare este menu-

select, unde este configurat jocul înainte de începerea lui prin alegerea tancului și a hărții.

23

2.1.3 Interfața de configurare a jocului

Figura 10: Interfața de configurare a jocului

Această interfață corespunde cu starea menu-select. Aici găsim meniul de unde putem

selecta tancul și harta pe care vrem să jucăm.

Cele patru tipuri de tancuri se deosebesc atât prin design, cât și prin diferite caracteristici,

ca de exemplu unul va avea mai mult blindaj, dar va fi mai încet, altul va avea viteză mai mare, dar

va lua mai puțin din viață etc. Mai jos avem o poză cu tipurile de tancuri și caracteristicile acestora.

Figura 11: Tipurile de tancuri

Sursa pentru design-ul tancurilor - http://opengameart.org/content/top-down-painted-tanks

24

Hărțile diferă prin mărime sau prin tipul și așezarea obstacolelor. Fiecare hartă poate avea

mai multe tipuri de obstacole, care se pot distruge mai greu sau mai ușor și au un număr predefinit

de inamici pentru jocul singleplayer.

La apăsarea butonului PLAY, în funcție de modul de joc selectat în meniul anterior,

jucătorul este trimis spre interfața de joc, unde începe jocul, dacă a ales modul singleplayer sau

spre interfață de așteptare a jucătorilor, numită și lobby, dacă a ales modul multiplayer.

2.1.4 Interfața de așteptare a jucătorilor

Figura 12: Interfața de așteptare a jucătorilor

Această interfață există doar pentru modul de joc multiplayer și este starea multiplayer din

managerul de stări Phaser, ce în diferite jocuri se mai numește și lobby. Pe scurt este un spațiu unde

jucătorii se adună și după ce toate cerințele sunt îndeplinite, jocul poate fi pornit de cel care creează

jocul.

În majoritatea jocurilor, lobby-ul este creat de un jucător care are control asupra lui. În jocul

acesta, proprietarul lobby-ului alege harta pe care o să joace și cu cine vrea să joace prin trimiterea

unui link unic celorlalte persoane.

La accesarea acestei interfețe, o conexiune WebSocket se inițializează cu serverul și se va

emite evenimentul newGame, împreună cu id-ul și numele de utilizator al jucătorului, cu tancul și

harta selectate și statusul dacă e pregătit sau nu să înceapă jocul.

25

Server-ul generează un token1 și creează o cameră folosind token-ul ca id al camerei pe care

îl trimite înapoi proprietarul lobby-ului. Conceptul de cameră este construit în Socket.IO, dar mai

multe informații despre acest concept veți putea găsi în capitolul despre dezvoltarea aplicației

server.

La primirea tokenului, acesta este afișat sub forma unui link, de exemplu

http://wartank.com/?gametoken=3f0baf6697 care poate fi trimis altor jucători pentru a se alătura

lobby-ului.

La accesarea acestui link, jucătorul trebuie să se autentifice sau să se înregistreze și va fi

trimis direct spre interfață de lobby, unde de asemenea se va crea o conexiune WebSocket. În

schimb, în loc de emiterea evenimentului de newGame, se va emite evenimentul joinGame, cu

datele utilizatorului, iar în loc de harta selectată se va trimite token-ul, ca serverul să știe în ce

cameră să îl atașeze.

socket.on('connect', function(){

console.log('WebSocket succesfully connected!');

if(self.queryUrl.gametoken){

socket.emit('joinGame',{

user : {

id: userData.id,

username: userData.username,

tank: userData.tank,

ready: false

},

gameToken: self.queryUrl.gametoken.replace('#', '')

});

} else {

socket.emit('newGame', {

user: {

id: userData.id,

username: userData.username,

tank: userData.tank,

ready: true

},

map: self.selectedMap

});

}

});

Secțiune de cod 5: Codul pentru inițializarea unei conexiuni la server și emitere a evenimentelor

de tip newGame si joinGame

Aici jucătorii vor putea să-și schimbe tancul cu care vor juca sau să-și schimbe statusul

când sunt pregătiți să înceapă jocul. La fiecare schimbare se vor emite diferite evenimente către

1 Este o cheie sau un șir unic de cuvinte și litere.

26

server, care la rândul lui va emite către ceilalți clienți din aceeași cameră sau lobby și datele sunt

actualizate automat în interfață.

După ce toți jucătorii au intrat și sunt pregătiți să înceapă jocul, proprietarul lobby-ului

apasă pe butonul PLAY care va emite un eveniment de începere a jocului, ce va fi transmis tuturor

jucătorilor din cameră și vor fi trimiși spre interfața de joc.

2.1.5 Interfața de joc

Figura 13: Interfața de joc

Aici este interfața de joc numită și starea game unde se joacă jocul propriu-zis. Structura

este asemănătoare cu cea din Secțiune de cod: 4.

Pentru început este apelată funcția init() în care atribuim unor variabile publice parametrii

primiți de la interfața precedentă, tipul tancului, harta și dacă este modul de joc multiplayer se mai

trimite și obiectul cu conexiunea la websocket ca să nu mai creem conexiunea încă odată. Apoi

este apelată funcția preload(), unde sunt încărcate de pe server sau din cache2 toate imaginile,

sprite-urile3 și fișierele JSON de configurație a tancurilor și a hărților.

2 Este o colecție de date ce sunt o copie la indigo a valorilor originale, unde operația de aducere este mai costisitoare. 3 Este o colecție de imagini puse într-o singură imagine.

27

După ce toate fișierele au fost încărcate în funcția preload(), este apelată funcția create(),

unde ne folosim de datele și imaginile încărcate pentru a desena harta și tancurile.

Mai jos am creat o instanță pentru unul din cele trei sisteme de fizică, furnizate de către

biblioteca Phaser, fiecare cu propria lui utilizare, în care avem avantaje și dezavantaje:

game.physics.startSystem(Phaser.Physics.ARCADE);

Sistemul de fizică Arcade (Phaser.Physics.ARCADE) este probabil cel mai folosit de către

dezvoltatorii de jocuri pentru că se găsesc cele mai multe tutoriale și este cel mai simplu și mai

rapid sistem în ceea ce privește performanța. Este un sistem care permite accelerația, viteza,

coliziunea și multe altele, dar la un nivel rudimentar deoarece totul este văzut ca un dreptunghi.

Sistemul de fizică Ninja (Phaser.Physics.NINJA) este un pic mai complex decât sistemul

de fizică Arcade. Cea mai mare diferență între cele două sisteme este că permite coliziunea cu

pante. Folosind acest sistem se poate crea o platformă cu dealuri și rampe, pe când sistemul Arcade

este unul destul de pătrățos.

Sistemul de fizică P2 (Phaser.Physics.P2JS) este cel mai complex și mai realistic, dar

consumă și cele mai multe resurse. Folosind acest sistem se pot crea arcuri, pendule și altele.

Un obiect din joc, de exemplu un sprite, poate aparține doar unui sistem de fizică, dar pot

exista obiecte cu sisteme diferite într-un joc. De exemplu se poate avea sistemul de fizică P2 pentru

gestionarea unui teren construit cu ajutorul poligoanelor, peste care trece un vehicul, în timp ce

trage cu gloanțe ce se folosesc de sistemul de fizică mult mai rapid Arcade.

Pentru desenarea hărții în funcția create() trebuie să creem o instanța a obiectului Map din

/src/prefabs/map.js, iar în constructorul lui se declară proprietatea mapData cu JSON-ul de

configurare a hărților și se setează limitele lumii jocului.

28

{

"name": "Default",

"backgroundColor": "#2d2d2d",

"backgroundImage": null,

"width" : 1024,

"height": 608,

"data" : [

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0],

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0],

[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,

16, 16, 0, 0],

[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,

16, 16, 0, 0],

[0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 0, 1, 1, 1, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 1, 1, 1, 0, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,

0, 0],

[16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 0,

0, 0, 0, 0, 16],

[16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 16, 16, 16, 16, 0, 0, 0, 0, 0,

0, 0, 0, 0, 16],

[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 0, 1, 1, 1, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 1, 1, 1, 0, 0, 0, 8, 0,

0, 0],

[0, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 0,

0, 0],

[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,

16, 16, 0, 0],

[0, 0, 16, 16, 16, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 16,

16, 16, 0, 0],

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0],

[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

0, 0],

[0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0,

0, 0]

],

"health": {

"obstacle1" : 100,

"obstacle8" : 1000,

"obstacle16" : 250

},

"enemiesNr": 5

}

Secțiune de cod 6: JSON de configurare a hărții

WarTank este un joc bazat pe plăci (en. Tile-based video game), asta înseamnă că aria unui

joc este formată din mici forme dreptunghiulare, pătrate care sunt asemănătoare unei piese de

puzzle ce formează o imagine mult mai mare.

În acest joc, harta este formată din plăci pătrate cu dimensiune de 32x32 pixeli. Pentru

așezarea obstacolelor pe hartă am folosit o matrice bidimensională; fiind un joc 2D, sistemul de

coordonate este unul bidimensional format din două axe x și y.

29

Așadar în JSON-ul de configurare a hărții, în data avem matricea bidimensională ce este

plină cu numere întregi, care dacă sunt diferite de 0 indică că acolo trebuie poziționat un obstacol

cu id-ul lui.

Map.prototype.createObstacles = function () {

var obstacles = this.game.add.physicsGroup(Phaser.Physics.ARCADE);

for (var x = 0; x < this.mapData.data.length; x++) {

for (var y = 0; y < this.mapData.data[x].length; y++) {

if (this.mapData.data[x][y]) {

var c = obstacles.create(y * this.blockSize, x * this.blockSize,

'obstacle', this.mapData.data[x][y]);

c.maxHealth = c.health = this.mapData.health['obstacle' +

this.mapData.data[x][y]];

c.body.immovable = true;

//c.anchor.setTo(0.5, 0.5);

}

}

}

return obstacles;

}

Secțiune de cod 7: Așezarea obstacolelor pe hartă

Atribuim variabilei obstacles un obiect al jocului de tip Group care este un container pentru

obiectele de afișare și îi setăm un sistem fizic de tipul Arcade. Apoi iterăm matricea bidimensională

și când întâlnim un număr întreg diferit de 0 creăm un obiect de tipul Sprite care așează obstacolul

pe poziția lui, apoi îi setăm proprietățile health, maxHealth și immovable. Fiecare tip de obstacol

se distruge mai ușor sau mai greu, în funcție de ce valoare are la viață și nu trebuie să se miște când

este atins de un jucător.

La crearea obiectului de tipul Sprite pentru poziționarea obstacolului sunt trimiși următorii

parametri:

x – poziția pe axa x în pixeli (numărul plăcii pe axa x * dimensiunea plăcii);

y – poziția pe axa y în pixeli;

key – indexul pus la încărcarea imaginii sau a sprite-ului în funcția preload();

frame – în cazul în care a fost încărcat un sprite, aici este indexul imaginii dorite.

După ce am desenat harta jocului, tot în funcția create() trebuie să adăugăm tancul

jucătorului și cel al inamicilor, prin creare unei instanțe a obiectului Tank. Se generează aleatoriu

o direcție și o poziție, pe care să nu existe un obstacol ca să poată fi poziționat tancul, iar pentru

asta ne folosim de o metodă din obiectul map.

30

Map.prototype.spawnTankLocation = function () {

var location = {x: 0, y: 0};

do {

location.y = this.game.rnd.between(0, this.mapData.data.length - 1);

location.x = this.game.rnd.between(0, this.mapData.data[0].length - 1);

}

while (this.mapData.data[location.y][location.x] !== 0);

return {

x: this.blockSize * location.x + 18,

y: this.blockSize * location.y + 18,

direction: this.game.rnd.pick(['right', 'left', 'up', 'down'])

};

}

Secțiune de cod 8: Căutarea unei poziții fără obstacol în matricea bidimensională

În obiectul Tank se declară o proprietate care conține toate setările tancului ce sunt calculate

din JSON-ul de configurare tancuri. În JSON avem valorile maxime admise pentru viața, rata de

foc, viteza, viteza proiectil și puterea de distrugere, iar pentru fiecare tip de tanc avem un procent

din valoarea maximă.

Mișcarea tancului se face prin apăsarea tastelor de navigare (cele cu săgeți) de pe tastatură

și totul este controlat în funcția update(), care este apelată de 60 de ori pe secundă. Aici se verifică

de fiecare dată dacă o tastă este apăsată și i se oferă tancului accelerație în direcția dorită.

this.tank.body.velocity.x = 0;

this.tank.body.velocity.y = 0;

if (cursors.left.isDown) {

this.tank.body.velocity.x = -this.tankSettings.speed;

this.tankDirection = 'left';

this.tank.angle = 180;

}

else if (cursors.right.isDown) {

this.tank.body.velocity.x = this.tankSettings.speed;

this.tankDirection = 'right';

this.tank.angle = 0;

}

else if (cursors.up.isDown) {

this.tank.body.velocity.y = -this.tankSettings.speed;

this.tankDirection = 'up';

this.tank.angle = 270;

}

else if (cursors.down.isDown) {

this.tank.body.velocity.y = this.tankSettings.speed;

this.tankDirection = 'down';

this.tank.angle = 90;

}

Secțiune de cod 9: Codul care se ocupă de mișcarea și direcția tancului

31

Pentru detectarea coliziunii dintre tanc și obstacol am folosit funcția collide() din sistemul

de fizică Arcade al jocului, care se poate folosi atât între simple instanțe de obiecte ale jocului, cât

și între o instanță de obiect și un grup de obiecte.

Sistemul de fizică face totul prea ușor, trebuie să adăugăm doar o simplă linie de cod în

funcția update().

this.game.physics.arcade.collide(this.tank, this.map.obstacles, this.tankHitObstacle,

null, this);

Secțiune de cod 10: Sistemul de detectare al coliziuni între tanc și obstacole

Primii doi parametri sunt obiectele jocului care trebuie verificate pentru detectarea

coliziunii, iar al treilea este funcția apelată când coliziunea este detectată.

2.2 Modul singleplayer și aplicarea tehnicilor de inteligență artificială

Într-un sens larg, cele mai multe jocuri încorporează o anumită formă de inteligență

artificială (IA), adică nu este acea formă academică, ci mai degrabă un amestec de tehnici care în

principal sunt preocupate de crearea iluziei de inteligență artificială.

De exemplu, dezvoltatorii s-au folosit de inteligența artificială ani de zile, pentru a da

aparent inteligență nenumăratelor caractere de joc, de la fantomele din clasicul joc Pac-Man până

la boții4 din jocul de acțiune cu împușcături Counter-Strike.

Unii dezvoltatori consideră că sarcinile cum ar fi căutarea unui traseu aparțin de inteligența

artificială a jocului. Steven Woodcok a raportat în “2003 Game Developer’s Conference AI

Roundtable Moderator’s Report” că unii dezvolatori cred că și detectarea de coliziune face parte

din inteligența artificială a unui joc, ceea ce arată că sunt o mulțime de interpretări exagerate.

Vom merge pe ideea că inteligența artificială în jocuri include totul, de la simpla urmărire

și evadare, la modelul de mișcare și multe altele.

4 Este un jucător controlat de calculator folosind inteligența artificială.

32

Ciclu de decizii al caracterului non-jucător (en: non-player character) execută trei pași în

mod constant: (10)

percepe - acceptă informații despre mediul înconjurător, hartă, inamic;

gândește - evaluează informațiile percepute și plănuiește conform regulilor;

acționează – execută planul;

Alții ar putea argumenta că această abordare este mult prea simplă și prin urmare ar putea

fi nepotrivită pentru crearea unei experiențe de joc plăcută, dar nu este așa. De fapt, jocurile video

nu au nevoie de caractere non-jucător care sunt la fel de sofisticate și capabile ca și jucătorii umani

care joacă. Jocurile sunt menite să fie distractive, iar sistemul de inteligență artificială nu ar trebui

să fie prea bun, asta ar face jocul imposibil de câștigat. În schimb, ar trebui să permită jucătorului

să câștige jocul în moduri interesante și provocatoare.

În general tehnicile de comportament în inteligența artificială pentru jocuri sunt de două

feluri: deterministe și nedeterministe. (11)

Determinist

Comportamentul determinist este specific și previzibil. Nu există nici o incertitudine. Un

exemplu simplu de comportament determinist este un algoritm simplu de urmărire. Se poate

programa un caracter non-jucător să se îndrepte spre un anumit punct țintă prin avansarea de-a

lungul axelor x și y până când coordonatele caracterului x și y coincid cu locația țintă.

Nedeterminist

Comportamentul nedeterminist este opusul comportamentului determinist, deoarece are un

grad de incertitudine și este oarecum imprevizibil. Un exemplu de comportament nedeterminist

este un caracter non-jucător care învață să se adapteze la tactică de luptă a unui jucător. O astfel de

învățare ar putea folosi o rețea neuronală, tehnică Bayesion sau un algoritm genetic.

În inteligența artificială tehnicile deterministe sunt cele mai folosite în devoltarea unui joc.

Aceste tehnici sunt previzibile, ușor și rapid de implementat, înțeles și testat. Deși sunt foarte

utilizate, metodele deterministe plasează sarcina de a anticipa și de a programa toate scenariile

explicite pe umerii dezvoltatorilor.

33

Metodele deterministe nu facilitează învățarea sau evoluția și după ce joci puțin, aceste

comportamente tind să devină previzibile. Acest lucru limitează perioada în care un joc este

distractiv și începe să devină plictisitor.

Probabil cea mai utilizată tehnică ce este folosită la scară largă în inteligența artificială este

trișarea. De exemplu, într-un joc ca al meu, calculatorul are acces la toate informațiile despre tancul

meu, de la poziția lui, tipul tancului, cât mai am din viață și până la direcția în care mă îndrept și

cu ce viteză. Trișarea în acest mod îl ajută pe caracterul non-jucător să aibă un avantaj în fața

inteligenței jucătorului uman, dar aceasta poate fi și ceva rău. Dacă este prea evidentă pentru

jucătorul uman această trișare, atunci el va crede că eforturile lui sunt zadarnice și își va pierde

interesul. Așa cum am explicat și mai sus, sistemul de inteligență artificială nu trebuie să fie prea

bun și acest mod de trișare pentru caracterele non-jucător trebuie să fie echilibrat destul cât să

creeze o suficientă provocare pentru jucător, dar îndeajuns cât să păstreze jocul interesant și

distractiv.

Clasicul joc Pac-Man îl face pe jucător să creadă că inamicii îl vânează, că fantomele sunt

un grup inteligent. De fapt, această percepție de grup inteligent este doar o iluzie. Pentru a fi siguri

că fantomele nu urmează toate același drum prin labirint și pentru a le oferi o personalitate

individuală, fiecăreia îi este oferită o variație ușor diferită a aceluiași algoritm, care este o selecție

foarte simplă de direcție de fiecare data când fantomele ajung la o intersecție în labirint.

În cazul în care se ajunge la o intersecție, fantoma trebuie să decidă dacă ar trebui să

schimbe direcția sau nu. Uneori fantoma își schimbă direcția ca să se miște spre jucătorul uman,

altădată alege o direcție aleatorie. Pentru a realiza acest comportament ușor diferit pentru fiecare

dintre fantomele create este folosit un sistem aleatoriu ce utilizează factori de ponderare unici

pentru fiecare dintre ele.

Astfel o fantomă se poate deplasa într-o direcție aleatorie 75% din timp și în direcția

jucătorului în celelalte 25% din cazuri, atunci când aceasta ajunge la o intersecție. O altă fantomă

ar avea alegerea aleatorie a direcției cu o pondere de 50% din timp și așa mai departe.

Rezultatul acestei metode simple este o personificare a fiecărei fantome de către jucător

(prin proiecție de subconștient) și va percepe comportamentul unei fantome ca cel al unui jucător

inteligent.

34

Diferența dintre mișcarea fantomelor în jocul Pac-Man nu este foarte mare față de tancurile

non-jucător din WarTank și am încercat deasemenea să le ofer câte o personalitate diferită pentru

a creea iluzia de inteligență.

Deoarece hărțile nu sunt sub forma unui labirint și tancurile au spațiu de mișcare mai mare,

direcția nu se poate schimba doar la întâlnirea unei intersecții, așa că am decis ca fiecare tanc să

aleagă o perioadă de timp aleatorie în care să verifice dacă să schimbe direcția sau nu. Așadar, la

fiecare schimbare de direcție, tancul își alege în mod aleatoriu un nou interval de timp din cele 4

intervale predefinite: 1000, 1500, 2000 și 2500 de milisecunde.

La alegerea direcției de mers am folosit un algoritm simplu de urmărire. Utilizând tehnica

de trișare, în care calculatorul știe poziția mea, am programat tancurile inamice să se îndrepte spre

mine prin avansarea de-a lungul axelor x și y până când una dintre axe este egală cu una din axele

mele. În acel moment tancul non-jucător se va opri, își va schimba direcția spre tancul cu jucător

uman și va continua să tragă până când tancul jucător se va mișca și nici una dintre axe x și y nu va

mai coincide.

Totuși am preferat că tehnica de trișare prin care calculatorul știe poziția mea să nu fie prea

evidentă, așa că în funcție de cadranul în care se află tancul jucătorului am decis să aleagă una din

cele două direcții și nu pe cea mai rapidă.

Figura 14: Cele patru cadrane ale unui sistem de coordonate carteziene

De fiecare dată când se alege o nouă direcție se verifică să nu fie blocată de un obstacol, iar

dacă este blocată, se verifică altă direcție până când se găsește una care nu este blocată de nici un

obstacol.

35

Se mai verifică de fiecare dată când este apelată funcția update(), adica de aproape 60 de

ori pe secundă, dacă tancul non-jucător este pe aceeași axă x sau y cu tancul jucătorului uman,

indiferent dacă timpul aleatoriu nu a trecut pentru o reverificare a direcției, tancul se oprește, se

îndreaptă spre tancul jucătorului uman și trage.

Tank.prototype.updateAIEnemyTank = function (playerTank) {

var directions, tankSpeed = this.tankSettings.speed;

this.game.physics.arcade.collide(this.tank, this.map.obstacles, this.tankHitObstacle, null,

this);

this.game.physics.arcade.overlap(this.projectiles, this.map.obstacles,

this.projectileHitObstacle, null, this);

this.tankMapLocation = this.map.spriteLocationOnMap(this.tank);

if(playerTank) {

directions = this.map.directionFromOneTankToAnother(this.tankMapLocation,

playerTank.tankMapLocation)

} else {

directions = [this.game.rnd.pick(['right', 'left', 'up', 'down']),

this.game.rnd.pick(['right', 'left', 'up', 'down'])];

}

if (this.game.time.now > this.enemyData.nextCheckDirection) {

this.enemyData.nextCheckDirection = this.game.time.now + this.game.rnd.pick([1000, 1500,

2000, 2500]);

this.tankDirection = this.map.checkAndGetDirections(directions, this.tankMapLocation);

this.tank.angle = this.tankAngleByDirection[this.tankDirection];

} else {

if(directions.length == 1){

this.tankDirection = directions[0];

this.tank.angle = this.tankAngleByDirection[this.tankDirection];

tankSpeed = 0;

}

}

this.tank.body.velocity.x = 0;

this.tank.body.velocity.y = 0;

if (this.tankDirection == 'left') {

this.tank.body.velocity.x = -tankSpeed;

}

else if (this.tankDirection == 'right') {

this.tank.body.velocity.x = tankSpeed;

}

else if (this.tankDirection == 'up') {

this.tank.body.velocity.y = -tankSpeed;

}

else if (this.tankDirection == 'down') {

this.tank.body.velocity.y = tankSpeed;

}

if(this.tank.alive){

this.fireProjectile();

}

}

Secțiune de cod 11: Codul pentru mișcarea tancurilor controlate de calculator

36

2.3 Modul multiplayer și comunicarea în timp real dintre client și server

Jocurile sunt destul de dificil de realizat. Senzația jocului trebuie să fie una plăcută, fizica

jocului să fie una lină, coliziunea să fie corectă și controlul să fie precis, iar toate aceste lucruri

necesită deja o cantitate foarte mare de muncă. Adăugarea unei componente multiplayer face acest

lucru mult mai complex, deoarece acum jucătorul trebuie să fie informat cu privire la acțiunile altor

jucători.

Modul în care un grup de jucători vor putea juca împreună este unul destul de simplu. Cum

am explicat și în subcapitolul 2.1.4 Interfața de așteptare a jucătorilor, jucătorul care accesează

jocul de pe un link care are un token de joc unic se alătură jocului creat de jucătorul care a generat

și a trimis link-ul la ceilalți jucători. Tot acest sistem funcționează ca un simplu sistem de lobby ce

este ilustrat mai jos.

Figura 15: Un sistem simplu de lobby și de comunicare client-server

37

La crearea conexiunii websocket în lobby, fiecare client va trimite token-ul sau cheia de

acces al user-ului pentru a demonstra că acea conexiune a fost făcută de un user autentificat.

Logica jocului pornește când toți jucătorii din lobby sunt pregătiți să înceapă și proprietarul

lobby-ului apasă pe butonul PLAY care va emite un eveniment de începere. După încărcarea

jocului, fiecare jucător va emite un eveniment gameLoaded, adică jocul a fost încărcat și sunt

pregătiți să înceapă; după ce toți jucătorii vor trimite acest eveniment, ei vor asculta evenimentul

startGame în care se vor primi toate pozițiile pe hartă a tancurilor și vor putea începe jocul.

În timpul jocului, clientul va emite următoarele evenimente prin websocket:

respawn – după ce tancul este distrus, la apăsarea tastei SPACE se va emite acest

eveniment în care clientul va cere să aducă la viață tancul distrus și să îl

repoziționeze pe hartă, iar server-ul va trimite această informație tuturor clienților

din camera jocului;

fire – acest eveniment se va emite tot la apăsarea tastei SPACE, dar când tancul este

în viață; evenimentul de tragere, care trimite la server poziția, direcția, accelerația

și cât din viață are tancul, va trimite mai departe la restul clienților această

informație;

movement – acest eveniment se va emite de fiecare dată când se va apăsa sau se va

elibera una din tastele de navigare5, și va trimite de fiecare dată aceleași informații,

ca și în cazul evenimentului fire, adică poziția, direcția, accelerația și cât din viață

are tancul.

După ce jocul a fost început, clientul va asculta numai evenimentul updatePlayer, unde vor

veni actualizările de la ceilalți jucători.

Când vine vorba de un joc multiplayer în timp real, logica în sine a jocului trebuie rulată

atât pe server, cât și pe client, deoarece serverul trebuie să fie autoritatea cu privire la starea de joc

în orice moment.

În cazul de față, logica jocului este rulată doar pe client și server-ul primește, verifică și

trimite informațiile celorlalți jucători.

5 Sunt tastele care au desenate pe ele săgeți.

38

Deoarece sunt trimise și primite informații despre tanc doar când se schimbă direcția și

accelerația, trebuie să rulăm animația tancului în care își schimbă poziția pe partea de client, acest

lucru fiind un sistem de prezicere a clientului, iar la următoarea actualizare trebuie reparate foarte

ușor greșelile de predicție, pentru a se păstra pozițiile tancurilor sincronizate, astfel rezolvându-se

problema timpului mare de răspuns în care se pot trimite informațiile.

Ce se poate întămpla dacă nu sunt reparate greșelile de predicție? De exemplu:

Clientul apasă tasta de navigare spre stânga, anunță serverul și în același timp începe

să mute tancul pe hartă;

După 100 de milisecunde informația ajunge pe server, unde este procesată și puțin

mai târziu o trimite celuilalt client;

La celălalt client informația ajunge după 150 de milisecunde, timp în care va începe

să mute tancul jucătorului inamic pe hartă.

Deja există o diferență de 250 de milisecunde care este insesizabilă, dar după câteva minute

de joc, tancurile nu vor mai fi sincronizate.

39

3 Dezvoltarea aplicației server

Aplicația server este formată din două fișiere localizate în folderul server, un fișier în care

se află datele de conectare la baza de date, url-ul aplicației client și url-ul aplicației server numit

config.js și main.js în care se află tot codul specific aplicației server.

Aplicația server se poate porni prin rularea comenzii node main.js în directorul server al

aplicației, dar am preferat să folosesc biblioteca pm2, care este un manager de procese pentru

aplicațiile Node.js. Astfel îmi permite să țin aplicația server tot timpul deschisă, iar în caz de eroare,

îmi salvează în log-uri ce s-a întâmplat și restartează aplicația automat. De asemenea, îmi oferă o

modalitate simplă de monitorizare a resurselor consumate de aplicație la cererea mea.

La pornirea aplicației server, în primele linii de cod, sunt instanțiate toate modulele necesare

explicate în subcapitolul 1.3 Arhitectura aplicației.

Următoarele linii de cod sunt doar câteva necesare pentru pornirea serverului folosind

modulul Express și Socket.IO care este atașat la Express, pentru ca amândouă să asculte pe același

port.

// Create a new Express application

var app = require('express')();

// Create an http server with Node's HTTP module and pass it the Express application

var server = require('http').createServer(app);

// Instantiate Socket.IO hand have it listen on the Express/HTTP server

var io = require('socket.io')(server);

// Start server and listen to port 8081

server.listen(8081, function () {

console.log(‘SERVER STARTED’);

});

Secțiune de cod 12: Codul pentru pornirea server-ului pentru cererile HTTP și WebSocket

După cum am explicat și în capitolul 1.3 Arhitectura aplicației, în această configurație

Express este utilizat pentru a trata cererile HTTP care vin în aplicația Node.js. Modulul Socket.IO

se atașează de Express și ascultă pe același port pentru conexiunele WebSocket de intrare.

Express are în componența lui diferite tehnici pe care le putem folosi în momentul în care

o cerere către o anumită pagină web sau o anumită informație este dorită spre vizualizare în

browser. Una dintre aceste tehnici se numește "routing" și are ca principal scop traseul cererii ce

40

vine de la client și care mai apoi este gestionată cu ajutorul diferitelor funcții sau metode, cu scopul

de a-i oferi un răspuns cu un anume conținut browser client. (12)

3.1 Cererile HTTP

Cererea de autentificare /login

La autentificarea clientului prin introducerea numelui de utilizator și a parolei în formular,

pe partea de client se face o cerere la server pentru validarea celor două.

Server-ul verifică dacă numele de utilizator și parola criptată folosind metoda MD5 există

în baza de date. În cazul în care există, dacă contul are peste o săptămâna vechime, verifică dacă

adresă de email este activată, apoi generează un șir unic format din numere și litere numit token,

pe care îl inserează apoi în baza de date și îl trimite clientului împreună cu informațiile lui într-un

JSON.

Dacă contul nu este valid sau adresa de email nu este activată, clientul primește un JSON

cu mesajul de eroare și statusul 0.

Cererea de înregistrare /register

Dacă clientul nu are cont va trebuie să se înregistreze, completând numai un formular ce

conține numele de utilizator, adresa de email și parola. Aceste date sunt apoi trimise server-ului

pentru validare.

Pentru început server-ul validează cele trei date necesare folosind modulul express-

validator:

adresa de email trebuie să fie validă;

numele de utilizator trebuie să conțină numai litere și cifre și să aibă între 3 și 10

caractere lungime;

parola trebuie să conțină și ea numai litere și cifre și să aibă între 4 și 10 caractere

lungime.

Dacă toate aceste date sunt valide, se verifică în baza de date să nu mai existe un utilizator

cu aceeași adresă de email sau același nume. Dacă nu există, se generează două token-uri unice,

41

unul pentru autentificarea utilizatorului și celălalt pentru validarea adresei de email, care se trimite

prin email, apoi se inserează datele în baza de date.

Ulterior, ca la autentificare, se trimit datele utilizatorului dacă totul este în regulă sau

mesajul de eroare, în formatul JSON.

Cererea pentru verificarea token-ului /check-login

La autentificare sau după înregistrare se generează un token unic care este introdus în baza

de date a utilizatorului pentru care a fost generat și apoi îi este trimis.

Aplicația client îl salvează într-un cookie și atâta timp cât accesează aplicația de pe același

calculator și același browser acesta nu mai trebuie să se autentifice de fiecare dată folosind user-ul

și parola. Aplicația verifică dacă în cookie există un token pe care îl trimite apoi serverului pentru

a vedea dacă corespunde cu cel din baza de date.

Cererea pentru activare email /activate/:token

Față de celelalte cereri care au folosit metoda de solicitare de tip POST, aceasta folosește

GET, adică cererea poate fi accesată pur și simplu prin introducerea link-ului în browser.

La înregistrarea utilizatorului pe adresa email, acesta primește un link în care se află un

token unic. La accesarea link-ului, serverul verifică dacă tokenul din link corespunde cu cel al unui

utilizator în baza de date; caz în care i se actualizează în baza de date adresa de email verificată și

este redirecționat către aplicația client.

3.2 WebSockets

WebSocket asigură comunicarea în timp real în două sensuri între un client și un server, și

prin urmare, este extrem de utilă în dezvoltarea jocurilor moderne de tip multiplayer în browser,

care se pot baza pe o conexiune activă tot timpul, cu o latență redusă, permițând transmiterea rapidă

a informațiilor despre jucător și joc.

42

Când un client se conectează la server folosind Socket.IO, este trimis și token-ul pe care l-

a primit la autentificarea pentru validarea conexiunii folosind o funcție intermediară, numită și

middleware.

După autentificare, clientul va emite un eveniment newGame de creare a jocului sau de

alăturare la un joc deja creat joinGame.

La emiterea evenimentului newGame de către client, serverul generează un token, încarcă

fișierul de configurare a hărții, adaugă într-o variabilă de tip obiect datele despre noul joc, creează

o cameră care are ca id token-ul generat și emite clientului un eveniment gameCreated că jocul a

fost creat împreună cu id-ul camerei.

Conceptul de cameră (en: room) este construit în Socket.IO. O cameră va grupa conexiunile

clienților pentru a permite evenimentelor să fie emise numai clienților din cameră. Ele sunt perfecte

pentru crearea de jocuri individuale, folosind un singur server. Fără camere, ar fi mult mai dificil

să ne dăm seama care jucători și gazde ar trebui să fie conectate între ele, în cazul în care mai multe

persoane încearcă să inițieze mai multe jocuri de pe același server.

socket.on('newGame', function(data){

var gameRoomToken = crypto.randomBytes(5).toString('hex');

socket.join(gameRoomToken);

fs.readFile('../html/assets/data/maps/'+data.map+'.json', function(err,

mapData){

if(err){

return socket.emit('alert', {message: 'Map data not loaded!',

redirect: true});

}

gameRooms[gameRoomToken] = {players: [data.user], map: data.map,

mapData: JSON.parse(mapData), started: false, host: data.user.id};

socket.emit('gameCreated', gameRoomToken);

socketsInfo[socket.id] = {

gameRoomToken: gameRoomToken,

user: data.user

};

});

});

Secțiune de cod 13: Funcția evenimentului de creare joc

Pe partea de server, la emiterea evenimentului joinGame de către client împreună cu token-

ul jocului, obiectul jocului este actualizat cu un nou jucător și este plasat în camera de joc, apoi

este emis evenimentul updatePlayers cu o listă actualizată cu toți jucători la toate conexiunile din

cameră.

43

Pentru că sintaxa este puțin confuză în Socket.IO, mai jos avem o listă despre cum pot fi

emise evenimentele către clienți:

socket.emit('hello', msg); - evenimentul va fi emis doar către expeditor;

io.to('my room').emit('hello', msg); - se va emite evenimentul către toți clienții din

camera 'my room', inclusiv expeditorului;

socket.broadcast.to('my room').emit('hello', msg); - se va emite către toți clienții

din camera ‘my room’, în afară de expeditor;

io.emit('hello', msg); - se va emite tuturor clienților conectați, indiferent de

cameră;

socket.broadcast.to(otherSocket.id).emit('hello', msg); - mesajul va ajunge doar la

clientul cu id-ul specificat, indiferent de cameră.

3.3 Validarea informațiilor primite prin WebSocket de la jucător

Dezvoltarea jocurilor este o provocare în sine, dar jocurile multiplayer adaugă un set

complet nou de probleme destul de interesante care trebuie tratate. Problemele de bază sunt de

natură umană și fizică.

Cea mai mare problemă pentru jocurile multiplayer este încercarea jucătorilor de a trișa. Ca

dezvoltator, de obicei nu te interesează prea mult dacă jucătorul trișează în modul singleplayer,

acțiunile lui nu îl afectează decât pe el. În acest caz, experiența jocului nu va mai fi așa cum a fost

plănuită, dar din moment ce este jocul lor, au dreptul să-l joace cum vor.

Jocurile multiplayer sunt diferite, totuși. În orice joc competitiv, un jucător care înșeală face

ca experiența jocului să fie una proastă nu doar pentru el, dar și pentru ceilalați jucători. În calitate

de dezvoltator, probabil că doriți să evitați această problemă, din moment ce îi face pe ceilalți

jucători să părăsească jocul.

Există multe lucruri care se pot face pentru a preveni înșelăciunea, dar cel mai important și

probabil singurul cu adevărat semnificativ este simplu, să nu ai încredere în jucător și să crezi tot

timpul că va încerca să trișeze.

44

Una dintre soluții este să rulezi motorul jocului pe server și tot ceea ce se întâmplă sub

controlul său și să-i faci pe clienți să fie doar niște simpli spectatori privilegiați ai jocului. Cu alte

cuvinte, clientul trimite comenzi la server, serverul rulează jocul și va trimite înapoi clienților

rezultatele. Această soluție se numește folosirea server-ului autoritar, pentru că server-ul este

singura autoritate cu privire la ceea ce se întâmplă în joc.

Bineînțeles nici soluția aceasta nu te scapă cu totul de probleme, dar te scapă de majoritatea

lor, care sunt cele mai simple și mai grave. De exemplu, nu aveți încredere în client cu starea de

sănătate a caracterului, poate el va modifica jocul și va spune server-ului că are 1000% la viață, dar

folosind un server autoritat el va ști că mai are doar 10% din viață. De asemenea să nu aveți

încredere în poziția jucătorului pe hartă, pentru că va încerca să se teleporteze sau să se miște mai

repede decât are voie, trimițând coordonate false server-ului.

Server-ul jocului WarTank nu este un server autoritar, pentru că nu rulează motorul jocului.

El se ocupă de primirea datelor de la jucători și trimiterea lor mai departe și de aceea încerc să

protejez jocul de trișori prin verificarea acestor date.

În secțiunea de cod de mai jos este prezentată verificarea datelor la emiterea evenimentelor

de tragere și de mișcare.

Prima dată sunt verificate în amândouă evenimentele dacă viață și viteza tancului trimisă

de client nu depășește valorile maxime admise pentru tipul de tanc selectat.

Apoi la primirea evenimentelor de tragere se verifică rata de tragere, adică timpul scurs

între emiterea evenimentelor nu trebuie să fie mai mică decât cea setată.

Ultima verificare este pentru evenimentul de mișcare, unde sunt comparate datele primite

acum cu cele de la evenimentul anterior. De exemplu dacă tancul se pune în mișcare, este verificată

poziția unde s-a oprit ultima oară să fie egală cu poziția de unde a pornit cu o marjă de eroare de

10 pixeli, apoi la schimbarea direcției de mers se verifică distanță de unde a plecat și unde își

schimbă direcția să nu fie mai mare decât cea calculată în funcție de timpul trecut și viteza tancului.

45

if(userInfo.lastEmmitedData){

// informatiile primite de la client despre viata si acceleratia tancului sa nu le

depaseasca pe cele setate

if(newData.health > tankSettings.health || newData.speed > tankSettings.speed){

cheatDetected = 1;

}

// verificare viteza de tragere

if(type == 'fire'){

if(userInfo.lastEmmitedData.nextFire){

if(userInfo.lastEmmitedData.nextFire > currentTime){

cheatDetected = 2;

}

userInfo.lastEmmitedData.nextFire = currentTime + tankSettings.fireRate;

} else {

userInfo.lastEmmitedData.nextFire = currentTime + tankSettings.fireRate;

}

}

//verificare schimbare de pozitie

if(type == 'movement'){

if(userInfo.lastEmmitedData.speed == 0){

if(Math.abs(userInfo.lastEmmitedData.location.x - newData.location.x) > 10

||

Math.abs(userInfo.lastEmmitedData.location.y != newData.location.y) >

10){

cheatDetected = 3;

}

} else {

var timeElapsed = currentTime -

userInfo.lastEmmitedData.lastMovementUpdate;

var maxPosition = timeElapsed * newData.speed / 1000;

var difference = 0;

if(newData.velocity.x != 0){

difference = _.max([newData.location.x,

userInfo.lastEmmitedData.location.x]) - _.min([newData.location.x,

userInfo.lastEmmitedData.location.x]);

if(difference > maxPosition ||

Math.abs(userInfo.lastEmmitedData.location.y != newData.location.y) > 10){

cheatDetected = 4;

}

} else if(newData.velocity.y != 0){

difference = _.max([newData.location.y,

userInfo.lastEmmitedData.location.y]) - _.min([newData.location.y,

userInfo.lastEmmitedData.location.y]);

if(difference > maxPosition ||

Math.abs(userInfo.lastEmmitedData.location.x != newData.location.x) > 10){

cheatDetected = 4;

}

}

}

userInfo.lastEmmitedData.lastMovementUpdate = currentTime;

}

}

Secțiune de cod 14: Verificarea pe server a datelor venite de la client

46

Concluzii generale

Scopul acestei lucrări a fost acela de a satisface unele curiozități și identificarea

provocărilor întâlnite în dezvoltarea unui joc în care sunt aplicate tehnicile de inteligență artificială,

comunicarea în timp real și de a găsi soluții optime în dezvoltarea lor.

Implementarea clientului a fost mai dificilă decât cea a server-ului, deoarece aici este

motorul jocului unde am folosit biblioteca Phaser, special aleasă pentru multitudinea de exemple

și pentru popularitate.

În dezvoltarea clientului am folosit tehnici deterministe pentru inteligența artificială în

modul singleplayer și comunicarea în timp real prin websocket în modul multiplayer.

Pe partea de singleplayer cea mai mare provocarea a fost să ofer o personalitate diferită

fiecărui tanc non-jucător pentru a crea iluzia de inteligentă și să găsesc un echilibru al sistemului

de inteligență artificială care nu trebuie să fie prea bun, dar îndeajuns cât jocul să fie câștigat într-

un mod provocator și distractiv.

Pe partea de multiplayer provocarea a fost rezolvarea problemelor de latență și de trimitere

a datelor pentru a păstra jocul sincronizat pe toți clienții.

În dezvoltarea server-ului cea mai mare provocare a fost protejarea jocului de trișori prin

verificarea informațiilor primite.

Pe viitor jocul ar putea fi îmbunătățit prin dezvoltarea a unui sistem de inteligență artificială

cu un comportament nedeterminist care se adaptează la tactica de luptă a unui jucător, prin

îmbunătățirea sistemului de protejare a jocului de trișori, prin adăugarea unor servere publice cu

hărți generate automat în funcție de numărul jucătorilor sau apariția pe hartă a unor abilități pentru

a face jocul mult mai distractiv.

47

Bibliografie

1. Battle City (video game). Wikipedia. [Online]

https://en.wikipedia.org/wiki/Battle_City_(video_game). 1.

2. U.S. social gaming development industry from 2012 to 2017. statista.com. [Online]

https://www.statista.com/statistics/242691/revenue-of-the-us-social-network-game-development-

industry/. 2.

3. JS: The Right Way. [Online] http://jstherightway.org/.

4. Node.js. Wikipedia. [Online] https://en.wikipedia.org/wiki/Node.js.

5. Which HTML5 Game Engine is right for you? HTML5 Game Engines. [Online]

https://html5gameengine.com/.

6. Socket.IO. Wikipedia. [Online] https://en.wikipedia.org/wiki/Socket.IO.

7. About Page. MySQL. [Online] https://www.mysql.com/about/.

8. Client- server. Wikipedia. [Online] https://ro.wikipedia.org/wiki/Client-server.

9. Canvas Element. Wikipedia. [Online] https://en.wikipedia.org/wiki/Canvas_element.

10. Andersn, Eike F. Playing Smart - Artificial Intelligence in Computer Games. [Online]

http://eprints.bournemouth.ac.uk/20461/2/zfxCON03_EAndersonText.pdf.

11. AI for Game Developers. [book auth.] David M Bourg Glenn Seemann. s.l. : O'Reilly Media, Inc., July

2004.

12. Expressjs - Notiuni generale. [Online] http://www.arta-web.ro/nodejs/expressjs/.