1. Introduction

La bataille Navale est un jeu de société apparu au 20ème siècle. Mais malheureusement jouer à ce jeux est très souvent synonyme de lenteur, d'ennuis. En effet, il faut tout d'abord dessiner les grilles, décider du système de coordonnées, etc. commencer le jeu est long. Or ce n'est pas tout, même pendant la partie (interminable) on s'embrouille très souvent à qui était-ce le tour, qu'est-ce qu'il a marmonné, et sans parler des triches !

Le but de ce projet est de faire un jeu de bataille navale en ligne. Il y aura un serveur et une « page web » qui fera office de client. Chaque utilisateur ouvrira son browser favori et accèdera à la page de login. Ainsi le serveur mettra en relation des joueurs et ils pourront enfin s’amuser.

2. Matériel et méthodes

2.1 Matériel

Ordinateur sur lequel on pourra lancer le serveur.

Matériel numérique :

  • Java (1.8.0)
  • Serveur MySQL
  • Apache Tomcat (9.0.12)
  • Maven (3.5.4)
  • JQuery
  • Framework Spring (5.0.9_Release)

2.2 Méthode

2.2.1 Le Jeu

Avant de commencer quoi que ce soit à ce projet, faisant un petit rappel des règles les plus importantes. La bataille navale se joue à deux. Les deux joueurs ont disposé leur bateau dans une zone quadrillée 10x10; il y a plusieurs bateaux de différentes longueurs orientés verticalement ou horizontalement. Il faut garder les positions de sa flotte secrète et le but est de gagner en faisant « couler » toute la flotte de l’ennemi en premier. C’est un jeu de tâtonnement on choisi une coordonnée de largement d’une bombe et si un bateau est touché nous avons le droit de retirer une bombe, sinon c’est au tour de l’autre à larguer une bombe.

Voici un premier croquis de ce qu’un utilisateur verrait sur son écran. Nous voyons qu’il a deux zone de plateau : à gauche celui de sa flotte qui est bombardée par l’ennemi et à droite la zone de la flotte ennemie dont il faut faire couler les bateaux au plus vite !

firstIdeas.png Figure 1 - Premières idées du résultat (interface)

2.2.2 API

L’API (Interface de programmation) est très important pour ce projet. En effet c’est la manière que le serveur et les clients devront communiquer. J’utilise le protocole HTTP, et la majorité des « messages » entre le client et le serveur est sous la forme de JSON. C’est un format de données très populaire qui contient de l’information structurées. Par exemple des listes, mais aussi des valeurs (nombres, chaînes de caractères, etc). Ce format est organisé en paires : nom, la clé et la valeur

Voici donc toutes les requêtes que l’on pourra (en tant que client) faire sur le Serveur :

	POST sur  /api/shoot
{
	"x": 1,
	"y": 1
}

Ici nous voyons qu’il faut simplement faire un tir en donnant les coordonnées. Le serveur devrait donc déterminer (après s’être login) quel joueur fait ce tir et donc connaîtra la partie qui est jouée.

GET sur  /api/mygame
{
	"id": "xxxx",
	"winner":  "",
	"opponent": "Mike",
	"myboats" : [
		{ "x": 1, "y": 1, "lenght": 6, « direction »:2},
		{ "x": 5, "y": 5, "lenght": 3, « direction »:3},
		{ "x": 1, "y": 1, "lenght": 5, « direction »:2},
		…
 	],
	"myshoots" : [
		{ "x": 1, "y": 1, "touchedABoat": true},
		{ "x": 3, "y": 2, "touchedABoat": false},
		{ "x": 6, "y": 1, "touchedABoat": true},
		…
 	],
	"oponnontshoots" : [
		{ "x": 1, "y": 5, "touchedABoat": true},
		{ "x": 8, "y": 2, "touchedABoat": false},
		{ "x": 6, "y": 1, "touchedABoat": true},
		…
 	],

	" current": "Trump"
}

