Déployer une application web en production

La méthode de mise en production proposée ici est une méthode parmi d’autres, je ne prétend pas être un expert en la matière alors il y a peut-être des contre-indications ou mauvaises pratiques que je ne connais pas. Toujours est-il que j’aime les solutions simples et sécurisées, adaptées à mon modèle de développement.

Création d’un nouveau dépôt distant “bare”

Ce dépôt sera celui où l’on va “pusher” notre code pour enclencher le déploiement automatique de l’application. C’est un dépôt git un peu spécial, appelé “bare” sur lequel vous ne pourrez pas travailler. Il ne contiendra que le répertoire “.git” et c’est tout, aucun fichier de code.

Tapez les commandes suivantes :

cd
git init --bare prod.git
git clone prod.git/ ./mywww.git

Ajoutez ce dépôt à comme nouveau dépôt distant à votre dépôt de développement :

git remote add prod ssh://test@vps1234.ovh.net/home/test/prod.git

Vous devriez maintenant observer ce changement au niveau de votre développement local :

git remote -v
live    ssh://test@vps1234.ovh.net/home/test/prod.git (fetch)
live    ssh://test@vps1234.ovh.net/home/test/prod.git (push)
origin  https://gitlab.com/monprojet/mywww.git (fetch)
origin  https://gitlab.com/monprojet/mywww.git (push)

Poussez une première fois votre code :

git push prod master

Cette première étape est terminée, maintenant vous disposez d’un dépôt dédié à la livraison en production.

Script de déploiement

Maintenant, nous allons créer un script dans ce dépôt de production qui sera enclenché dès que du code est pushé. Cela s’appelle un hook dans le monde git. Pour cette action particulière, il doit s’appeler ‘post-receive’ et doit avoir le flag exécutable.

cd prod.git/hooks
touch post-receive
chmod +x post-receive 
nano post-receive 

Voici le contenu de mon script. Là, c’est complètement dépendant de votre application, ici j’utilise un back-end NodeJS et un front-end Vue.js

echo ‘post-receive: Triggered.’
cd /home/test/mywww.git
echo ‘post-receive: git check out ...’
git --git-dir=/home/test/prod.git --work-tree=/home/test/mywww.git checkout master -f
echo ‘post-receive: npm install...’

# Prepare front-end sources
cd client
npm install
echo ‘post-receive: building’
npm run build

# Prepare server sources
cd ../server/
npm install

# Restart server (depending of your supervisor)
# ...

echo ‘post-receive: → done.’

N’oubliez pas de redémarrer votre serveur à la fin de la mise à jour, cela dépend de votre moyen de superviser votre processus de serveur (pm2, systemd …).

Make Something Horrible (5)

Make Something Horrible est une Game Jam organisée régulièrement par le magazine papier (et Web maintenant) Canard PC. Le concours consiste à créer un jeu original, surtout drôle et forcément laid car créé par des gens sans talents graphiques. Moi compris ! Cette année j’ai décidé de participer en ayant comme objectif secondaire la découverte de certaines technologies et la mise en pratique d’architectures. Voici le cinquième et dernier chapitre.

Finitions et ajustements

La première livraison en “démo” d’un jeu est importante : vous peaufinez vos scripts de livraison et surtout cela vous permet d’avoir des premiers retours utilisateurs. Pour ma part, j’en ai eu plusieurs par le biais du forum de Canard PC, dans la section dédiée à la Game Jam. Très bien, il me reste une semaine, avec ces remarques et ma TODO list du précédent article j’ai de quoi remplir mes soirées.

Pouvoir spécial

Je savais qu’il manquait une chose essentielle : le pouvoir spécial de chaque joueur. Voici ce que j’ai ajouté : avant de commencer une partie, le joueur choisit son pouvoir spécial parmi trois :

  • Tempête sur le Camping : effacez des emplacements adverses
  • Mal de tête : bloquez le joueur adverse pendant plusieurs tours
  • Pétanque Master : videz la Bibine adverse

image

Le développement est assez simple côté serveur.

Effets visuels et sonores

Maintenant que l’architecture est stable, je peux ajouter des effets pour rendre le jeu plus sympatique. Je souhaitais ajouter des effets graphiques. Je pourrais le faire en SVG, mais je sais que les jeux HTML5 sont plus souvent réalisés en utilisant la balise ‘canvas’. Donc, j’aurais plus de chance de trouver du code source d’exemple d’effets graphiques.

J’ajoute donc une couche au sandwich HTML avec une balise canvas prennant tout l’écran.

image

Comme d’habitude, je crée un composant pour cela ce qui permet de découpler un peu les choses. Le premier effet que je mets est la puie dans le cas de l’enclenchement du pouvoir “Tempête”. Je récupère du code (libre) sur Internet et l’effet est lancé en parallèle avec un effet sonore de tempête. C’est l’effet sonore qui arrêtra l’effet graphique, toujours à l’aide de la librairie Howler.js :

