Journal de bord: création d’une application de création de schéma en HTML5: Jour 4

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12485/developpement/journal-de-bord-creation-dune-application-de-creation-de-schema-en-html5-jour-3

Dans ce billet on va voir plusieurs choses: l’ajout d’un webservice, la possibilité de sélectionner un objet sur le dessin et enfin la possibilité de dupliquer un objet.

Ajout d’un webservice
Il peut être utile de pouvoir générer un schéma à la volée. Ceci va être permis grâce à l’installation d’un webservice.
C’est également l’occasion de montrer comment développer un serveur webservice.

D’abord un fichier webservice.php dans le répertoire public/
Dans celui-ci on inclut le fichier index.php et l’on force le lancement du module de webservice

1
2
$_GET['nav']='webservice::index';
include('index.php');

Ensuite on créé un module webservice dans le répertoire module/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class module_webservice{
   
    public function _index(){
        ini_set("soap.wsdl_cache_enabled","0");

        $oPluginWsdl=new plugin_wsdl;
        $oPluginWsdl->setName('mkdraw');
        $oPluginWsdl->setUrl('http://localhost/mkframework/data/genere/mkdraw/public/webservice.php');
        $oPluginWsdl->addFunction('setContent');
            $oPluginWsdl->addParameter('id','int');
            $oPluginWsdl->addParameter('tObject','string');
            $oPluginWsdl->addParameter('tMenuLayerObject','string');
            $oPluginWsdl->addReturn('return','string');
            $oPluginWsdl->addReturn('ok','int');
           
        if(isset($_GET['WSDL'])) {
            //affichage du wsdl
            $oPluginWsdl->show();
           
        }else {
           
            $oServer = new SoapServer( 'http://localhost/mkframework/data/genere/mkdraw/public/webservice.php?WSDL', array('cache_wsdl' => WSDL_CACHE_NONE));                   
            $oServer->setClass('webservice');
            $oServer->handle();
           
           
        }
        exit;
    }
}
class webservice{
    public function setContent($id,$tObject,$tMenuLayerObject){
         
        $oSchema=model_schema::getInstance()->findById($id);
        $oSchema->tObject=$tObject;
        $oSchema->tMenuLayerObject=$tMenuLayerObject;
        $oSchema->save();
         
        return array('return'=>'test','ok'=>1);
       
    }
}

Ici, à la récupération de deux paramètres que sont le tableau d’objet sérialisé ainsi que le tableau de l’ordre des calques objets.

Possibilité de sélectionner un objet sur le dessin
L’idée c’est de permettre de cliquer sur un objet sur le dessin pour pouvoir l’éditer plutot que de cliquer à droite dans les calques objets.
Pour cela on va afficher un crayon cliquable sur les objets dont on veut permettre cette fonctionnalité: les carrés et bdd

