Jeu de SODOKU avec AngularJS MVVM - JavaScript SODOKU puzzle Application with AngularJS MVVM -- Part 1

Introduction
Le jeux de Sodoku est un jeu très connu qu'on trouve souvent dans les dernières pages des journaux . Le mot Sodoku en lui même signifie chiffre unique en langue japonaise d'ou l'origine de ce jeu .
Dans cette première partie on s’intéressera a la résolution d'une grille de Sodoku classique voir une grille de 9x9 en utilisant un algorithme très simple en javascript.
Pour présenter la grille dans une page Web d'une manière interactive et sans grand effort je dirais ,on utilisera AngularJS .

Cette librairie va structurer l'application séparer les infrastructures , la présentation , la gestion des données et plus encore ,sans oublier le data binding Pour ce qui sont dans le brouillard a propos de AngularJS vous pouvez visiter le site ici

A fin d’améliorer la maintenabilité, la productivité et lisibilité simplement de l'application on adoptera le pattern MVVM pour ce qui veulent savoir plus sur ce pattern icipour commencer . MVVM dans AngularJS est très implicite et dont le présent exemple sera une illustration enfin j’espère !


Sodoku classique - Algorithme
Rappel

le jeux consiste en une grille carrée de 9x9 qui contient tous les chiffres de 1 à 9 qu'on appellera solution puis en cachant 20,30...cases selon la complicité qu'on veux donner, on obtient une grille de jeu .
Sodoku s'énonce avec les règles suivantes :

  • - Tous les chiffre de 1 à 9 sont présent une fois sur chaque ligne
  • - Tous les chiffre de 1 à 9 sont présent une fois sur chaque colonne
  • - Tous les chiffre de 1 à 9 sont présent une fois dans chaque sous grille de 3x3
ces trois règles vont formuler l’Algorithme suivant :


Algorithme

L'algorithme qu'on va appliquer ici est un algorithme connu de résolution de grille de Sodoku avancer de deux pas et reculer d'un on avance quand même
la solution consiste en une grille (une matrice, un tableau) de 9x9 cellule .
Chaque cellule est un objet composé de d'une valeur qui est un chiffre de 1 à 9 et d'un tableau de valeur possibles qui est un tableau de tous les chiffre de 1 à 9 .
le déroulement :
Pour chaque cellule :
tant que il y a des possibilités pour cette cellule :
prendre un nombre aléatoirement un chiffre dans le tableau des possibilités et le supprimer dans celui ci :
vérifier que :
si ce nombre est assigne a la valeur de la cellule :
- sur la ligne de cette cellule ce nombre n existe pas déjà
- de même sur la colonne
- dans la sous grille de 3x3 ce nombre n existe pas déjà
si le cas on a trouvé une valeur alors l assigner a la cellule et arrêter le tant que
sinon boucler le tant que
si après le tant que on a pas trouvé alors
réinitialiser les possibilités de la cellule
reculer d'une case
fin
Et voilà : on génère une solution pour une grille classique de 9x9 a chaque lancement puis nous reste qu'a cacher certaines cellules pour faire un jeu bien sur . Implémentation Maintenant nous allons implémenter cet algorithme en JavaScript . On imagine bien sur un objet Javascript composé d'un tableau a deux dimensions dont chaque élément est une cellule composée a son tours d'une valeur et un tableau de chiffre de 1 à 9
Une méthode public qui consiste a générer la solution et d'autres méthodes utilitaires pour l'initialisation , la validation la génération de nombre aléatoirement ... voici le code jusqu' a cette étape :