if (action.effects[e] == 'rain') {
    this.$refs.canvasLayer.startRain();
    new Howl({
        src: ['sounds/rain.webm'],
        autoplay: true,
        onend : () => {
        this.$refs.canvasLayer.stopRain();
        }
    });
}

Efficace et simple. Pour information, c’est le serveur qui envoie l’ordre d’effet dans un tableau d’événements liés aux actions effectuées par la carte envoyée.

Ajustements de GamePlay

Pour rendre la jouerie plus sympa et moins longue, j’effectue les modifications suivantes :

  • Limite des emplacements à 5 points
  • L’IA choisit sa défense aléatoirement
  • Stratégie IA aléatoire (choix stratégiques)

Ainsi, les emplacements sont plus facilement prenables et moins défendable, pour le coup. Egalement, cela permettra de créer des cartes plus fortes, genre une attaque à 5 points, mais plus rares !

La modification fondamentale de GamePlay a été de complexifier la carte du Camping. En effet, en l’état, il n’y a pas de dimension stratégique : on conquiert les emplacements où l’on veut.

Je décide donc d’autoriser la pose des cartes uniquement sur des cartes adjacentes (horizontales ou verticales) à d’autres cartes du même camp (sauf le premier coup, bien entendu !!).

Evolutions graphiques

Attaquons nous maintenant à quelques ajouts graphiques finaux. Dans un premier temps, j’améliore la Police de caractères utilisée pour les nombres du Camping ; plus grosse et plus explicite, surtout entre le chiffre 1 et 7. Merci le forum Canard PC ! Nous avons maintenant par exemple le portrait des joueurs.

De nouvelles cartes sont développées à l’arrache pour rendre le jeu plus sympa.

Conclusion

Le compte à rebours est terminé, il est temps de livrer la version finale du jeu, non sans défauts. Il n’est pas très sympa à jouer à vrai dire, notamment parcequ’il est compliqué de le terminer. Bon, j’ai tout de même eu du plaisir à le développer, j’ai pu tester la technologie Web avec satisfaction pour créer un jeu en “local”. Bonne chance à tous pour cette GameJam !

Make Something Horrible (4)

Make Something Horrible est une Game Jam organisée régulièrement par le magazine papier (et Web maintenant) Canard PC. Le concours consiste à créer un jeu original, surtout drôle et forcément laid car créé par des gens sans talents graphiques. Moi compris ! Cette année j’ai décidé de participer en ayant comme objectif secondaire la découverte de certaines technologies et la mise en pratique d’architectures. Voici le quatrième chapitre.

Galères et bugs

Alors dans les chapitres précédents, j’expliquais que je voulais développer le back-end en C++. Malheureusement, j’ai eu des gros soucis de programmation Socket sous Windows (alors que sous Linux ça fonctionne au poil). Bon, pour ne pas perdre trop de temps dans le développement, je suis passé à Electron.js : le back-end se fera donc sous Node.js.

J’ai développé un serveur HTTP vite-fait bien fait, sans utiliser de framework pour éviter les dépendances, uniquement chargé de répondre aux requêtes sur une API REST.

On construit les cartes

Attaquons nous maintenant aux cartes. Côté serveur, je dispose d’un fichier JSON flexible listant toutes les cartes du jeu. Voici l’exemple du carte, modélisée par les paramètres suivants :

{
    "title": "Tente",
    "category": "defense",
    "picture": "tent",
    "text": "Planter sa tente marque le territoire du Campeur. You shall not pass, comme dirait l'autre.",
    "value": 1
}

Là, on entre dans le coeur du Game Play : forcément, les paramètres ont un impact sur les règles du jeu, pas encore totalement définies à ce moment là du développement. J’ai décidé de rester simple :

  • On définit trois catégories : défense (à jouer sur le plateau), attaque (à jouer contre l’adversaire ou le plateau) et bibine (pour remplir son niveau d’énergie)
  • Une carte a une valeur, un chiffre dont la signification dépendra de la catégorie
  • Le reste c’est du graphisme : un titre, un texte et une illustration

Normalement, je ne devrais pas passer beaucoup de temps dans le moteur de jeu, mais je ne sais absolument pas ce que donnera le jeu en terme de “jouerie” : facile ? difficile ? Ennuyant ? C’est ma première GameJame, je vous avoue que je n’avais pas anticipé cette crainte, de devoir attendre très tardivement dans le développement du jeu pour … jouer !