Premièrement, on permet d’afficher un icone de crayon au dessus des objets
Ajout d’une méthode enableEdit sur la classe Data (data.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
enableEdit:function(){
       
        var divEdit=getById('edit_'+this.id);
        if(divEdit){
            divEdit.style.top=(this.y-5)+'px';
            divEdit.style.left=(this.x+10)+'px';
           
        }else{
       
            var sHtml='';
            sHtml+='<div id="edit_'+this.id+'" class="edit">';

            sHtml+='&nbsp;';

            sHtml+='</div>';

            oApplication.addContent('tEdit',sHtml);
        }
    },

note: on ne voit pas dans l’extrait de code le onClic (il a du être filtré), regardez cette methode dans le code via github: https://github.com/imikado/mkdraw/blob/master/public/js/data.js
Qui créé une cellule div avec une action lors du clic pour selectionner l’objet en question.
La question qui se pose ensute c’est quand afficher cette fonctionnalité, je suis parti sur le fait de cliquer sur un calque pour afficher tous les « crayons » des objets sur le calque.

Ensuite on va boucler pour afficher ces boutons d’édition lorsque l’on selectionne un calque.
Editez la méthode selectLayer() de la classe application.js
En ajoutant à la fin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
        //on commence par effacer le div qui contient nos boutons
        var a=getById('tEdit');
        if(a){
            a.innerHTML='';
        }
        //puis en boucle on affiche le bouton si l'objet est un carre ou une bdd
        for(var i=0;i&lt;this.tObject.length;i++){
            if(!this.tObject[i]){ continue; }
            if(this.tObject[i].idLayer==idLayer &amp;&amp; (this.tObject[i].type==&quot;carre&quot; || this.tObject[i].type==&quot;bdd&quot;) ){
               
                this.tObject[i].enableEdit();
            }
           
        }

Permettre de dupliquer un objet
Il est souvent utile lorsque l’on fait un shéma de dupliquer un objet, on va le permettre en ajoutant un bouton dans le formulaire d’édition d’un objet.
Dans la méthode getForm() de data.js

1
2
3
4
if(this.type=='carre' || this.type=='bdd' || this.type=='losange'){
           
            sHtml+='<p></p>';
        }

On ajoute un bouton qui appelera la methode duplicate de la classe Application

Et dans la classe application, cette méthode de duplication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
duplicateObject:function(idObject){
        //on recupere l'objet à dupliquer
        var oTmpData=this.getObject(idObject);
       
        //on cree un nouvel objet
        var oNewData=new Data(oTmpData.type,oTmpData.idLayer);
        //que l'on decale de 10, afin de le voir
        oNewData.x=oTmpData.x+10;
       
        //liste des propriétés à copier
        var tColumn=Array(
                    'strokeStyle',
                    'fillStyle',
                    'width',
                    'height',
                    'from',
                    'comment',
                    'to',
                    'lineWidth',
                    'y',
                    'x2',
                    'y2',
                    'texte',
                    'size',
                    'info',
                    'relativeObject',
                    'relativeX',
                    'relativeY',
                    'textAlign',
                    'strokeStyleText',
                    'fromPosition',
                    'toPosition'
           
                );
       
        //on boucle sur ce tableau pour copier les propriétés
        //sur le nouvel objet
        for(var i=0;i&lt;tColumn.length;i++){
            oNewData[tColumn[i] ]=oTmpData[tColumn[i] ];
        }
        //on demande à l&#039;afficher.
        oNewData.build();
       
        this.addLayerObject(oTmpData.idLayer,oNewData);
       
        this.selectObject(oNewData.id);
       
    },

Voila pour les dernières fonctionnalités ajoutées.
On continue petit à petit à améliorer l'ergonomie et le confort de création de schéma avec cette application

Dépot Github
Le dépot github: https://github.com/imikado/mkdraw

Journal de bord: création d’une application de création de schéma en HTML5: Jour 3

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12470/developpement/journal-de-bord-creation-dune-application-de-creation-de-schema-en-html5-jour-2

Dans ce billet, je vais expliquer la méthode pour enregistrer un schéma et le charger.
Je vais également indiquer les dernières avancées.

Enregistrer un schéma
L’idée est simple: sérialiser les éléments et les enregistrer en base de données.
Pour cela, un bouton « save » qui fait appel à une function save()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function loadSave(){
    //on serialize le tableau tObject
    var stObject=JSON.stringify( oApplication.tObject);
   
    //on enregistre la serialisation dans l'input tObject
    var b=getById('tObject');
    if(b){
        b.value=stObject;
    }
   
    //on fait de meme pour les calques objet, afin de conserver l'ordre
    var stMenuLayerObject=JSON.stringify( oApplication.tMenuLayerObject);
   
    var c=getById('tMenuLayerObject');
    if(c){
        c.value=stMenuLayerObject;
    }
}
function save(){
   
    loadSave();
   
    //une fois le chargement fait, on soumet le formulaire
    var a=getById('formSave');
    if(a){
        a.submit();
    }
}

Et coté php, dans le framework dans me module default

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private function processSave($oSchema){
        if(!_root::getRequest()-&gt;isPost()){
            return;
        }
       
        //on modifie les proporiétés tObject et tMenuLayerObject
        $oSchema-&gt;tObject=_root::getParam('tObject');
        $oSchema-&gt;tMenuLayerObject=_root::getParam('tMenuLayerObject');
        //on enregistre le shema
        $oSchema-&gt;save();
       
        //on redirige sur la page
        _root::redirect('default::schema',array('id'=&gt;$oSchema-&gt;id));
    }

Processus de chargement du shéma
L’idée est simple: on récupère le tableau d’objet sérialisé ainsi que le tableau indiquant l’ordre des calques objets
Données que l’on assigne à la vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//dans l'action schema
public function _schema(){
        //on initialize les variables a vide
        $tObject=null;
        $tMenuLayerObject=null;
       
        //on recupere l'enregistrement du shema en base
        $oSchema=model_schema::getInstance()-&gt;findById(_root::getParam('id'));
        if($oSchema){
            //on appel le processus d'enregistrement
            $this-&gt;processSave($oSchema);
           
            //on recupere les valeurs serialises
            $tObject=$oSchema-&gt;tObject;
            $tMenuLayerObject=$oSchema-&gt;tMenuLayerObject;
        }
       
       
        $oView=new _view('default::index');
        $oView-&gt;tObject=html_entity_decode($tObject);
        $oView-&gt;tMenuLayerObject=html_entity_decode($tMenuLayerObject);
       
        $this-&gt;oLayout-&gt;add('main',$oView);
    }

Ensuite dans la vue, on va reconstruire le schéma: on instancie un tableau avec la valeur serialisé de tObject (malheureusement, la coloration syntaxique ne permet pas de le voir correctement)

1
var tObject=tObject?&gt;;

On boucle sur ce tableau serialisé pour recréé les objets Datas

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
for(var i=0;i&lt; tObject.length;i++){
    if(!tObject[i]){ continue; }
    var oData=new Data(tObject[i].type,tObject[i].idLayer);
    oData.x=tObject[i].x;
    oData.y=tObject[i].y;
    oData.strokeStyle=tObject[i].strokeStyle;
    oData.fillStyle=tObject[i].fillStyle;
    oData.width=tObject[i].width;
    oData.height=tObject[i].height;
    oData.from=tObject[i].from;
    oData.comment=tObject[i].comment;
    oData.to=tObject[i].to;
    oData.lineWidth=tObject[i].lineWidth;
    oData.x2=tObject[i].x2;
    oData.y2=tObject[i].y2;
    oData.texte=tObject[i].texte;
    oData.size=tObject[i].size;
    oData.info=tObject[i].info;
    oData.relativeObject=tObject[i].relativeObject;
    oData.relativeX=tObject[i].relativeX;
    oData.relativeY=tObject[i].relativeY;
    oData.textAlign=tObject[i].textAlign;
    oData.strokeStyleText=tObject[i].strokeStyleText;
   
    oData.fromPosition=tObject[i].fromPosition;
    oData.toPosition=tObject[i].toPosition;
   
   
    oData.build();
   
    oApplication.addLayerObject(1,oData);
}

On fait de même pour le tableau de calques objet: sauf que cette fois on l’enregistre dans l’objet application, puis on boucle pour reconstruire les différents calques.

1
2
3
4
5
6
7
oApplication.tMenuLayerObject=tMenuLayerObject?&gt;;

for(var i=1;i&lt;=iMax;i++){
   
    oApplication.clearMenuObject(i);
    oApplication.builListMenuLayerObject(i);
}

Les avancées
On peut désormais ordonner les calques objets et cliquer sur un objet pour l’envoyer sur un autre calque.

Dépot Github
Le dépot github: https://github.com/imikado/mkdraw

Réviser ses expressions régulières en s’amusant

regex0Introduction
Dans la plupart des languages on a la possibilité d’utiliser des expressions régulières.
C’est d’habitude une chose plus ou moins complexe a écrire (selon son expérience). Un site a eu la bonne idée de mixer les mots croisés et les expressions régulières.
Ainsi, il nous faut comprendre les expressions pour remplir correctement les cases vides.

Comment jouer ?
Chaque couple de case représente une chaine de caractère, celle-ci doit respecter les expressions en abscisse et en ordonnée.
Par exemple pour cet exemple:
regex
Vous avez une chaine de 2 caractères qui doit respecter les expressions « AB » « [CA]* » et « (B|C)*
Ainsi pour la première case c’est B: l’expression en abscisse nous indique attendre « A puis B »: et la première case est déjà occupé par le A
Et en ordonné on a « B ou C »
Pour la seconde case, on a en abscisse: « C ou A » et en ordonnée « B ou C », c’est donc C

Vous avez compris le principe, je vous invite à vous y essayer c’est une autre manière de réviser en s’amusant :)