///<summary>
///Objet qui decrit une grille de Sodoku
///<summary> 
 var sodokuGrid=  {
  ///<summary>
  ///Represente la grille en elle meme comme une matrice
 ///<summary> 
           matrix:null,
 ///<summary>
        /// Un compteur qui servira dans les tests
 ///<summary> 
    counter:0,
    size:9,
 ///<summary>
  /// Un chrono pour les tests et les perfs
  ///<summary> 
   stopWatch:0,
  ///<summary>
       /// generer une solution valide 
       ///<summary>
       ///<param name="arg"></param>
     ///<return name="matrix">renvoie une matrice  solution</return>
    resolve:function(arg){
      this.init(arg);
   var allcells=this.size*this.size;
   for(var i=0;i<allcells;i++){
     var row = parseInt(i / 9);
     var col = i - (row * 9);
     this.counter++
           var num = this.getNext(row,col);
            if( num){
             this.matrix[row][col].value =num ;
        }else{
       i-=2;
      }
      
   }
   this.stopWatch=new Date()-this.stopWatch; 
   return  this.matrix
    },
    
     ///<summary>
     ///obtenir un nombre valid pour la cellule actuelle  
            ///<summary>
            ///<param name="i">la ligne actuelle</param>
     ///<param name="j">la colonne actuelle</param>
 ///<return name="number">renvoie un nombre de 1 a 9 ou undefined </return>
 getNext:function(i,j){
       var len=this.matrix[i][j].poss.length
        if(!len){
          this.matrix[i][j].poss = this.getPossibilities();
          return 
  }
 var num = this.matrix[i][j].poss.splice(this.randomize(0,len), 1)[0]
     if(this.isValid(i,j,num)){
  return num
     } else{
         return this.getNext(i,j);
     }
       
    },
  ///<summary>
 ///verifie qu un nombre est valable pour la cellule   
       ///<summary>
        ///<param name="i">la ligne actuelle</param>
         ///<param name="j">la colonne actuelle</param>
 ///<param name="num">le nombre a verifier</param>
    ///<return name="number">renvoie un nombre de 1 a 9 ou undefined</return> 
    isValid:function(i,j,num){
   for(var k=0;k<j;k++){
         if(this.matrix[i][k].value ==num)return false ;
   }; 
                
   for(var k=0;k<i;k++){
         if(this.matrix[k][j].value==num)return false ;
   };
     
   var minJ=parseInt(j/3)*3,minI=parseInt(i/3)*3;
   var maxJ=minJ+3,maxI=minI+3;
   for(var l=minI;l<i;l++){
       for(var m=minJ;m<maxJ;m++){
            if(this.matrix[l][m].value==num)return false ;
        }
    }
     return true;
    },
         ///<summary>
 ///initialise la grille    
 ///<summary>
   init:function(){
  this.matrix=new Array(this.size);
  this.counter=0;
  this.stopWatch=new Date();
          var that=this;
  this.matrix=new Array(this.size);
     for(var j=0;j<this.size;j++){
      
        this.matrix[j]=new Array(this.size);
  for(var i=0;i<this.size;i++){
          this.matrix[j][i]={                 
                                           value:"",                 
         poss:that.getPossibilities()
                                             }             
    }
              }      
    
       },    
       /// <summary>
 ///genere un tableau de possibiles de 1 a 9     
 /// <return name="Array" > renvoie un tableau </return> 
    getPossibilities: function(){
      return [1,2,3,4,5,6,7,8,9] ;
  }, 
     /// <summary>
     ///genere un nombre aleatoire entre deux chiffres   
    /// <summary>
    /// <param name="min"  >la valeur basse</param>
          /// <param name="max">la valeur haute</param>
  /// <return name="Number" >renvoie entre le min et le mx </return> 
    randomize: function(min, max){
      return parseInt(Math.random()*max)+min ;
  } 
 
    }





Presentation d'une grille et tests

Nous allons maintenant créer une page HTML pour tester l'algorithme et visualiser une grille solution.

La page HTML sera une introduction a l'application AngularJS
Une application AngularJS se compose de modules , de contrôleurs ,de directives et de services .Chaque contrôleur possède un scope qui nous servira plus tard de ViewModel et sans oublier les Templates et pages HTML
Pour l instant notre page sera une simple page avec un contrôleur.


Venons au faites et voici a quoi ressemble la page
<html ng-app>
<!-- ng-app indique que l 'application utilise  Angular-->
<head>
<title>SODOKU</title>


</head>
<body>
<div id="sodoku"  style="width:400px; height:400px;" ng-controller="sodokuCtrl">

<!-- ng-controller indique le controlleur de cette partie est sodokuCtrl  -->

<table width="100%" border="1">
<tr ng-repeat="row in matrix">
    <td ng-repeat="col in row">{{col.value}}</td>
</tr>
</table>
<br/>
Nombre de boucles: <input ng-model="counter"/>
<br/>
Temps de génération en ms :<input ng-model="stopWatch"/>
 
</div>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.4/angular.min.js">
</script>

<script type="text/javascript" src="SodokuAlgo.js">
</script>
<script>

//c'est le controleur de la page 
//qui est déclare sur le premier div 
// le paramétré $scope est injecté par Angular et représente
// le scope de ce contrôleur  
function sodokuCtrl($scope){
   
  //Ici on compose le ViewModel
  //la vue(page) a besoin d'une matrice(grille)
  //d'un conteur de boucle qu'a parcouru l’algorithme pour générer la grille
  //et d'un chrono pour afficher le temps mis 
   $scope.matrix=sodokuGrid.resolve();
   $scope.counter=sodokuGrid.counter;
   $scope.stopWatch=sodokuGrid.stopWatch;
 }