Au niveau du front-end, je dispose déjà d’un composant Card.js bien séparé (règle d’or : dans le doute, toujours créer un composant dédié !!). La vue générale passera l’objet JSON regroupant les propriété d’une carte à chaque carte affichée. Le composant Card.js se chargera de gérer l’affichage au bon endroit !

image

On construit la grille

Attaquons nous maintenant à l’autre élément central qui n’existe toujours pas : la grille repréentant le Camping. Normalement, cela devrait aller vite car j’ai déjà passé du temps sur le moteur de ‘drop’ générique. Je vais donc définir N rectangles.

Astuce : commencez par dessiner un rectangle en affichant ses contours, puis positionnez le au pif, ajustez les tailles et les positions. Ensuite, constuisez dans la fonction ‘created()’ du composant GameView.js la matrice en mémoire :

// Create the grid
for (let i = 0 ; i < 9; i++) {
    for (let j = 0 ; j < 3; j++) {
    this.grid.push( {
        state: 0,
        camp: "transparent",
        x: 765 + i*123,
        y: 190 + j*175
    });
    }
}

Pour chaque case, on maintient un statut (la valeur de défense de l’emplacement) et à qui appartient l’emplacement. Ici, astuce, on va utiliser cette propriété pour afficher la couleur du rectangle :

  • transparent : l’emplacement est à personne
  • blue : l’emplacement est au joueur humain
  • red: l’emplacement est au joueur adverse (ordinateur)

L’affichage de la grille est réalisée en utilisant le système de template efficace de Vue.js :

  <template v-for="g in grid">
    <rect ry="10" :x="g.x" :y="g.y" width="110" height="155" v-bind:style="{ fill: g.camp }" class="droppable"/>
    <text :x="g.x + 50" :y="g.y + 70" 
        font-family="Badaboom" 
        font-size="40">
        
    </text>
  </template>

Bingo c’est terminé ! Chaque carré aura une classe “droppable” comme vu au chapitre précédent.

image

On filtre les drop

C’est bien beau, mais pour le moment tous les zones de drop s’affichent. On va filter selon le type de carte sélectonnée. Pour cela, nous allons utiliser la propriété ‘data-xxx’ de chaque zone et la comparer avec le type de carte.

Nous modifions la fonction de sélection de zone en ajoutant l’index de la carte en cours de drag :

app.isSelected(d3.event.x, d3.event.y, parseInt(c.attr('index')));
//...
// On vérifie si la carte peut être jouée ici:
if (type == app.cards[card_id].category) {
    accept_drop = true;
} else if (type == 'trash') {
    accept_drop = true;
}

Et plus loin on teste donc le nom de cette zone par rapport à la propriété de la carte (le deck du joueur étant toujours accessible dans les data() du composant Vue.js). On accèpte toutes les cartes dans la poubelle du Camping, bien évidemment. Notre belle fonction se termine par retourner non seulement ce statut de drop autorisé ou non mais aussi la destination choisie par le joueur : joueur, grille, poubelle ou adversaire.

Côté serveur, on initialise le jeu et le protocole

Nous allons maintenant commencer à déclarer toutes les variables de notre jeu côté serveur : la grille, le niveau de Pastis de chaque joueur, les cartes de chaque joueur.

Côté protocole réseau, on reste dans le simple :

image

Pour chaque carte jouée par le joueur humain, on met à jour les différentes variables, on fait jouer l’IA et on renvoit tout le status du jeu : la grille, les cartes et les niveaux de Pastis.

Le front-end, à la réception, copiera ces statuts en local (section data()) et grâce au système réactif de Vue.js on n’a rien à faire, le framework va répercuter graphiquement tous les changements.

On s’évitera également côté serveur tout un tas de vérifications déjà bloquées par le front-end, on va rester simple et efficace pour cette Game Jam ; évidemment, dans le cadre pro ne faites pas ça …

Regardoons ce qu’il se passe dans le cas où l’on joue une carte dans la poubelle :

// On joue cette carte (pas de vérification côté serveur on est en Game Jam :)
if (destination == 'trash') {
    // on vire cette carte et on en tire une autre
    player_cards.splice(index, 1);
    player_cards.push(cards[getRandomInt(cards.length)]);
}

Et hop on revoie tout au client :

// On renvoit un objet contectant tout le statut du jeu que le front-end mettra à jour graphiquement
res.writeHead(200, {'Content-Type': 'application/json'});
res.end(JSON.stringify({ grid: grid, cards: player_cards }));