Le site: http://regexcrossword.com/

Comment choisir son framework php

siteBlogMVC
Introduction
Depuis quelques années, nous avons vu se developper sur notre plateforme php de nombreux frameworks.
Le plus difficile aujourd’hui est de choisir parmi eux.
Jusque là, vous aviez à votre disposition un tableau comparatif: http://socialcompare.com…
Aujourd’hui grâce à l’initiative du site grafikart, vous avez une autre manière de comparer: le projet blogmvc.com (http://blogmvc.com)

Présentation du projet
L’idée est simple: montrer comment coder la même application avec différents frameworks du marché.
Pour cela, l’auteur met à disposition un projet github + des spécifications + une demo HTML du rendu attendu.
Ensuite libre à chacun de proposer une version de ce projet développé avec son framework préféré.
siteBlogMVCgithub
Le projet github: https://github.com/Grafikart/BlogMVC

Les frameworks php présentées actuellement
Actuellement, seuls 5 frameworks php sont représentés:

L’adresse du site du projet:
http://blogmvc.com/

L’adresse du billet expliquant l’initiative: http://www.grafikart.fr/blog/comparer-frameworks-mvc

Présentation des arborescences des 5 projets
Arborescence fermée
blogMVCclosed

Arborescence dépliée
blogMVC

En espérant que cette initiative vous aide à trouver votre framework ;)

note: pour information, le projet n’est pas restreint aux frameworks php (il y a par exemple RoR et Django), libre à vous de proposer votre framework peut importe son langage (ruby, python, perl…)

Retour au menu principal

Journal de bord: création d’une application de création de schéma en HTML5: Jour 2

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12464/developpement/journal-de-bord-creation-dune-application-de-creation-de-schema-en-html5-jour-1

Dans ce billet je vais un peu plus expliquer comment fonctionne l’application, puis dans un second temps j’indiquerai les modifications du jour.

Les calques
L’application à la manière d’un photoshop/inkscape ou Gimp gère les calques: il y a un bouton pour ajouter des calques qui fait appel à la méthode addLayer de la classe application
Dans le fichier public/js/application.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
addLayer:function(){
    this.addMenuLayer(this.idLayer);
       
    var sCanvas='';
       
    this.addContent('tCanvas',sCanvas);
       
       
    this.tLayer[this.idLayer]=new Canvas('canvas_'+this.idLayer);
       
    this.tLayer[this.idLayer].fillText(0,0,'Nouveau calque '+this.idLayer,'#000');
       
    this.selectLayer(this.idLayer);
       
    this.idLayer++;
},

Cette méthode créé un canvas sur la page + instancie un objet Canvas puis le stoque dans un tableau indexé par un id pour que l’on puisse interagir par la suite à chaque calque.
Puis le selectionne, pour que l’on puisse facilement lui ajouter des éléments

Pour information la classe Canvas se situe dans public/js/canvas.js elle permet de simplifier le dessin sur le canvas.