</script>
</body>
</html>

Et voila la page qui permet de générer des solutions de grilles Sodoku et d'afficher
le nombre de boucles de tentatives qu'a mis l'algorithme pour résoudre et enfin le temps mis a chaque fois en millisecondes

Voici tous le code que vous pouvez tester en direct


Conclusion

c'est une brève introduction à AngularJS et un algorithme de résolution de sodoku assez optimal sur 1000000 de tests effectué la moyenne de boucles de génération est de 185 qui veut dire qu on retrouve 81 cases en 185 tentatives et sur un processeur de 2.3GHz et avc Google Chrome comme navigateur le temps moyen pour générer une grille est moins de 3 ms
A la prochaine partie

Aide à la saisie des adresses avec google map -- JQuery JavaScript google map address form autocomplete

Si un jour vous avez eu a faire a la saisie d'un formulaire d'inscription sur un site internet vous avez certainement trouvé long de remplir tous les champs des formulaires un par un . Et si vous développez vous même ce genre de formulaires vous avez certainement cherché a automatiser le remplissage des champs du moins au mieux
Voici un plugin JQUERY qui pourrais vous facilité la complétion automatique des champs d'adresse , nom de rur, code postal , ville , pays ...
le plugin est base sur l'API GOOGLE MAP qui vous assiste lors de la saisie des adresse
pour mettre en place ce plugin n'ouliez pas d inclure JQUERY etgoogle map dans vos pages comme ceci :
 
 

puis définir le plugin lui même que vous pouvez faire dans la même page ou dans un fichier JS séparé comme ceci:
$(function (){

 $.fn.googleMapAuocompleteAddress = function(opt) {
    var options = $.extend({}, {
    }, opt);
   // each : si le plugin est énumérer tous les éléments du selector   
         return this.each(function(i, e){
             //l’élément a qui s’applique le plugin ne peut etre qu un champs input
              if(! $(this).is("input"))return ;
         var autocomplete = new google.maps.places.Autocomplete($(this).get(0));
         //Définir le champs comme autocomplete
       //Attacher un évènement chaque fois que l’adresse saisie change   
       google.maps.event.addListener(autocomplete, 'place_changed', function() {
          //Récupérer les lieux correspondants a l adresse saisie 
           var place = autocomplete.getPlace();
   //Récupérer les composants de l adresse    
    var componenents=place.address_components||[];
    var ln=componenets.length;
     for (var i=0 ; i<ln;i++) { 
            var editor=$("[data-role|="+componenets[i].types[0]+"]"); 

             //Regarder tous les elements html dont l'attribut data-role
             // ressemble a street_number, route,locality,postal_code,country ...

   if(editor.is("input")){
       editor.val(componenets[i].long_name);
                     //si l'element est un champs on rempli sa valeur
   }else{
                     //sinon son html 
      editor.html(componenets[i].long_name);
   }//end if 
   }//end for 
        
 });//end addListener 
     });//end each  
   };//end googleMapAuocompleteAddress
});


Voici maintenant un exemple d'utilisation de ce plugin












Vous tester directement ici le résultat voici un Plunker

Comparer deux dates -- JQuery Javascript Compare dates