Là par exemple, on ne gère toujours pas le niveau de Pastis :(

On met à jour la grille

On continue sur notre belle lancée. Maintenant, si le joueur joue sur la grille on va la mettre à jour. La règle est la suivante :

  • Une carte défense jouée sur un emplacement vide : ok, on le prend
  • Une carte défense jouée sur un emplacement à nous : la valeur de l’emplacement augmente d’autant
  • Une carte attaque jouée sur un emplacement adverse : la valeur de l’emplacement réduit d’autant
  • Une carte attaque n’est valide que sur un emplacement pris par l’adversaire (le rouge dans notre cas)

On effectue une vérification locale pour filtrer les zones de drop acceptées ou non, puis lorsque la carte est lâchée (fin du drop), on l’envoie au serveur.

Côté serveur, on effectue la même vérification, on s’arrange et on anticipe : notre fonction de vérification de la carte jouée aura en paramètre le camp qui joue : camp rouge (joueur adverse, l’ordinateur) ou bleu (joueur humain). On réutilisera cette fonction lors du développement de notre IA.

Notre fonction de dialogue avec le serveur est assez simple : on utilise une Promesse, on envoie la carte jouée et on reçoit une mise à jour de tous les éléments du jeu que l’on va recopier en local. Vue.js fait le reste et met tout à jour graphiquement.

dropCard(dest, card_idx) {
    // On envoie au serveur notre carte jouée, en retour on reçoit la conséquence et le jeu de l'adversaire
    Api.sendCard({card_idx: card_idx, dest: dest }).then((action) => {
        console.log("Received: " + JSON.stringify(action));

        // Update the grid
        for (let i = 0 ; i < 9*3; i++) {
            this.grid[i].camp = action.grid[i].camp;
            this.grid[i].state = action.grid[i].state;
        }

        // Update the player's cards
        this.cards = action.cards;
    });
},

Voilà le résultat pour l’équipe Bleue, on commence à avoir du Game Play !!

image

On met à jour le niveau de Pastis

Là c’est tout simple, on a déjà l’aspect graphique de développé. Il suffit de communiquer avec notre composant enfant et lui envoyer la nouvelle valeur du niveau de Pastis :

Parent :

this.$refs.bibineBlue.updatePastisLevel(action.bibine); // le joueur humain, le bleu
this.$refs.bibineRed.updatePastisLevel(action.opponent); // le joueur ordi, le rouge

Enfant :

updatePastisLevel(newLevel) {
    // newLevel entre 0 et 10
    newLevel = newLevel * 10;
    this.pastisLevel = 100 - newLevel; // ça fonctionne à l'envers
},

On fait jouer l’adversaire

Concernant l’adversaire, on va se contenter d’une seule IA pour le moment, une IA très agressive :

  • On scanne nos cartes en main
  • Si le niveau de Pastis de l’adversaire est non nul et que l’on peut l’attaquer, on le fait
  • Sinon on attque un emplacement, si on a de quoi

Si rien de tout cela fonctionne :

  • On remplit notre bibine
  • Sinon on ajoute un emplacement aléatoire (en privilégiant le nombre)

Et si rien ne va, on envoie une carte à la poubelle.

Si j’ai le temps, j’améliorerai l’IA avec quelques subtilités :)

Condition de victoire

Pour tester la condition de victoire, je fais mal jouer l’IA : elle se contentera de jeter la première carte venue à la poubelle ce qui me laissera le champs libre pour gagner et tester la victoire.

Lorsque toutes les cases de la grille sont remplient d’une couleur, le jeu s’arrête et on affiche une popup de victoire ou de défaite.

L’algorithme est simple, on écrit une fonction qui teste si le camp passé en paramètre est gagnant, que l’on appelle après que chaque joueur ait joué :

function hasWon(camp) {
  let won = false;

  let count = 0;

  for (let i = 0 ; i < grid.length; i++) {
    if (grid[i].camp == camp) {
      count++;
    }
  }

  if (count >= (9*3)) {
    won = true;
  }

  return won;
}

// ...
playCard(action, 'blue');

if (hasWon('blue')) {
    game_result = 'victory';
} else {
    // On fait jouer l'ordinateur
    playComputerIA();
}

if (hasWon('red')) {
    game_result = 'lost';
}

Conclusion et reste à faire

Nous voilà dans la dernière ligne droite du concours. Il reste une semaine avant la dead-line. Vu que cette version est jouable, je décide de la diffuser en beta-test sur itch.io. Au pire, si tout se passe mal, c’est cette version qui sera jugée. Si je parviens à faire mieux, alors tant mieux !

Ce qu’il reste à faire d’ici une semaine :

  • Déclencher le coup spécial
  • Ajouter du du son (son du coup spécial, musique d’ambiance, son poubelle)
  • Ajouter les portrait des joueurs
  • Faire quelques cartes supplémentaires

S’il me reste du temps :

  • Faire parler les joueurs avec une bulle style BD
  • Encore plus de carte
  • Des effets sur les cartes lors des drops
  • Choix du coup spécial au démarrage de la partie
  • Améliorer la Popup de Victoire avec une image sympa :)