Grâce à cet exercice d’écriture de l’API, nous voyons ici que le résultat de cette requête est très personnalisée pour le client. En effet il y a « my boats » et heureusement, car si on retournait tout ce que le serveur connaît au sujet de cette partie, nous verrions aussi les bateaux ennemis (ce qui est évidemment déconseillé de faire car les coordonnées de nos bateaux sont sensés rester top secrètes.

POST sur  /api/newgame
{
 	"myboats" : [
 		{ "x": 1, "y": 1, "lenght": 6, « direction »:2},
 		{ "x": 5, "y": 5, "lenght": 3, « direction »:3},
 		{ "x": 1, "y": 1, "lenght": 5, « direction »:2},
 		…
  	]
 }

Le système de coordonnées utilisé pour toutes ces requêtes est avec un axe x depuis en haut à gauche vers la droite, et un axe y du haut de la gauche vers le bas (système de coordonnées utilisées pour les écrans d’ordinateur).

2.2.3 Back End

Le serveur est la partie centrale de ce projet, toute la logique et les règles du jeu, de la « sécurité » des données y sont modélisés. Le choix final des technologies utilisées c’est tourné autour de Java.

2.2.3.1 Serveur Apache Tomcat

Le conteneur web Tomcat va s’occuper de recevoir les requêtes faites à l’ordinateur pour les rediriger sur des Servlets, et donc le programme Java en lui-même.

2.2.3.2 Framework Spring

Sachant qu’il va falloir récupérer les requêtes, utiliser une base de données MySQL, etc. il est plus simple d’utiliser un Framework qui est une couche au dessus des Servlets et simplifie toute cette partie de configuration. Ce framework s’appelle Spring.

2.2.3.3 MVC

Dans un projet informatique complexe liant interface, graphique, logique, réseau, etc. Il est primordial de diviser le problème. Model-View-Controller est un pattern (patron de conception) très pratique. Model est la partie logique, la View se réfère ici plus à la partie cliente (HTML, JavaScript) et Controller qui correspond aux points d’entrées de l’API sur lesquelles ont peut questionner le serveur.

2.2.3.4 Model

Dans le serveur, la partie purement logique, Java est un Language orienté objet, il est donc normal d’employer ce paradigme de programmation ici. Voici après des heures de réflexion la structure du programme

schemaUML.png Figure 2 - Structure du Model

Un player représente justement le joueur d’une Game. Instancier une Game avec juste un seul Player (car il attend un adversaire) n’a pas de sens, c’est pourquoi le stade « précédant » la partie en train d’ être jouée s’appelle NewGame.

import java.util.Random;
 
public class NewGame {
 
   private final Player playerA;

   NewGame(Player playerA) {
       this.playerA = playerA;
   }

   Game start(Player playerB) {
       return new Game(playerA, playerB, (new Random().nextInt(2) == 0) ? playerA : playerB);
   }

}

Et il faut faire .start() sur NewGame pour enfin démarrer une Game. Dont le premier joueur est tiré aléatoirement ici.

public class Game {
   private Player playerA;
   private Player playerB;
   private Player current;

   public Game(Player playerA, Player playerB, Player current) {
       this.playerA = playerA;
       this.playerB = playerB;
       this.current = current;
   }

   public void shoot(Player player, int x, int y) {
       if (isHisTurn(player)) {
           if (getOpponent().hasBeenTouched(x, y)) {
               player.saveShoot(new Shoot(x, y, true));
           } else {
               player.saveShoot(new Shoot(x, y, false));
               switchCurrent();
           }
       } else {
           System.out.println("not your turn");
       }
   }

   private Player getOpponent() {
       return (current == playerA) ? playerB : playerA;
   }

   private boolean isHisTurn(Player player) {
       return player == current;
   }

   private void switchCurrent() {
       current = (current == playerA) ? playerB : playerA;
   }
}

Les attributs d’une Game sont un joueur A, un joueur B, et un joueur étant le joueur current (celui qui doit désormais tirer une bombe). Un joueur A voulant tirer va faire .shoot() sur une Game. C’est ici que le résultat de ce tir est interprété, le joueur a-t-il droit de retirer une nouvelle bombe, est-ce que c’est bel est bien le tour à ce joueur-ci de tirer, etc.

Jetons un coup d’oeil sur la classe Player

import java.util.ArrayList;
import java.util.List;
 
class Player {
   private List<Ship> ships;
   private List<Shoot> shoots;

    Player(List<Ship> ships) {
       this.ships = ships;
       this.shoots = new ArrayList<>();
   }


   public boolean hasBeenTouched(int x, int y) {
       return ships.stream().anyMatch(ship -> ship.hasBeenTouched(x, y));
   }

   public void saveShoot(Shoot shoot){
       this.shoots.add(shoot);
   }
}

Un player a une liste de bateaux et une liste de tirs. Pour savoir si un joueur est touché, on demande simplement .hasBeenTouched(x, y) mais nous savons bien que la question est si un bateau appartenant au joueur a été touché (simple parcours de la liste à l’aide des streams). Et il est possible d’ajouter un tir avec .saveShoot().

Un shoot est en fait juste un point et a deux statuts : soit il a percuté un bout de bateau, soit il a percuté l’eau. Donc :

public class Shoot {
    private int x;
    private int y;
    private boolean touchedABoat;

    public Shoot(int x, int y, boolean touchedABoat) {
        this.x = x;
        this.y = y;
        this.touchedABoat = touchedABoat;
    }
}

Nous voyons que tout tend à graviter autour des bateaux, il est modélisé comme suit :

public class Ship {
   private int x, y;
   private int lenght;
   private Direction direction;

   public Ship(int x, int y, int lenght, Direction direction) {
       this.x = x;
       this.y = y;
       this.lenght = lenght;
       this.direction = direction;
   }

   public boolean hasBeenTouched(int x, int y) {
       for (int i = 0; i < lenght; i++) {
           int xe = this.x + direction.deltaX*i;
           int ye = this.y + direction.deltaY*i;
           if (xe == x && ye == y) {
               return true;
           }
       }
       return false;
   }
 
   public enum Direction {
       RIGHT(1, 0),
       LEFT(-1, 0),
       UP(0, -1),
       DOWN(0, 1);

       private final int deltaX;
       private final int deltaY;

       Direction(int deltaX, int deltaY) {
           this.deltaX = deltaX;
           this.deltaY = deltaY;
       }
   }
}

Un Ship aurait peu être modélisé comme étant une liste de cases faisant office de bout de bateau, mais cela n’est pas très pratique pour la partie cliente. Non seulement on utilise de la bande passante pour rien, mais pour le déboggage du JSON c’est aussi illisible.

Ici un Ship a un attribut x, y, length et une direction. Ce sont les informations minimales pour situer la position d’un bateau. x et y sont les coordonnées d’un « bout" de bateau (une case de quadrillage) et à partir de ce point ce suivent les autres « bouts » de ce bateau.

2.2.3.5 View

La View pour le serveur ne sont que des fichiers .html, .css, .js. Rien d’autre.

Cf 2.2.4 pour le contenu et le fonctionnement interne du client.

2.2.3.6 Login&Register

Le but ici est de pouvoir se logger et s’enregistrer sur le serveur. Le fonctionnement de base de logis sera utilisé. Après que le serveur aie reconnu un utilisateur, le serveur va renvoyer un token (sous la forme d’un cookie) dont le client enverra toujours dans le header de toutes ses requêtes. Il y aura donc une base de données avec une table USERS contenant les colonnes USERNAME et PASSWORD. Et une table TOKENS avec les colonnes USERNAME et TOKEN. Cette dernière permet justement de conserver en mémoire les token valides.

Cette partie est très longue et je ne vais pas tout détailler. Mais voici les points clefs :

2.2.3.6.a Spring

Pour ajouter le Framework Spring nous devons le mentionner dans le fichier web.xml. Notamment :

<servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

Il faut aussi ajouter la classe Spring dans le dossier config où l’on déclare les Controller, etc. Spring arrive le « trouver » grâce aux @Bean que l’on ajoute au dessus de chaque méthodes.

2.2.3.6.b Filtres

La classe SecurityFilter implémente la classe Filter. Cela permet de filtrer les requêtes et de directement rejeter les requêtes non autorisées. Notamment la nécessité ou pas d’être connecté pour accéder à un chemin.

private final static Map<String, List<String>> SKIP_AUTH_PATHS = new HashMap<String, List<String>>() {{
    put("GET", asList("/login", "/register", "/css", "/javascript", "/img"));
    put("POST", asList("/login", "/account/create"));
}};

Et la méthode surchargée (override) doFilter() ajoute cette logique de filtrage :

@Override
public void doFilter(ServletRequest req, ServletResponse resp,  FilterChain chain) 
         throws IOException, ServletException {
    final HttpServletRequest request = (HttpServletRequest) req;
    if (!needsAuthentication(request)) {
        chain.doFilter(req, resp);
        return;
    }
    final Optional<Account> account = authenticate(request);
    if (account.isPresent()) {
        request.setAttribute("account", account.get());
        chain.doFilter(request, resp);
        return;
    }
    final HttpServletResponse response = (HttpServletResponse) resp;
   response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}

On cast ServletRequest en HttpServletRequest et si il n’as pas besoin d’être authentifié, c’est bon la requête est autorisée (et il continue son chemin vers les controllers). Si il a besoin d’être authentifié, on cherche dans les cookies si l’utilisateur match. Si il existe c’est que c’est bon il est autorisé à accéder. Et finalement si l’utilisateur (par exemple sans cookies, tentant d’accéder un lieu où il faut être logger) alors il ne sera pas autorisé; ce qui fait qu’il ne pourra même pas accéder aux Controller.

2.2.3.6.c Login Controller

Pour que le client s’identifie, il doit se logger avec le bon nom d’utilisateur et le nom mot de passe. C’est ce qui est justement le but du LoginController

@ResponseBody
@RequestMapping(method = POST)
public ResponseEntity login(@RequestBody Credentials credentials, HttpServletResponse response) {
    final String username = credentials.username();
    final String passwordMD5 = DigestUtils.md5DigestAsHex(credentials.password().getBytes(UTF_8));
    final String token = accounts.login(username, passwordMD5);
    response.addCookie(createCookie(token));
    return ResponseEntity.ok().body(token);
}

Dans le loginController, le client fait un POST sur /login, et bien il va relayer le boulot à accounts pour vérifier dans la base de donnée que l’utilisateur avec ce mot de passe existe bien. Pour le pas enregistrer les mot de passes en clair (#FaceBook) il est plus judicieux de hacher les mots de passes en MD5 et de les stocker sur la base de donnée aussi.

2.2.3.6.d Accounts

La classe Accounts est centrale sur la gestion du login et register. C’est comme un pont pour franchir la rivière.

public class Accounts {

    private final AccountsDAO accountsDAO;
    private final TokensDAO tokensDAO;
    public Accounts(AccountsDAO accountsDAO, TokensDAO tokensDAO) {
        this.accountsDAO = accountsDAO;
        this.tokensDAO = tokensDAO;
   }

   public Optional<Account> authenticate(String token) {
       final Optional<AccountData> account = tokensDAO.find(token);
       return account.map(data -> new Account(data.username, accountsDAO, tokensDAO));
   }

   public String login(String username, String password) {
       if (!accountsDAO.find(username, password).isPresent()) {
           throw new NotAuthorizedException("Invalid username or password");
       }
       return tokensDAO.create(username);
   }

   public String create(String username, String password) {
       accountsDAO.create(username, password);
       return tokensDAO.create(username);
   }
}

Il a deux attributs AccountsDAO et TokensDAO (explication chapitre suivant). La méthode login demande à AccountsDAO si cet utilisateur existe et si tout est correct, TokensDAO doit créer un nouveau token. De même pour la création du compte create() sauf que lui, il le fait à tous les coups.

2.2.3.6.e JDBC

L’API JDBC vient de Java Database Connectivity permet à notre programme d’avoir une façon de se connecter sur « tout » type de bases de données relationnelles. Il faut donc l’importer dans le pom.xml…

2.2.3.6.f DAO

Data Access Object ou objet d’accès aux données est un pattern qui permet de diviser la structure globale du projet en une zone à part. Une zone où les DAO comme AccountsDAO et TokensDAO qui décrivent des tables d’une base de données. L’utilité est de pouvoir demander à « la objet » ce qu’il y a actuellement de stocké dans une table. Voici AccountsDAO :

public class AccountsDAO {
   private final JdbcTemplate jdbcTemplate;

   public AccountsDAO(JdbcTemplate jdbcTemplate) {
       this.jdbcTemplate = jdbcTemplate;
   }

   public Optional<AccountData> find(String username, String password) {
       final RowMapper<String> rowMapper = (resultSet, i) -> resultSet.getString(USERNAME.name());
       final List<String> users = new Select(USERNAME)
               .from(USERS)
               .where(USERNAME).is(username)
               .and(PASSWORD).is(password)
               .execute(jdbcTemplate, rowMapper);
       return users.stream().findFirst().map(AccountData::new);
   }

   public void create(String username, String password) {
       new InsertInto(USERS)
               .set(USERNAME, username)
               .set(PASSWORD, password)
               .set(ROLE, "ROLE_USER")
               .set(ENABLED, 1)
               .execute(jdbcTemplate);
   }

   public boolean update(String username, String oldPasswd, String newPasswd) {
       return new Update(USERS)
               .set(PASSWORD, newPasswd)
               .where(USERNAME).is(username)
               .and(PASSWORD).is(oldPasswd)
               .execute(jdbcTemplate) > 0;
   }

   public boolean delete(String username, String password) {
       return new Delete(USERS)
               .where(USERNAME).is(username)
               .and(PASSWORD).is(password)
               .execute(jdbcTemplate) > 0;
   }
}

Il y est implémenté des opérations simples à effectuer sur cette table : find(), create(), delete()… Mais un élément intéressant est de jeter un coup d’oeil sur l’implémentation. On pourrait s’attendre à faire des choses du genre « SELECT * FROM … » mais ce n’est pas très joli. Mais cela est bel et bien fait cela est possible grâce aux « classes-outils » se situant dans le dossier sql. Ils font exactement la même choses que « SELECT * FROM »… mais par programmation. Ces classes font donc une interface entre le programme (Java) et la base de donnée. Dont les requêtes finales générées sont exécutées vers la base de données en passant par l’API JDBC mentionné précédemment.

2.2.3.7 Controller

Le Controller est littéralement la charnière entre le serveur et le client. Il reçoit les requêtes et il ne doit que les interpréter main dans la main avec le Model.

Les Controller sont déclarés dans le fichier Spring avec les @Bean comme dit précédemment. Mais l’implémentation de ces controllers sont les plus utiles pour le client. Car c’est grâce au chemin de l’url que Tomcat et Spring arrivent à finalement rediriger la question sur les Controllers.

L’APIController est le chaînon liant entre le client et la Model :

List<Game> games;
List<NewGame> newGames;
 
public ApiController() {
   games = new ArrayList<>();
}
 
@ResponseBody
@RequestMapping(value = "/api/shoot", method = PUT, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Boolean> shoot(@RequestAttribute("account") 
       Account account, @RequestBody ShootData shootData) {
   Game game = getGameWithAccount(account);
   game.shoot(getPlayerWithAccount(account, game), shootData.getX(), shootData.getY());
   return new ResponseEntity<>(true, HttpStatus.CREATED);
}
   
@ResponseBody
@RequestMapping(value = "/api/newgame", method = PUT, produces = MediaType.APPLICATION_JSON_VALUE)
public String newGame(@RequestAttribute("account") 
      Account account, @RequestBody NewGameData newGameData) {
   Player player = new Player(newGameData.getBateaux());
   if (this.newGames.isEmpty()) {
       this.newGames.add(new NewGame(player));
       return "play";
   } else {
       this.newGames.get(0).start(player);
       return "wait";
   }
 }
  
@ResponseBody
@RequestMapping(value = "/api/mygame", method = PUT, produces = MediaType.APPLICATION_JSON_VALUE)
public MyGameData getMyGame(@RequestAttribute("account") Account account) {
   Game game = getGameWithAccount(account);
   Player player = getPlayerWithAccount(account, game);

   final List<BateauData> bateauDataList = 
               player.getShips().stream().map(BateauData::new).collect(Collectors.toList());
   final List<ShootAllInfosData> myshootDataList = 
               player.getShoots().stream().map(ShootAllInfosData::new).collect(Collectors.toList());
   final List<ShootAllInfosData> hisshootDataList = 
               getOponnent(player, game)
                     .getShoots().stream().map(ShootAllInfosData::new).collect(Collectors.toList());

   return new MyGameData(null, getOponnent(player, game).getName(), bateauDataList, 
                               myshootDataList, hisshootDataList, game.getCurrent().getName());
}

En paramètres de toutes ces méthodes, Spring va injecter le Account. C’est-à-dire quelle est le compte qui a fait cette requête. Les classes xxxData sont simplement des classes qui permettent de « créer » le contenu JSON avec des objets (c’est une librairie qui fait cela).

2.2.4 Front End

La partie Front End permet aux clients de finalement jouer à la bataille navale. Il doit y avoir principalement deux modes d’affichage pour le client. Un mode création où l’utilisateur positionner ses bateaux en drag&drop sur son plateau et un deuxième mode où le jeu est en cours de route : avec les deux plateaux.

Ces deux modes correspondent à deux div nommées : #playingModeZone et #creationModeZone. Qui sont générées et supprimées à volonté.

function enterPlayingMode() {
   $("#center").append(
       "<div id=\"playingModeZone\">\n" +
       "   <div class=\"plateau\" id=\"plateauUn\"></div>\n" +
       "   <div class=\"plateau\" id=\"plateauDeux\"></div>\n" +
       "   <div id=\"bateauxRestant\">Bateaux restants :\n" +
       "        <p id=\"bateauxRestantTexte\">5</p>\n" +
       "   </div>\n" +
       " </div>");
   $("#plateauUn").css("margin-left", "30%");
 
   buildPlayingPlateaux()
}

function buildPlayingPlateaux() {
   $('#plateauUn').append('<table></table>');
   var table = $('#plateauUn').children();
   for (i = 0; i < 10; i++) {
       table.append('<tr>');
       for (j = 0; j < 10; j++) {
           table.append('<td class=\"water ' + j + i + '\">' + j + i + '</td>');
       }
       table.append('</tr>');
   }
 
 
   $('#plateauDeux').append('<table></table>');
   var table = $('#plateauDeux').children();
   for (i = 0; i < 10; i++) {
       table.append('<tr>');
       for (j = 0; j < 10; j++) {
           table.append('<td>' + j + i + '</td>');
       }
       table.append('</tr>');
   }
}

Le language utilisé ici est évidemment du Javascript, mais pour rendre les choses plus faciles à écrire et plus faciles, on utilise ici la bibliothèque jQuery. Ce code est exécuté du côté client et ça fait une chose de moins à penser lors de la conception du serveur.


function enterCreationMode() {
   $("#center").append("" +
       "<div id='creationModeZone'>" +
       "   <div id=\"creationWaitingBoats\">\n" +
       "        <p>place these boats (7) :</p>\n" +
       "   </div>\n" +
       "   <div class=\"plateau\" id=\"creationPlateau\"></div>" +
       "   <input type=\"submit\" title=\"OK\">" +
       "</div>");
   $("#creationWaitingBoats").css("margin-left", "30%");


   buildCreationPlateau();

   addBoatToListForBuldingGame(5);
   addBoatToListForBuldingGame(4);
   addBoatToListForBuldingGame(3);
   addBoatToListForBuldingGame(2);
   addBoatToListForBuldingGame(2);
   addBoatToListForBuldingGame(1);
   addBoatToListForBuldingGame(1);
}

function buildCreationPlateau() {
   $('#creationPlateau').append('<table></table>');
   var table = $('#creationPlateau').children();
   for (i = 0; i < 10; i++) {
       table.append('<tr>');
       for (j = 0; j < 10; j++) {
           var name = "creationCase" + j + i;
           table.append('<td class="ui-droppable ui-droppable-active 
                     water" id=\"' + name + '\">' + j + i + '</td>');
           $("#" + name).droppable({
               drop: function (event, ui) {  //handle drop event
                   const targetId = $(event.target).prop('id').toString();
                   const targetCoords = targetId.slice(-2);
                   // update new id :
                   const toto = ui.draggable.prop('id'); // form : 5boat10to1
                   const mess = toto.substr(0, 5) + targetCoords + toto.substr(7);
                   $("#" + toto).attr("id", mess);
                   // positioning rectangle
                   $(ui.draggable).offset($(this).offset());
               }
           });
       }
       table.append('</tr>');
   }
}

function exitCreationMode() {
   $("#creationModeZone").remove();

   $("#plateauUn").show();
   $("#plateauDeux").show();
   $("#bateauxRestant").show();
   $("#creationWaitingBoats").remove();
   $("#creationPlateau").remove();

   $("#plateauUn").css("margin-left", "30%");
}

function addBoatToListForBuldingGame(lenght) {
   $("<div></div>", {
       "class": "draggableRectangle",
       "id": lenght + "boatxxto1",
       "style": "width:" + (30 * lenght) + "px; height:30px",
       text: " ",
       draggable: true
   }).appendTo("#creationWaitingBoats");

}

Ainsi il est très facile d’entrer de changer de mode.

Il ne manque plus que de faire les requêtes avec le serveur pour décider quoi afficher. Mais dommage, il manque de temps.

3. Résultats

Le résultat est le suivant :

creatingmode.png Figure 3 - Avant de jouer

playingmode.png Figure 4 - Pendant le jeu

4. Discussion

Concrètement on ne peut malheureusement pas jouer à la Bataille Navale car le dernier chaînon liant n’est pas connecté. En effet il manque plus que le client fasse les requêtes qu’on parle tant! Mais il n’y a pas tout qui est inutilisable dans la partie cliente. La page de login et la page d’enregistrement marchent à merveille. Le système de compte qui permet de bloquer la triche et donner accès uniquement à des individus précis sur la disposition des bateaux. J’ai jugé plus important de terminer cette parte plutôt que d’avoir un jeu à moitié jouable et détourné en quelques clics.

La partie serveur est sans aucun doute la partie la plus intéressante du projet (même si le drag&drop en HTML était amusant aussi :) )

La conception du projet, la réflexion sur comment modéliser les règles du jeu, sur comment structurer/diviser le serveur en étapes . Ainsi que la réflexion sur comment informatiquement lier différentes technologies en employant plusieurs pattern du mieux que je pouvais. C’était vraiment amusant.

Comme tout bon projet, il y a toujours des choses à améliorer, mais aussi - ici - à terminer

Client :

  • Terminer la partie cliente : faire les requêtes vers le serveur et afficher les bonnes choses à l’écran.
  • Rendre la page plus attractive visuellement : La rendant responsive pour qu’il s'adapte aux smartphones.

Serveur :

  • Déterminer le gagnant d’une partie
  • Ne pas utiliser le username comme id, faudrait évidemment plutôt avoir un numéro de série ou autre. Car on ne sait jamais suivant les caractères qu’un utilisateur met il risque probablement d’avoir des bugs.
  • Gérer tout ces cas limites, comme si le serveur redémarre on perd les parties qui étaient en train d’être jouées.
  • Stocker non seulement les utilisateurs dans la base de donnée, mais aussi les parties. Comme ça il serait possible de reprendre une partie plus tard
  • Ecrire des tests pour vérifier le fonctionnement général du programme. Car les test unitaires étaient souvent irrelevant dans ce projet.

Finalement c’était très amusant de faire ce projet, je regrette tout de même ne pas l’avoir complètement terminé, qu’on aie au moins peu faire un semblant de jouer. Mais bon c’est pas si grave que ça.

5. Conclusion

Ce projet est un bon bac à sable, si nous avions encore un projet Q, ce serait sympa de pouvoir l’améliorer et de continuer à ajouter des fonctionnalités comme par exemple la possibilité d’envoyer un lien d’invitation à un ami pour jouer avec lui; ou d’avoir un chat en ligne pour tacher/commenter en direct; ou pourquoi pas avoir à la page d'accueil un classement général pour savoir qui est le meilleur; ou même, mettre de la publicité sur les bords pour gagner de l’argent et rembourser la consommation des serveurs. Bon peut-être que cette dernière idées n’est pas si ouf que ça 8D

Références

Serveur

https://spring.io/guides (dernièrement consulté le 16 mai 2019)

https://spring.io/projects/spring-security (dernièrement consulté le 16 mai 2019)

https://www.baeldung.com/spring-bean (dernièrement consulté le 16 mai 2019)

https://docs.spring.io/spring-security/site/docs/current/guides/html5/form-javaconfig.html (dernièrement consulté le 16 mai 2019)

https://www.baeldung.com/spring-security-login (dernièrement consulté le 16 mai 2019)

https://en.wikipedia.org/wiki/Java_Database_Connectivity (dernièrement consulté le 16 mai 2019)

http://www-igm.univ-mlv.fr/~dr/XPOSE2003/tomcat/tomcat.php?rub=16 (dernièrement consulté le 16 mai 2019)

https://www.jmdoudoux.fr/java/dej/chap-spring.htm (dernièrement consulté le 16 mai 2019)

https://stackoverflow.com/questions/32019353/adding-a-custom-login-controller-with-spring-security (dernièrement consulté le 16 mai 2019)

https://hellokoding.com/registration-and-login-example-with-spring-security-spring-boot-spring-data-jpa-hsql-jsp/ (dernièrement consulté le 16 mai 2019)

https://www.theserverside.com/news/1363858/Introduction-to-the-Spring-Framework (dernièrement consulté le 16 mai 2019)

https://stackoverflow.com/questions/17193365/what-in-the-world-are-spring-beans (dernièrement consulté le 16 mai 2019)

https://www.baeldung.com/java-dao-pattern (dernièrement consulté le 16 mai 2019)

https://openclassrooms.com/fr/courses/26832-apprenez-a-programmer-en-java/26830-liez-vos-tables-avec-des-objets-java-le-pattern-dao (dernièrement consulté le 16 mai 2019)

https://spring.io/guides/gs/accessing-data-mysql/ (dernièrement consulté le 16 mai 2019)

https://stackoverflow.com/questions/11861817/creating-a-ssh-tunnel-passing-through-a-bridge-machine (dernièrement consulté le 16 mai 2019)

Client

https://jquery.com (dernièrement consulté le 16 mai 2019)

https://jqueryui.com (dernièrement consulté le 16 mai 2019)

https://www.w3schools.com/jquery/ (dernièrement consulté le 16 mai 2019)

https://www.w3schools.com/js/default.asp (dernièrement consulté le 16 mai 2019)

https://openclassrooms.com/fr/courses/510018-decouvrez-la-puissance-de-jquery-ui/509350-le-drag-drop (dernièrement consulté le 16 mai 2019)

https://developer.mozilla.org/fr/docs/Web/Guide/HTML/Formulaires/Sending_forms_through_JavaScript (dernièrement consulté le 16 mai 2019)

https://www.w3.org/Style/Examples/011/firstcss.fr.html (dernièrement consulté le 16 mai 2019)