JavaScript fournit nativement les opérateurs de comparaison de deux date . Savoir si deux dates sont identiques, l(une est plus récente que l autre... Ce que ont pourrais résumer dans un exemple comme ceci :
  var dt1 = new Date(2002,10,14,2,2,3) ;
  var dt2=new Date(2002,10,14,2,2,3);
  
  var assert=  dt1



  Maintenant , si on veux comparer  deux date , juste pour savoir si elle sont du même jour ou du même mois ou du la même minute 
 Si deux dates sont du même jour peut importe les heures et minutes.. On veux savoir si ca se passe le même jour 

Exemple :
  var dt1 = new Date(2002,10,14) ;
  var dt2=new Date(2002,10,14,2,2,3);

var assertAreEqual = dt1-dt2==0;
//assertAreEqual =false : les deux dates sont différentes 

var assertAreSameDay= (dt1-dt2)/(24*60*60*1000) < 1
  //assertAreSameDay=true : les deux dates sont du même jour
  //même  mois même année 
  // on a calculé la diffrence en millisecondes entre les deux date puis
  // on transforme les millisecondes  en jour par division par (24*60*60*1000)
  //  heures dans la journée X minutes dans une heure X secondes dans une minutes
  // X millisecondes dans la seconde 
  // On vérifie que la différence est inférieur a un journée
 
 var assertAreSameHour=(dt1-dt2)/(60*60*1000) < 1
    //assertAreSameHour=false 
Conclusion :
Bien faire attention au comparaison des dates en JavaScript , bien que ca semple évident et simple ca réserve parfois des surprises

Mise en forme des nombres au format monétaire --Javascipt format money

Pour mettre formater un n nombre au format monétaire voir la séparation des décimales des milliers des millions et plus et tenir compte des formats locaux comme les symboles monétaires préfixe ou suffixe utiliser la virgule pour séparer les décimales ou le point le nombre de chiffre après la virgule ...... Javascript ne fournit pas de méthodes directes pour ce besoin cependant , il existe des méthodes de traitement des nombre assez utile dans ces cas comme:
toFixed qui nous permet de limiter le nombre de décimales
Number.toLocaleString qui permet entre autre d 'afficher la virgule ou le point pour le séparateur de décimales
Bien même ces méthodes ca ne répond pas a notre besoin qui est de mettre les nombre au format monétaire voir (symbole)n nnn nnn ,nnnnnn(symbole)
il existe bien des plugins des librairie Javascript qui fournissent ces fonctionnalités . Mais je trouve que cette tache est bien plus simple pour inclure une librairie
Pour ca je propose cette fonction:
function formatMoney(num , localize,fixedDecimalLength){
          num=num+"";
   var str=num;
          var reg=new RegExp(/(\D*)(\d*(?:[\.|,]\d*)*)(\D*)/g)
          if(reg.test(num)){ 
       var pref=RegExp.$1;
       var suf=RegExp.$3;
       var part=RegExp.$2;
             if(fixedDecimalLength/1)part=(part/1).toFixed(fixedDecimalLength/1);
      if(localize)part=(part/1).toLocaleString();
str= pref +part.match(/(\d{1,3}(?:[\.|,]\d*)?)(?=(\d{3}(?:[\.|,]\d*)?)*$)/g ).join(' ')+suf ;
     };
  return str;
}
Voici quelques tests
formatMoney(5879);
//5 879
formatMoney(5879.158);
//5 879.158
formatMoney(5948794875358.158);
//5 948 794 875 358.158
formatMoney(5948794875358.158,true, 2)
//5 948 794 875 358,16
formatMoney("5948794875358.158")
//5 948 794 875 358.158
formatMoney("5948794875358.158€")
//5 948 794 875 358.158€
formatMoney("$5948794875358.158")
//$5 948 794 875 358.158
formatMoney("USD5948794875358.158")
//USD5 948 794 875 358.158
formatMoney(-5948794875358.158)
//-5 948 794 875 358.158










Cette méthodes est basée sur les expression régulière pour délimiter les séparation elle tient compte du fait qu on puisse ajouter des symboles monétaire avant et prés de l'usage de la virgule ou du point pour les décimale et bien sure de arrondi et réduction des décimales

Mise en forme des chaînes de date/heure -- JQuery JavaScript date format

Javascript fournit nativement toutes les fonctionnalités de traitement des dates et heures , Cependant , dès qu’il s'agit de la présentation des dates au formats humains je dirais on dispose plus de méthodes directes qui nous permettent de faire ça . Les raisons a ça sont fort nombreuses notamment la différences de culture de langues des internautes chacun a besoin en effet d'un format bien particulier . De ce fait il existe un formatage pour chaque langue et culture ici on présentera les dates au format français qui pourra bien sure être traduit dans d autre cultures : Venons au but : On citera d'abord les méthodes de formatage de date déjà connues et utilisées leurs avantages et limites avant de présenter notre solution ici .
La méthode JQUERY
JQueryUI grâce au datepicker nous permet de formater une date et de la traduire également pour l'utiliser faut bien sure inclure JQuery et JQueryUI



 
 


et de traduire les noms des mois des jours comme fournit dans JQueryUI ce qui se fait comme suit :


var dateSettings={
       //noms des mois 
      monthNames: ['Janvier','Fèvrier','Mars','Avril','Mai','Juin',
   'Juillet','Août','Septembre','Octobre','Novembre','Décembre'], 
         //noms des courts  
   monthNamesShort: ['Jan', 'Fev', 'Mar', 'Avr', 'Mai', 'Jun',       'Jul','Aou', 'Sep', 'Oct', 'Nov', 'Dec'], 
                        
         //noms des jours           
  dayNames: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi','Vendredi', 'Samedi'],
                 
  //noms des jours courts              
  dayNamesShort: ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']
       }  





A present on peut utiliser directement les methodes de JQueryUI


 $.datepicker.formatDate("d M yy",new Date(1995,11,11),dateSettings);
//11 Dec 1995
 $.datepicker.formatDate("DD, dd MM yy",new Date(1995,11,11),dateSettings);
// Lundi, 11 Décembre 1995
$.datepicker.formatDate("yy-mm-dd",new Date(1995,11,11),dateSettings);
//11-12-1995
//.......voir jquery datepicker pour les autres formats de date disponibles 



Citiques :
- Avec JQuery datepicker on peut formater les dates et heures correspondants a la plus part des formats dates connus ATOM,COOKIE,ISO_8601....
- Pour utiliser cette méthode on est obligé d inclure JQuery et JQueryUI du moins le datepicker ce qui est parfois lourd si on a juste une simple page
- On pourra citer quelques anomalies a cette méthode
   $.datepicker.formatDate("Aujourd'hui : dd-mm-yy",new Date(),dateSettings);
   //Auj4ur4hui : dd-mm-yy
   //le d et le o de Aujourd'hui est remplace par la date du jour 
   //la présence de l apostrophe a empêcher de finir le formatage  
   //........
//.......

Autres méthodes
Voici une fonction Javascript qui permet de formater la date au format les plus divers et même d’utiliser d'autres chaines dans le format
  function formatDate(date,format,dateSettings){
      var d=date.getDate();
      var dy=date.getDay();
      var y=date.getFullYear();
      var M=date.getMonth();
      var H=date.getHours();
      var m=date.getMinutes();
      var s=date.getSeconds();
      var z=date.getTimezoneOffset()/60*100;
      z = z<0?"+"+Math.abs(z):"-"+Math.abs(z);
       var matches={
          "M{4,}"  :     [ "g",  dateSettings.monthNames[M]],
           "M{3}"    :     [ "g",    dateSettings.monthNamesShort[M] ],
            "M{2}"    :     [ "g", M<9?"0"+(M+1):M+1 ],
             "M{1}"   :     [ "g",  M+1  ],
              "Y{3,}"  :     [ "gi",   y    ],
               "Y{1,2}" :     [ "gi",  (y+"").substring(2) ],
             "D{4,}"  :     [ "gi",  dateSettings.dayNames[dy] ],
              "D{3}"   :      [ "gi", dateSettings.dayNamesShort[dy] ],
               "D{2}"   :      [ "g",  dateSettings.dayNames[dy] ],
                "D{1}"   :      [ "g", dateSettings.dayNamesShort[dy]] ,
               "d{2}"   :      [ "g", d<9?"0"+d:d   ],
                "d{1}"   :      [ "g", d            ],
                "m{2,}"  :      [ "g",  m<9?"0"+m:m    ],
                 "m{1}"   :      [ "g",  m              ],
                 "s{1}"   :      [ "gi",   s            ] ,
    "s{2}"   :      [ "gi",  s<9?"0"+s:s   ]  ,
    "H{1}"   :      [ "gi",  H             ] ,
     "H{2,}"  :      [ "gi",  H<9?"0"+H:H ] ,    
     "Z{1,}"  :      [ "gi",   "GMT"+z ] ,    
     
       }
       for(var match in matches){
             var reg=new RegExp("(^|[\\W]+)("+match+")($|[\\W]+)" , matches[match][0]);
     format= format.replace(reg , "$1"+matches[match][1]+"$3");
       }
     return format 
    }


Voici quelques tests de cette fonctions
formatDate(new Date(),"dd-MM-YY",dateSettings);
//04-01-13
formatDate(new Date(),"DD dd-MMM-YYY",dateSettings)
//Vendredi 04-Jan-2013
formatDate(new Date(),"D dd-MMM-YYY",dateSettings);
//Ven 04-Jan-2013
formatDate(new Date(),"DDDD, dd MMMM YYYY",dateSettings)
//Vendredi, 04 Janvier 2013
formatDate(new Date(),"Aujourd'hui : DDDD, dd MMMM YYYY",dateSettings)
//Aujourd'hui : Vendredi, 04 Janvier 2013
formatDate(new Date(),"Aujourd'hui : DDDD, dd MMMM YYYY hh:mm ss",dateSettings);
//Aujourd'hui : Vendredi, 04 Janvier 2013 20:55 35

formatDate(new Date(),"Aujourd'hui : DDDD, dd MMMM YYYY hh:mm ss z",dateSettings);
//Aujourd'hui : Vendredi, 04 Janvier 2013 20:56 44 GMT+100
//.....
//....
//..... 
Bien sure cette méthode on pourrait la personnaliser a ses besoins et cas particuliers