Les calques objets
Plutot que de dessiner simplement sur le canvas, comme on le ferait sur paint, l’idée est de créer des objets que l’on dessine sur les calques afin de pouvoir les modifier individuellement par la suite.
Pour cela, à chaque fois que l’on dessine un élement, on instancie un objet en utilisant la classe Data ( et en lui indiquant son calque).
Puis on lui demande de se dessiner sur le calque en appelant sa méthode build
Fichier public/js/data.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
build:function(){
       
        if(this.relativeObject!=''){
            var tmpRelativeObject=oApplication.getObject(this.relativeObject);
            if(tmpRelativeObject){
                this.x=this.relativeX+tmpRelativeObject.x;
                this.y=this.relativeY+tmpRelativeObject.y;
            }
        }
       
        if(this.type=='carre'){
            oApplication.tLayer[this.idLayer].drawRect(this.x,this.y,this.width,this.height,this.lineWidth,this.strokeStyle,this.fillStyle);
            oApplication.tLayer[this.idLayer].fillText(this.x+10,this.y+10,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='texte'){
            oApplication.tLayer[this.idLayer].fillText(this.x,this.y,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='ligne'){
            oApplication.tLayer[this.idLayer].line(this.x,this.y,this.x2,this.y2,this.strokeStyle,this.lineWidth);
        }else if(this.type=='fleche'){
            oApplication.tLayer[this.idLayer].arrow(this.x,this.y,this.x2,this.y2,this.strokeStyle,this.lineWidth);
        }else if(this.type=='bdd'){
            oApplication.tLayer[this.idLayer].drawBdd(this.x,this.y,this.width,this.height,this.lineWidth,this.strokeStyle,this.fillStyle);
            oApplication.tLayer[this.idLayer].fillText(this.x+10,this.y+30,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='link'){
           
            var oFrom=oApplication.getObject(this.from);
            var oTo=oApplication.getObject(this.to);
            console.log('build link from:'+this.from+' to:'+this.to);
           
            if(!oFrom || !oTo){
            }else if(this.points!=''){
                if(oApplication.pointIdSelected!==''){
                    oApplication.tLayer[this.idLayer].linkPointWithSelected(oFrom,oTo,this.points,oApplication.pointIdSelected,this.strokeStyle,this.lineWidth);
                }else{
                    oApplication.tLayer[this.idLayer].linkPoint(oFrom,oTo,this.points,this.strokeStyle,this.lineWidth);
                }
            }else{
                console.log('oFrom et oTo'+oFrom+' '+oTo);
                oApplication.tLayer[this.idLayer].link(oFrom,oTo,this.strokeStyle,this.lineWidth);
               
            }
        }
       
        this.updateInfo();
    },

Comme vous pouvez le voir: en fonction du type d’element on va appeler une méthode différente de dessin sur le layer où est dessiné l’objet.

A partir de là on peut cocher/decocher un objet pour l’afficher ou non sur son calque.
Pour ce faire, on joue sur la propriété visible de l’objet et on demande à l’application de redessiner le calque:
Fichier public/js/application.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
showHideObject:function(idObject){
        var a=getById('checkbox_object_'+idObject);
        if(a){
            var oObject=this.getObject(idObject);
           
            if(a.checked){
                oObject.visible=1;
            }else{
                oObject.visible=0;
            }
           
            this.buildLayer(oObject.idLayer);
        }
       
    },

Qui appelle ensuite la methode de reconstruction du calque (toujours dans le même fichier)

1
2
3
4
5
6
7
8
9
10
11
12
13
buildLayer:function(idLayer){
        oApplication.tLayer[idLayer].clear();
       
        var iLength=this.tMenuLayerObject[idLayer].length-1;
       
        for(var i=iLength;i&gt;=0;i--){
            var tmpObj=this.getObject(this.tMenuLayerObject[idLayer][i]);
            if(tmpObj &amp;&amp; tmpObj.visible==1){
                tmpObj.build();
            }
        }
       
    },

Qui comme vous le voyez efface le calque (canvas) avant de boucler sur les claques objets pour dessiner ou non celui-ci en fonction de sa propriété visible.

Voila pour ce premier tour d’explication du fonctionnement de l’application

Les modifications du jour
On pouvait créer des liens dynamiques entre les objets: en créant un objet « link » et en indiquant les id from et to à relier dynamiquement.
A chaque re-dessin du calque il recuperait les coordonnées des deux elements pour construire un lien « cassé » entre les deux
Le problème c’est que si il y avait plusieurs liens dans le schémas on était frustré de ne pas avoir un peu la main sur chemin emprunté par ses liens.
Pour cela, on peut désormais cliquer sur le calque pour ajouter les points à utiliser pour faire son lien.
Ensuite on voit se lister les différents points ajouté comme « contrainte » avec la possibilité de les supprimer individuellement
Enfin on peut cliquer sur l’un de ces points pour le déplacer en cliquant (une fois sélectionné) sur son nouvel emplacement sur le calque

Le rendu en image
projets_mkdraw4

Dépot Github
Le dépot github: https://github.com/imikado/mkdraw

Journal de bord: création d’un bomberman-like en HTML5, jour 5

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12456/developpement/journal-de-bord-creation-dun-bomberman-like-en-html5-jour-4

Plusieurs choses dans ce billet:
Premièrement je souhaiterais rappeler comment faire fonctionner le jeu (suite à une question sur github).
Deuxièmement je vais faire un petit point sur la séparation du code client / serveur
Enfin j’indiquerais les prochaines améliorations à venir

Comment faire fonctionner cette application
Cette application HTML5 se divise en 2 parties: la partie cliente : qui sera utilisée par les joueurs et la partie serveur qui non seulement fera tourner le jeu mais broadcastera également l’evolution du jeu à tous les joueurs.

Partie serveur
Pour la partie serveur, on utilise ici 2 choses: node.js et socket.io qui interprète le fichier « serverBomberM.js »
Pour le lancer

1
nodejs serverBomberM.js

Cette ligne de commande doit lancer le serveur et ne doit pas rendre la main, il rend la main qu’en cas d’erreur ;)
Cette partie serveur fait donc tourner le jeu, et écoute le port 1338
Il recalcule à intervale régulier la position de chaque joueur, bombes et gère les animations de ceux-ci + diffuse à chaque joueur des commandes pour mettre à jour la partie sur son navigateur.

Partie cliente
Cette partie là a été épurée, désormais elle contient uniquement la partie écoute d’action socket et dessin de la partie à l’écran + une partie pour envoyer ses commandes au serveur (action de presser une touche de clavier ou de la relacher)
Elle ouvre 2 choses sur le serveur:

  • le fichier socket.io qui est diffusé par le serveur http://votreserveur:1338/socket.io/socket.io.js
  • elle ouvre une connection socket (sur le meme port) http://votreserveur:1338

Avancement de la séparation client/serveur
Le chantier a bien avancé, la séparation est quasiment finie, j’aimerais vraiment au maximum épurée la partie cliente avant de commencer les prochaines mises à jour.
Cela permettra de vraiment tout gerer coté serveur node.js

Améliorations à venir
Le jeu est déjà jouable, mais il lui manque des choses de base comme le fait de ne pas avoir de Game Over, gérer plusieurs parties, il manque également des éléments de jeux comme les murs destructibles, et les bonus d’amélioration de bombes: (plus grand périmètre d’explosion), bombes uniquement horizontal/vertical…
Bref il y a matière à améliorer cette base pour avoir une jeu sympa et envisager de le mettre en ligne

Voila pour ce jour 5, ce projet est un peu en pause actuellement car je profites de ce que j’ai appris pour faire une autre application en parallèle, dont je fais également un journal de bord ;)
Pour les curieux: http://www.developpez.net/forums/d1413605/webmasters-developpement-web/javascript/journal-bord-creation-d-application-dessin/

Journal de bord: création d’une application de création de schéma en HTML5: Jour 1

Introduction
Depuis quelques temps déjà j’ai besoin de créer des schémas, si possible interactif.
Principalement des schémas d’architecture avec des serveurs, des bases de données ainsi que des liens entre eux.

Avec le développement des derniers jeux RTS et Bomberman en HTML5, j’ai appris à utiliser les canvas.

Je viens donc vous présenter ici l’application sur laquelle je travaille en ce moment: MkDraw.
Comme d’habitude, vous trouverez en bas de ce billet le lien vers le github du projet.

Présentation fonctionnelle de l’application
Cette application, basée sur le mkframework, permet de créer des schémas si besoin interactif.
Vous pouvez actuellement:

  • gérer des calques
  • afficher/cacher des calques/objets
  • créer des rectangles, lignes, flèches et bases de données
  • créer des liens entre certains éléments
  • placer des éléments de manière fixe ou relative (comme un aimant)
  • définir une couleur de fond, de bord ainsi que son épaisseur
  • écrire un texte sur un rectangle/base de données
  • créer une infobulle (avec html si besoin)
  • enregistrer votre schéma

Présentation technique
Dans cette application, je créé autant de canvas que de calques, chaque objet ajouté sur la map est un objet javascript.
Chaque objet est une instanciation de la classe Data dans le fichier public/js/data.js
Le constructeur:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function Data(type,idLayer){
    this.type=type;
    this.idLayer=idLayer;
    this.x;
    this.y;
    this.x2;
    this.y2;
   
    this.texte='';
    this.id=oApplication.idObject;
    this.size=11;
    this.visible=1;
   
    this.fillStyle='#ffffff';
    this.lineWidth=2;
    this.strokeStyle='#000000';
   
    this.from;
    this.to;
   
    this.comment='comment';
   
    this.info='';
   
    this.relativeObject='';
   
    this.relativeX=0;
    this.relativeY=0;
   
    this.points='';
   
    oApplication.tObject[this.id]=this;
   
    oApplication.idObject++;
   
    if(!oApplication.tMenuLayerObject[idLayer]){
        oApplication.tMenuLayerObject[idLayer]=Array();
    }
   
   
    oApplication.tMenuLayerObject[idLayer].unshift(this.id);
     
   
}

Dans ce constructeur, on définit les variables par défaut, incrément l’id général et on ajoute le nouvel objet en début de tableau d’objet.
Au moment de dessiner un objet sur la map, on appelle la méthode build() de l’objet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Data.prototype={
    build:function(){
       
        if(this.relativeObject!=''){
            var tmpRelativeObject=oApplication.getObject(this.relativeObject);
            if(tmpRelativeObject){
                this.x=this.relativeX+tmpRelativeObject.x;
                this.y=this.relativeY+tmpRelativeObject.y;
            }
        }
       
        if(this.type=='carre'){
            oApplication.tLayer[this.idLayer].drawRect(this.x,this.y,this.width,this.height,this.lineWidth,this.strokeStyle,this.fillStyle);
            oApplication.tLayer[this.idLayer].fillText(this.x+10,this.y+10,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='texte'){
            oApplication.tLayer[this.idLayer].fillText(this.x,this.y,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='ligne'){
            oApplication.tLayer[this.idLayer].line(this.x,this.y,this.x2,this.y2,this.strokeStyle,this.lineWidth);
        }else if(this.type=='fleche'){
            oApplication.tLayer[this.idLayer].arrow(this.x,this.y,this.x2,this.y2,this.strokeStyle,this.lineWidth);
        }else if(this.type=='bdd'){
            oApplication.tLayer[this.idLayer].drawBdd(this.x,this.y,this.width,this.height,this.lineWidth,this.strokeStyle,this.fillStyle);
            oApplication.tLayer[this.idLayer].fillText(this.x+10,this.y+30,this.texte,this.strokeStyle,this.size);
        }else if(this.type=='link'){
           
            var oFrom=oApplication.getObject(this.from);
            var oTo=oApplication.getObject(this.to);
            console.log('build link from:'+this.from+' to:'+this.to);
           
            if(!oFrom || !oTo){
            }else if(this.points!=''){
                oApplication.tLayer[this.idLayer].linkPoint(oFrom,oTo,this.points,this.strokeStyle,this.lineWidth);
            }else{
                console.log('oFrom et oTo'+oFrom+' '+oTo);
                oApplication.tLayer[this.idLayer].link(oFrom,oTo,this.strokeStyle,this.lineWidth);
               
            }
        }
       
        this.updateInfo();
    },

Ci dessous un exemple de méthodes de dessin utilisées dans l’objet Canvas, notamment drawBdd()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
,
    drawBdd:function(x,y,ilargeur,ihauteur,lineWidth,strokeStyle,fillStyle){
       
        this.ctx.lineWidth=lineWidth;
        this.ctx.strokeStyle=strokeStyle;
        this.ctx.fillStyle=fillStyle;
       
        var hauteurEllipse=30;
         
        //this.line(x,y,x,y+ihauteur);
        //this.line(x+ilargeur,y,x+ilargeur,y+ihauteur);
               
        var centerX=x+(ilargeur/2);
        var centerY=y;
       
        var width=ilargeur;
        var height=5;
       
        x=parseFloat(x);
        y=parseFloat(y);
        ilargeur=parseFloat(ilargeur);
        ihauteur=parseFloat(ihauteur);
               
        //fond
        this.ctx.beginPath();
       
            this.ctx.moveTo(x,y);
            this.ctx.bezierCurveTo(
                            x,y-hauteurEllipse,
                            x+ilargeur,y-hauteurEllipse,
                            x+ilargeur,y
            );
                                   
            this.ctx.moveTo(x+ilargeur,y);
            this.ctx.lineTo(x+ilargeur,y+ihauteur);
           
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x,y+ihauteur);
           
            this.ctx.moveTo(x+ilargeur,y);
            this.ctx.lineTo(x+ilargeur,y+ihauteur);
           
            this.ctx.moveTo(x+ilargeur,y+ihauteur   );
            this.ctx.bezierCurveTo(
                            x+ilargeur,y+ihauteur+hauteurEllipse,
                            x,y+hauteurEllipse+ihauteur,
                            x,y+ihauteur
            );                 
                               
            this.ctx.moveTo(x,y+ihauteur);
                                   
            this.ctx.lineTo(x,y+ihauteur);
            this.ctx.moveTo(x,y);
       
            this.ctx.fillRect(x,y,ilargeur,ihauteur);
           
            this.ctx.fill();
       
        this.ctx.closePath();
       
        //trait
       
        this.ctx.beginPath();
            this.ctx.moveTo(x,y);
            this.ctx.bezierCurveTo(
                                    x,y+hauteurEllipse,
                                    x+ilargeur,y+hauteurEllipse,
                                    x+ilargeur,y
                                    );
            this.ctx.moveTo(x,y);
            this.ctx.bezierCurveTo(
                                    x,y-hauteurEllipse,
                                    x+ilargeur,y-hauteurEllipse,
                                    x+ilargeur,y
                                    );
           
            this.ctx.moveTo(x,y+ihauteur);
            this.ctx.bezierCurveTo(
                                    x,y+hauteurEllipse+ihauteur,
                                    x+ilargeur,y+hauteurEllipse+ihauteur,
                                    x+ilargeur,y+ihauteur
                                    );
                                   
            this.ctx.moveTo(x,y);
            this.ctx.lineTo(x,y+ihauteur);
           
            this.ctx.moveTo(x+ilargeur,y);
            this.ctx.lineTo(x+ilargeur,y+ihauteur);
           
            this.ctx.stroke();
       
        this.ctx.closePath();
       
       
         
    }

ou link()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
,
    link:function(oFrom,oTo,color,border){
       
        var x1=oFrom.x+(oFrom.width/2);
        var y1=oFrom.y+(oFrom.height/2);
       
        var x2=oTo.x+(oTo.width/2);
        var y2=oTo.y+(oTo.height/2);
       
        //calcul centre
        var xCenter=x1+((x2-x1)/2);
       
        this.ctx.beginPath();
        this.ctx.lineWidth=border;
        this.ctx.strokeStyle = color;
        this.ctx.moveTo(x1,y1);
        this.ctx.lineTo(xCenter,y1);
        this.ctx.lineTo(xCenter,y2);
        this.ctx.lineTo(x2,y2);
        this.ctx.stroke();
    },

Quelques images de l’application
projets_mkdraw1

projets_mkdraw2

projets_mkdraw3

Dépot Github
Le dépot github: https://github.com/imikado/mkdraw

Journal de bord: création d’un bomberman-like en HTML5, jour 4

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12454/developpement/journal-de-bord-creation-dun-bomberman-like-en-html5-jour-3

Aujourd’hui on va passer l’intelligence du jeu coté serveur, on va arrêter le concept du joueur bleu qui pilote la partie.
Pour cela, on va alléger la partie client qui ne devrait à terme pouvoir uniquement effacer, afficher, animer un personnage, une bombe (demandé par le serveur).
Le client va également indiquer au serveur les touches qu’il appuie ou qu’il relache (pour que le serveur puisse déduire la direction, et les coordonnées des bombes à placer)

Sur le github habituel vous pouvez voir pas mal de mise à jour en conséquence.

Duplication des classes Game,Perso et Bomb
Pour cela, il nous faut simplifier les classes coté client, et modifier un peu celles coté serveur.
Avant, lorsqu’un joueur été déplacé, le joueur bleu indiquait au serveur la modification à faire pour que celui-ci la broadcast.
Maitenant, vu que c’est le serveur qui gère l’ensemble: il sait que le joueur se déplace et le broadcast directement.

Parenthèse Node.js
Quand j’ai commencé cette migration, je me suis dit que ce serait simple simplement ajouté des inclusions dans le fichier server, un chose que je ne savais pas c’est qu’apparement la porté des variables n’est pas la même :(
Une chose à faire par exemple quand vous importez une classe dans node.js, il faut penser dans celle-ci à l’exporter
Par exemple: (dans serverBomberM.js)

1
var Perso=require('./bombermanM_PersoB.js');

Et dans le fichier de la classe bombermanM_PersoB.js (en fin de fichier)

1
module.exports=Perso;

Je vous invite à attendre la fin de cette migration pour lire les fichiers sur github, je ferais un billet quand ce sera fini.

Le dépot Github:
https://github.com/imikado/bomberhtml5

Journal de bord: création d’un bomberman-like en HTML5, jour 3

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12445/developpement/journal-de-bord-creation-dun-bomberman-like-en-html5-jour-2

Aujourd’hui nous allons corriger quelques bugs du jeu rencontré lors des premiers essai avec des collègues.

  • Menu affichage des team (en fonction des team disponibles)
  • Permettre de déposer une bombe pendant que le joueur court
  • Bugfix concernant le haut de la map et la partie gauche

Mise a jour affichage des teams disponibles
Lorsque les joueurs rejoignent la partie, il leur faut choisir une équipe bleu,rouge,jaune ou vert.
On va mettre à jour cette liste à chaque fois qu’un joueur choisit une équipe ou quitte la partie

Du coté client, on va, au moment où l’on choisit une équipe, indiquer au serveur la team choisie pour qu’il l’enregistre
Coté client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
setTeam:function(team){
    this.team=team;
               
    if(team=='blue'){
        setTimeout(run,fps);
    }
       
    getById('team').style.display='none';
               
    map.build();
    this.refresh();
       
    socket.emit('setTeamBroadcast',team);
},

Coté serveur

1
2
3
4
5
6
7
8
socket.on('setTeamBroadcast',function(team){
    tTeamConnected[team]=1;
    tSocket[socket.id]=team;
       
    var tTeamConnectedB=getTeamConnected();
       
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB);
});

Ensuite du coté du serveur, on va indiquer que lors de la connection on va broadcaster les team actives

1
2
3
4
5
6
7
8
socket.on('setTeamBroadcast',function(team){
    tTeamConnected[team]=1;
    tSocket[socket.id]=team;
       
    var tTeamConnectedB=getTeamConnected();
       
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB);
});

Et la fonction getTeamConnected()

1
2
3
4
5
6
7
8
9
function getTeamConnected(){
    var tTeamConnectedB=Array();
    for(var i=0;i&lt; tColor.length;i++){
        if(tTeamConnected[tColor[i]]==1){
            tTeamConnectedB.push(tColor[i]);
        }
    }
    return tTeamConnectedB;
}

Idem lors de la déconnection:

1
2
3
4
5
6
7
8
socket.on('disconnect', function () {
    tTeamConnected[ tSocket[socket.id] ]=0;
       
    var tTeamConnectedB=getTeamConnected();
       
    socket.emit('Game.listTeam',tTeamConnectedB);
    socket.broadcast.emit('Game.listTeam',tTeamConnectedB);
});

On appelle la fonction d’affichage du menu des teams disponibles:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
socket.on('Game.listTeam',function(tTeamConnected){
       
    for(var i=0;i&lt;tTeam.length;i++){
        var a=getById(&#039;button-&#039;+tTeam[i]);
        if(a){
            a.style.display=&#039;block&#039;;
        }
    }
       
    for(var i=0;i&lt; tTeamConnected.length;i++){
        console.log(&#039;desactivation &#039;+tTeamConnected[i]);
        var a=getById(&#039;button-&#039;+tTeamConnected[i]);
        if(a){
            a.style.display=&#039;none&#039;;
        }
    }
       
});

Ainsi: lorsqu’on arrive sur la partie, le jeu affiche la liste des 4 team, en parallèle si un autre joueur cliquer sur une team, il va broadcaster aux autres qu’une team n’est plus disponible.
Et au contraire, si un des joueurs quitte la partie: volontairement ou fait un F5 pour un soucis de navigateur, il peut de nouveau se reconnecter avec sa team.

Permettre de déposer une bombe en courant
Je cherchais pourquoi lorsque l’on courrait et appuyait sur la barre d’espace, le joueur déposait sa bombe et s’arretait…
C’était tout bête: un bug lors de la gestion du keyUp (relachement d’une touche)
Dans ce cas là j’annulais la direction du personnage, hors dans le cas du relachement de la barre d’espace, ce ne doit pas être le cas
Ainsi, la modification

1
2
3
4
5
6
7
eventKeyUp:function(e){
    var touche = e.keyCode;
    //si touche relachée différente de la barre d'espace
    if(touche!=32){
        this.setTeamDirectionBroadcast('');
    }
},

Conclusion
Voilà pour ce 3ème jour, comme vous le voyez en peu de temps, on a déjà un jeu multi-joueur jouable.
Les prochains jours permettront d’améliorer la jouabilité ;)

Journal de bord: création d’un bomberman-like en HTML5, jour 2

Introduction
Ce billet fait suite au billet: http://blog.developpez.com/ducodeetdulibre/p12443/developpement/journal-de-bord-creation-dun-bomberman-like-en-html5-jour-1

Ici nous allons voir les classes des personnages et des bombes

bombermanM_Perso.js
Ici la classe de personnage qui va permettre d’afficher chacun des personnages sur la map du jeu.
Elle permet également de gérer son animation: pour simuler la marche de celui-ci en direction de la touche de curseur pressée.
Regardons de plus près la méthode build:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
build:function(){
               
    var sDirection=oGame.tDirection[this.team];
       
    //si pas de direction, on affiche l'image statique 
    if(sDirection==''){
        tmpImg=this.idImg;
    //si l'image précédente était marche 2, on affiche marche 1
    }else if(this.tmpIdImg==this.idImg+'_walking2'+sDirection){
        tmpImg=this.idImg+'_walking'+sDirection;
    //sinon on affiche la marche 2
    }else{
        tmpImg=this.idImg+'_walking2'+sDirection;
    }
    this.tmpIdImg=tmpImg;
       
    //partie affichage de l'image du personnage sur le canvas
    oImages.drawImageOnLayer(tmpImg,(this.x*widthCase)-20,(this.y*heightCase)-20,widthCase,widthCase,'perso');
       
    //on affiche ici un carré représentant la couleur de la team
    oLayer_perso.fillRect((this.x*widthCase),(this.y*heightCase)-25,8,8,this.team);
       
    //on enregistre les nouvelles coordonnées du joueur
    oGame.savePerso(this);
       
},

Comme vous pouvez le voir, on récupère la direction du personnage qui est déduite dans la classe Game (lors du pressage de la touche)
Si aucune direction: le joueur attend, on affiche l’image statique (de face), sinon il y a une direction d’engagée.
Dans ce cas on gère une animation de marche: pour cela on a par direction 2 images, et on va les alterner à chaque affichage du personnage.
Vous voyez également qu’on affiche un petit carré en haut à gauche du personnage pour signaler son équipe.

bombermanM_Bomb.js
Une nouvelle classe fait ici son apparition afin de gérer des objets « Ã©phémères »: les bombes: en effet, à partir de leur création, elles ont une durée limitée sur la map.
De plus, elles doivent à la fin exploser: créer une image de flamme sur plusieurs cases adjacentes et supprimer les eventuels joueurs sur le passage.

Premièrement, au niveau de l’affichage des bombes, on va gérer une animation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
refreshBomb:function(){
           
    for(var i=0;i&lt; this.tBomb.length;i++){
        var oBomb= this.tBomb[i];
        if(oBomb.life &lt; 14){
            //pendant 14 iterations, on va alterner entre deux sprites
            if(oBomb.life % 2 ){
                oBomb.idImg=&#039;bomb-0&#039;;
            }else{
            oBomb.idImg=&#039;bomb-1&#039;;
            }  
        }else if(oBomb.life &lt; 17){
            //puis animation d&#039;explosion
            if(oBomb.life % 2 ){
                oBomb.idImg=&#039;explosion&#039;;
            }else{
                oBomb.idImg=&#039;explosion-1&#039;;
            }
        }else if(oBomb.life &lt; 19){
            oBomb.idImg=&#039;explosion-2&#039;;
        }else{
            oBomb.idImg=&#039;explosion-finish&#039;;          
        }
           
        oBomb.life++;
           
        //on broadcast l&#039;animation de la bombe
        oBomb.animateBroadcast(oBomb.idImg);
    }
               
},

Ensuite au niveau de la classe bombe, on gère l’affichage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
build:function(){
       
    if(this.idImg=='explosion' || this.idImg=='explosion-1' || this.idImg=='explosion-2'){
        for(var i=-2;i&lt; 3;i++){
            oLayer_bomb.clearRect(((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase);
            oLayer_bomb.clearRect(((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase);
           
            if(map.tMap[this.y][this.x+i]==1){
                oImages.drawImageOnLayer(this.idImg,((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase,&#039;bomb&#039;);
            }
            if(map.tMap[this.y+i][this.x]==1){
                oImages.drawImageOnLayer(this.idImg,((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase,&#039;bomb&#039;);
            }
        }
   
    }else if(this.idImg==&#039;explosion-finish&#039;){
        for(var i=-2;i&lt; 3;i++){
            oLayer_bomb.clearRect(((this.x+i)*widthCase),(this.y*heightCase),widthCase,widthCase);
            oLayer_bomb.clearRect(((this.x)*widthCase),((this.y+i)*heightCase),widthCase,widthCase);
           
            var oPersoVictim=oGame.getPerso(this.x+i,this.y);
            if(oPersoVictim){
                oGame.removeBroadcastPersoById(oPersoVictim.id);
                console.log(&#039;remove &#039;+oPersoVictim.id);
            }
            oPersoVictim=oGame.getPerso(this.x,this.y+i);
            if(oPersoVictim){
                oGame.removeBroadcastPersoById(oPersoVictim.id);
                console.log(&#039;remove &#039;+oPersoVictim.id);
            }
           
        }
       
        oGame.removeBroadcastBombById(this.id);
        return;
    }else{
        oLayer_bomb.clearRect((this.x*widthCase),(this.y*heightCase),widthCase,widthCase);
    }
           
    oImages.drawImageOnLayer(this.idImg,(this.x*widthCase),(this.y*heightCase),widthCase,widthCase,&#039;bomb&#039;);
       
    oGame.saveBomb(this);
   
},

Gestion intersection
Afin d’eviter d’avoir un bug graphique de chevauchement: lorsqu’un joueur est à cheval entre deux cases, il ne peut pas descendre/monter au risque de « marcher sur un mur »
Pour cela, dans la gestion du déplacement, on verifie que l’on est pas à cheval:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
refreshPerso:function(){
   
    //on boucle sur les persos existants
    for(var i=0;i&lt; this.tPerso.length;i++){
        var oPerso= this.tPerso[i];
        if(oPerso.life &lt;=0){ continue;}
       
            var vitesse=0.5;
           
            if(!this.tDirection[oPerso.team]){
                continue;
            }
           
            var sDirection=this.tDirection[oPerso.team];
           
            //on efface le dessin sur le calques
            oPerso.clear();
           
            //on initialise les nouvelles coordonnées
            var newX=oPerso.x;
            var newY=oPerso.y;
            var newXcheck=oPerso.getX();
            var newYcheck=oPerso.getY();
           
           
            if(newY != parseInt(newY)){
                //si entre deux cases, on ne peut pas descendre/monter
            }else if(sDirection==&#039;right&#039;){
                newX+=vitesse;
                newXcheck+=1;
            }else if(sDirection==&#039;left&#039;){
                newX-=vitesse;
                newXcheck-=1;
            }
            if(newX != parseInt(newX)){
                //si entre deux cases, on ne peut pas descendre/monter
            }else if(sDirection==&#039;up&#039;){
                newY-=vitesse;
                newYcheck-=1;
            }else if(sDirection==&#039;down&#039;){
                newY+=vitesse;
                newYcheck+=1;
            }
           
            if(this.checkCoord(newXcheck,newYcheck)){
                //si les coordonnées est libre
                oPerso.x=newX;
                oPerso.y=newY;
               
            }
           
            //on dessine le personnage
            oPerso.buildBroadcast(&#039;walking&#039;);
           
       
    }
   
   
},

En image
bomber4

Le dépôt github
Le dépôt https://github.com/imikado/bomberhtml5